15.3 Writing S3 classes

Let’s now create our own S3 class! We will go back to the example from the introduction and create a class “student”. A student will have attributes name, age and field of study, as well as a vector of grades. A student can introduce himself and compute his average grade.

We start by creating an object that holds these attributes of a student. Then, we assign it to a class “student”.

> liam <- list(name = "Liam", age = 25, field = "biology", grades = c(4.5, 6, 5.5))
> class(liam) <- "student"

Now let’s write an S3 print method for the student class. This class needs to be named print.student(); otherwise UseMethod() will not find it. The method should also take the same arguments as print(); otherwise, R will give an error when it tries to pass the arguments to print.student():

> print.student <- function(x, ...) {
+   output <- paste0("Hello there! I'm a student. My name is ", x$name, ", I'm ", x$age, " years old and I study ", x$field, ".")
+   print(output)
+ }

Does our method work? Yes, and not only that; R uses the print method to display the contents of liam.

> print(liam)
[1] "Hello there! I'm a student. My name is Liam, I'm 25 years old and I study biology."

Note that we don’t need to write the generic function print() itself. This method has already been implemented in base R.

Let’s now implement a generic function calculateMeanGrade(). There is no generic R function that does this already, so we have to implement 1) the generic function calculateMeanGrade() and 2) the specialized function calculateMeanGrade.student()!

> # implement generic function
> calculateMeanGrade <- function(stud) {
+   UseMethod("calculateMeanGrade", stud)
+ }
> 
> # implement S3 function
> calculateMeanGrade.student <- function(stud){
+   meanGrade <- mean(stud$grades)
+   return(meanGrade)
+ }
> 
> # calculate mean grade
> calculateMeanGrade(liam)
[1] 5.333333

Note that you should never directly call the function calculateMeanGrade.student(). Although this would give you exactly the same result, the whole point of S3 classes and generic functions would be lost. Maybe you have other classes - e.g. “secondaryStudent”, “universityStudent” - at some point that also need to calculate mean grades, but require a different implementation. The beauty of S3 programming is that you don’t need to remember the class of the object you’re dealing with. No matter if it is a secondaryStudent, a universityStudent or simply a student - you can just call calculateMeanGrade() and R will do whatever it needs to do for you.