Object Oriented Programming In R

0
414

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.

Figure 1: Consumer Price Index
Figure 1: Consumer Price Index

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.

LEAVE A REPLY

Please enter your comment!
Please enter your name here