R Classes and Objects ( Chapter 1 Episode 8 Final Episode)
Table of contents
R supports the features of object-oriented programming. Actually, R treats everything as an object. An R object refers to a data structure with attributes and methods that act on the attributes. A class is a blueprint of an object. Think of a class as a sketch/prototype of a house. It describes the details of windows, floors, doors, etc. The house will then be built based on the description. Most programming languages have a single class system, but R comes with 3 different class systems. These include the following:
S3 Class
S4 Class
Reference Class
S3 Class
This type of R class system is primitive in nature. It doesn’t have a formal definition. To create an object of this class, we simply have to add a class attribute to it. Such simplicity explains why this class is highly used in R programming. Actually, the majority of the in-built classes are of this type.
Definition
There is no formal or predefined way to define an S3 class. A list whose class attribute has been set to a class name is an S3 object. The list components will become the member variables of the object.
Syntax
s <- list(name = "Alice", age = 20, GPA = 3.5)
class(s) <- "student"
We have created a class from the list and the class has been given the name student. We have gone further to create an object from the class, and the object has been named s. To see the details of this object, just type its name on the R console and hit the return key as shown below;
> s
$name
[1] "Alice"
$age
[1] 20
$GPA
[1] 3.5
Creating objects from Constructors
In many cases, we use a function having the same name as a class to create objects. Such a mechanism brings uniformity when it comes to the creation of objects and it makes them look the same. It is possible for us to impose integrity checks when it comes to the attributes. The class attributes of an object can be set using the attr() function.
Use case 1
#create a list
s <- list(name = "Alice", age = 20, GPA = 3.5)
#create a class
class(s) <- "student"
#create a constructor function for class "student"
student <- function(n,a,g) {
#impose integrity checks
if(g>4 || g<0) stop("Student's GPA must be between 0 and 4")
value <- list(name = n, age = a, GPA = g)
# we can set a class using either the class() or attr() function
attr(value, "class") <- "student"
value }
We can now use the constructor to create objects:
s <- student("John", 24, 3.7)
output
> s
$name
[1] "John"
$age
[1] 24
$GPA
[1] 3.7
attr(,"class")
[1] "student"
Use case 2
s <- student("Alice", 22, 5)
> s <- student("Alice", 22, 5)
Error in student("Alice", 22, 5) : Student's GPA must be between 0 and 4
Methods and Generic Functions
Anytime we write the name of the object, all its internals will be printed. While in the interactive mode, anytime we type the name, it will be printed by use of the print() function.
> s
$name
[1] "John"
$age
[1] 24
$GPA
[1] 3.7
attr(,"class")
[1] "student"
Creating Own Methods
We can implement our own method by using the print.student() method. Let us demonstrate this:
print.student <- function(obj) {
cat(obj$name, "\n")
cat(obj$age, "years old\n")
cat("GPA:", obj$GPA, "\n") }
output
> s
John
24 years old
GPA: 3.7
To remove the class attitude:
unclass(s)
$name
[1] "John"
$age
[1] 24
$GPA
[1] 3.7
S4 Class
These types of classes are advanced compared to the S3 classes. They come with a formally defined structure that helps them make objects of the same class look similar. It is defined and a uniform way of creating objects makes code very safe and prevents us from making basic mistakes.
Defining S4 Classes
To define an S4 class, we use the setClass() function. In R, the member variables are referred to as slots. During a class definition, we are required to set the class name and the slots that it will have. The following example demonstrates how to define an S4 class in R:
setClass("Student", representation(name = "character", age = "numeric"))
setClass("ClassInfo", representation(generic = "Student"), contains = "Student")
In the above example, we have created a class named student with three slots, name, age, and GPA
Creating S4 Objects
To create objects from S4 classes, we use the new() function. Let us use an example to demonstrate this: We will use the new() function to create an object. We will also provide the name of the class as well as the slots.
std1 <- new("Student", name = "Hadley", age = 31)
output
> std1
An object of class "Student"
Slot "name":
[1] "Hadley"
Slot "age":
[1] 31
Modifying Slots
R allows us to access and modify the slots. To access the components of a list, we used the $, but to access a slot of an object, we use the @. Here is how we can access the slots:
std1@name
std1@age
output
> std1@name
[1] "Hadley"
> std1@age
[1] 31
Methods and Generic Functions
Just like the S3 class, S4 class methods also belong to the generic functions instead of the class itself. The S4 generics can be used in much the same way as the S3 generics. We can use the showMethods() function to list all the S4 generic functions and methods.
showMethods()
We use the show() function, which is an S4 generic function. Let us use the isS4() function to check whether a function is generic or not:
isS4(show)
isS4(print)
output
> isS4(show)
[1] TRUE
> isS4(print)
[1] FALSE
>
Writing Own Methods
To create our own method, we can use the setMethod() helper function. Here is an example showing how we can implement the class method for show() generic:
setMethod("show", "Student",
function(object) {
cat(object@name, "\n")
cat(object@age, "years old\n") })
s <- new("Student",name="Kate", age=21)
s
output
> s
Kate
21 years old
Reference Class
The reference class system is similar to the object-oriented programming we use in other programing languages such as Java and C++. It was introduced later. The reference class is simply S4 class with an environment added to it
Defining Reference Classes
A reference class can be defined in the same way as an S4 class. However, instead of using the setClass() function, we use the setRefClass() function. The name of the class is passed as the argument to the function. Let us demonstrate this:
setRefClass("Student")
The member variables for the class should be part of the definition. In the reference class system, the member variables are referred to as fields. Here is how we can define a reference class in R with three fields:
setRefClass("student", fields = list(name = "character", age = "numeric"))
Creating Reference Objects
The setRefClass() function gives us a generator function that can help us create objects of that class. This is demonstrated below:
student <- setRefClass("Student", fields = list(name = "character", age = "numeric"))
output
> student
Generator for class "Student":
Class fields:
Name: name age
Class: character numeric
Class Methods:
"field", "trace", "getRefClass", "initFields", "copy", "callSuper",
".objectPackage", "export", "untrace", "getClass", "show",
"usingMethods", ".objectParent", "import"
Reference Superclasses:
"envRefClass"
We now have a generator function named student(). We can use it to create new objects:
s <- student(name = "Alice", age = 20)
Modifying Fields
For us to modify the fields, we must first access them. We can access the fields of an object using the $ operator. Here is an example:
s$name
s$age
To modify any field, we only have to reassign it. Let us change the name of the student:
s$name <- "Kate"
We can then view the details of the object to see whether the change was successful:
> s
Reference Methods
Methods that have been defined for a reference class do not belong to the generic functions as it is the case in S3 and S4 classes. All reference classes come with predefined methods since they are inherited from the envRefClass superclass.
> student
Generator for class "Student":
Class fields:
Name: name age
Class: character numeric
Class Methods:
"field", "trace", "getRefClass", "initFields", "copy", "callSuper",
".objectPackage", "export", "untrace", "getClass", "show",
"usingMethods", ".objectParent", "import"
Reference Superclasses:
"envRefClass"
The above list shows methods such as list(), copy(), field() etc. It is possible for us to create our own methods for the class. To do this, we pass a list of function definitions to the methods argument of the setRefClass() function during class definition.
student <- setRefClass("student",
fields = list(name = "character", age = "numeric"),
methods = list(
increase_age = function(a) {
age <<- age + a },
decrease_age = function(a) {
age <<- age - a }))
In the above code, we have defined two methods, increase_age() and decrease_age(), that will modify the age field. We should use the non-local assignment operator, that is, <<-, since age is not in the local environment of the method. If we had used the simple assignment operator, that is, <-, a local variable named age would have been created. This is not what we need to achieve. In such a case, R will give us a warning. The example given below demonstrates how to use the methods defined above:
s <- student(name = "Peter", age = 21)
Let us now use the methods. First, let us increase the age by 4 years:
s$increase_age(4)
s$age
output
> s <- student(name = "Peter", age = 21)
> s$increase_age(4)
> s$age
[1] 25
s$decrease_age(5)
s$age
output
> s$decrease_age(5)
> s$age
[1] 20