R is a functional programming language, but it does have support for object-oriented programming (OOP). In this thirteenth article in the R series, we shall learn about S3 and S4 classes (recommended) in R.
Many software packages like ‘lattice’ and ‘ggplot2’ have been written using R objects. We will use the R version 4.1.2 installed on Parabola GNU/Linux-libre (x86-64) for the code snippets.
$ R --version R version 4.1.2 (2021-11-01) -- “Bird Hippie” Copyright (C) 2021 The R Foundation for Statistical Computing Platform: x86_64-pc-linux-gnu (64-bit) R is free software and comes with ABSOLUTELY NO WARRANTY. You are welcome to redistribute it under the terms of the GNU General Public License versions 2 or 3. For more information about these matters see https://www.gnu.org/licenses/.
The basic classes for built-in types are listed below.
builtin | function |
character | character |
closure | function |
complex | complex |
double | numeric |
environment | environment |
expression | expression |
integer | integer |
language | call |
list | list |
logical | logical |
NULL | NULL |
pairlist | pairlist |
special | function |
symbol | name |
S3 class
The S3 object is an R primitive object with attributes and a class name. Consider the mtcars data set in the lattice library:
> library(lattice) > head(mtcars) mpg cyl disp hp drat wt qsec vs am gear carb Mazda RX4 21.0 6 160 110 3.90 2.620 16.46 0 1 4 4 Mazda RX4 Wag 21.0 6 160 110 3.90 2.875 17.02 0 1 4 4 Datsun 710 22.8 4 108 93 3.85 2.320 18.61 1 1 4 1 Hornet 4 Drive 21.4 6 258 110 3.08 3.215 19.44 1 0 3 1 Hornet Sportabout 18.7 8 360 175 3.15 3.440 17.02 0 0 3 2 Valiant 18.1 6 225 105 2.76 3.460 20.22 1 0 3 1
We can create a list for the ‘Mazda RX4’ values, and define a class ‘Car’ for the same as shown below:
> m <- list(mpg=21.0, cyl=6, disp=160) > class(m) <- “Car” > m $mpg [1] 21 $cyl [1] 6 $disp [1] 160 attr(,”class”) [1] “Car”
We can also add the ‘Hornet 4 Drive’ attributes to a new instance, as follows:
> h <- list(mpg=21.4, cyl=6, disp=258) > class(h) <- “Car” > h $mpg [1] 21.4 $cyl [1] 6 $disp [1] 258 attr(,”class”) [1] “Car”
The attributes() function returns the object’s attributes, as shown below:
> attributes(h) $names [1] “mpg” “cyl” “disp” $class [1] “Car”
You can also add attributes to an object using the attr() function. For example:
> attr(m, “hp”) <- c(110) > print(m) $mpg [1] 21 $cyl [1] 6 $disp [1] 160 attr(,”hp”) [1] 110
We can define a method to operate on the class object, as follows:
> print.Car <- function(obj) { + cat(“mpg: “, obj$mpg, “\n”) + cat(“cyl: “, obj$cyl, “\n”) + cat(“disp: “, obj$disp, “\n”) + } > print(m) mpg: 21 cyl: 6 disp: 160
A new Gear class can be created with simple inheritance from the Car class, as shown below:
> g <- list(mpg=21, cyl=6, disp=160, gear=4) > class(g) <- c(“Gear”, “Car”) > inherits(g, “Car”) [1] TRUE > print(g) mpg: 21 cyl: 6 disp: 160
Another example for S3 classes is the time series ‘ts’ objects. Consider the consumer prices (annual %) inflation data for India from July 2021 to June 2022. The ts() function can be used to create the time-series objects as illustrated below:
> cpi <- ts(data=c(162.5, 162.9, 163.2, 165.5, 166.7, 166.2, 165.7, 166.1, 167.7, 170.1, 171.7, 172.6)) > cpi Time Series: Start = 1 End = 12 Frequency = 1 [1] 162.5 162.9 163.2 165.5 166.7 166.2 165.7 166.1 167.7 170.1 171.7 172.6 > > attributes(cpi) $tsp [1] 1 12 1 $class [1] “ts” > typeof(cpi) [1] “double”
The plot of the growing Consumer Price Index is shown in Figure 1.
The S3 classes do not have the structure of S3 objects and encapsulation is not enforced by the language. You can identify the hidden methods in a package by identifying an asterisk in their names, as shown below:
> methods(plot) [1] plot,ANY-method plot,color-method plot.acf* [4] plot.data.frame* plot.decomposed.ts* plot.default [7] plot.dendrogram* plot.density* plot.ecdf [10] plot.factor* plot.formula* plot.function [13] plot.ggplot* plot.gtable* plot.hcl_palettes* [16] plot.hclust* plot.histogram* plot.HoltWinters* [19] plot.isoreg* plot.lm* plot.medpolish* [22] plot.mlm* plot.ppr* plot.prcomp* [25] plot.princomp* plot.profile.nls* plot.R6* [28] plot.raster* plot.shingle* plot.spec* [31] plot.stepfun plot.stl* plot.table* [34] plot.trans* plot.trellis* plot.ts [37] plot.tskernel* plot.TukeyHSD*
You can also create an S4 class based on the S3 class using the setOldClass function. It accepts the following arguments:
Argument | Description |
Classes | A character vector of old S3 classes |
prototype | An object to use as a prototype |
test | A logical value to test inheritance for the object |
S4Class | A class name or definition for an S4 class |
S4 class
The S4 class implementation is recommended for classes and methods for new software. The S4 classes support formal class definitions, multiple inheritance, encapsulation and parametric polymorphism. The setClass() function can be used to define a new class. We can define a class for Car, as shown below:
> setClass( + “Car”, + representation( + mpg = “numeric”, + cyl = “numeric”, + disp = “numeric” + ) + )
The following arguments are accepted by the setClass() function:
Argument | Description |
Class | The name of the new class |
representation | A list of slot and object names in the class |
contains | A character vector of super classes |
prototype | A default object for slots in the class |
validity | A function to check validity of an object in the class |
where | The environment to store the definition |
sealed | A logical value that indicates if the class can be redefined |
package | A package name for the class |
S3 methods | A logical value on whether to have S3 methods for the class |
An instance for the Car class can be created using the new() function. For example:
> m <- new(“Car”, + mpg=21.4, + cyl=6, + disp=258) > m An object of class “Car” Slot “mpg”: [1] 21.4 Slot “cyl”: [1] 6 Slot “disp”: [1] 258
The setValidity() function is used to add validity checks for the members in the class. For example, we can check that the number of cylinders for the cars should be less than eight, as shown below:
> setValidity(“Car”, + function(obj) { + obj@cyl <= 8 + } + ) Slots: Name: mpg cyl disp Class: numeric numeric numeric
The validObject() function can then be applied to check if any object conforms to the class specification, as shown below:
> validObject(m) [1] TRUE
If we try to create a Car object with ten cylinders, the command fails. For example:
> invalid <- new(“Car”, + mpg=18.7, + cyl=10, + disp=360)
Error in validObject(.Object) : invalid class “Car” object: FALSE
The object members can be obtained directly or by using a function, as illustrated below:
> m@mpg [1] 21.4 > getmpg <- function(object) {object@mpg} > getmpg(m) [1] 21.4
You can also use generic functions in R that support polymorphism using the setGeneric() function. This function accepts the following arguments:
Argument | Description |
name | A name for the generic function |
def | An optional definition for the function |
group | An optional group generic name to be associated with the function |
valueClass | An optional name of the class to which the function should belong |
where | The environment in which to store the generic function |
package | A package name |
signature | Names of formal arguments and classes for the same |
simpleInheritanceOnly | A logical value to use simple inheritance only |
useAsDefault | A logical value to indicate if to use as the default method |
The showMethods() function displays the function, environment and its associated objects, as indicated below:
> showMethods(disp) Function: disp (package .GlobalEnv) object=”ANY” object=”Car” (inherited from: object=”ANY”)
You can also define your own methods using the setMethod() function. In the following example, a show() method is added to the Car class.
> setMethod(“show”, + “Car”, + function(obj) { + cat(“mpg: “, obj@mpg, “\n”) + cat(“cyl: “, obj@cyl, “\n”) + cat(“disp: “, obj@disp, “\n”) + } + ) > m mpg: 21.4 cyl: 6 disp: 258 > showMethods(show) Function: show (package methods) object=”ANY” object=”Car” object=”classGeneratorFunction” object=”classRepresentation” object=”envRefClass” object=”externalRefMethod” object=”genericFunction” object=”genericFunctionWithTrace” object=”MethodDefinition” object=”MethodDefinitionWithTrace” object=”MethodSelectionReport” object=”MethodWithNext” object=”MethodWithNextWithTrace” object=”namedList” object=”ObjectsWithPackage” object=”oldClass” object=”refClassRepresentation” object=”refMethodDef” object=”refObjectGenerator” object=”signature” object=”sourceEnvironment” object=”traceable”
The setMethods() function accepts the following arguments:
Argument | Description |
f | A name of a generic function |
signature | Names of formal arguments and classes for the same |
definition | A function to be called when method is invoked |
where | The environment where the method is defined |
sealed | A label to indicate if the class can be redefined |
A number of helper functions are available in R for its classes, objects and functions. A few examples are illustrated below:
> isS4(m) [1] TRUE > isS4(show) [1] TRUE > isGeneric(“disp”) [1] TRUE > isGroup(show) [1] FALSE > signature(“Car”) [1] “Car” > hasMethod(show) [1] TRUE > getMethod(show) Method Definition (Class “derivedDefaultMethod”): function (object) showDefault(object) <bytecode: 0x55ab1e472cd8> <environment: namespace:methods> Signatures: object target “ANY” defined “ANY”
You are encouraged to read the manual pages for the above R functions to learn more on their arguments, options and usage.