To Documents
S3 and S4 Classes
References
- Joseph Adler, R in a Nutshell, O'Reilly, 2012
- Kelly Black, R Object-oriented Programming, Packt Publishing, 2014
- Nicolas Christian, Advanced Programming--Lecture 4
www.pitt.edu/~njc23/Lecture4.pdf
Accessed 5/18/13.
- Hadley Wickham, Advanced R, CRC Press, 2015
http://adv-r.had.co.nz/
Accessed 5/15/2015.
Obtaining Datatype Information
- The mode, storage.mode, typeof, and class functions can all be used to obtain the datatype of an object
- mode is the oldest of the three functions. It is compatible with older versions of S. For basic and older objects (S3 and earlier),
mode returns the underlying datatype of the object. For S4 classes, it merely returns "S4".
- The storage.mode function returns details about the way a variable is represented for the purposes of interfacing with other languages.
- The typeof function is designed specifically for R and is usually to be preferred over mode.
- The class function returns information similar to mode, storage.mode, and
typeof for basic datatypes or older objects. It returns implementation
details of S4 class objects.
- Here are some return values for these functions for various datatypes:
| typeof | mode | storage.mode | class |
| logical | logical | logical | logical |
| integer | numeric | integer | integer |
| double | numeric | double | double |
| complex | complex | complex | complex |
| character | character | character | character |
| raw | raw | raw | raw |
| S4 | S4 | S4 | <Class Details> |
Details of S3 Classes
- S3 classes are older and simpler than S4 classes.
- An S3 class is a list object with its class attribute set to the name of the
class.
- Example: A Kid class.
# Create a kid object.
> k1 <- list(name="Alice", gender="F", age=11)
> class(k1) = "Kid"
# Print the Kid object.
> print(k1)
$name
[1] "Alice"
$gender
[1] "F"
$age
[1] 11
attr(,"class")
[1] "Kid"
In practice, a constructor is used to create a class object.
Here is the Kid class constructor:
kid <- function(theName, theGender, theAge) {
theObject <- list(name=theName, gender=theGender,
age=theAge)
class(theObject) = "Kid"
return(theObject)
}
We create another Kid object using the constructor:
> k2 <- kid("Mark", "M", 12)
> print(k2)
$name
[1] "Alice"
$gender
[1] "F"
$age
[1] 11
attr(,"class")
[1] "Kid"
Later we define a custom print method for the Kid
class. We don't need to define a generic print method on which to base our print.Kid
method because a generic print method already exists. We can see all of the print methods that are
currently defined:
> methods(print)
[1] print.acf*
[2] print.anova
[3] print.aov*
... # 175 more print method names
This method call returns the list of all 178 defined print
methods, of which 146 are non-visible and marked with *.
If a method is visible via methods(print), we can view its source code by
typing its name in R:
> print.data.frame
function (x, ..., digits = NULL, quote = FALSE, right = TRUE,
row.names = TRUE)
{
n <- length(row.names(x))
if (length(x) == 0L) {
cat(sprintf(ngettext(n, "data frame with 0 columns and %d row",
"data frame with 0 columns and %d rows"), n), "\n",
sep = "")
}
else if (n == 0L) {
print.default(names(x), quote = FALSE)
cat(gettext("<0 rows> (or 0-length row.names)\n"))
}
else {
m <- as.matrix(format.data.frame(x, digits = digits,
na.encode = FALSE))
if (!isTRUE(row.names))
dimnames(m)[[1L]] <- if (identical(row.names, FALSE))
rep.int("", n)
else row.names
print(m, ..., quote = quote, right = right)
}
invisible(x)
}
<bytecode: 0x0000000010f25020>
<environment: namespace:base>
If a method is non-visible, such as print.aov, simply typing it in the R
Console will not display its source code. Use the getAnywhere function to
display the source code of a non-visible function. (Try it!)
Now we define a print method (named print.Kid) for our
Kid class:
print.Kid <- function(theObject) {
cat(theObject$name, theObject$gender,
theObject$age, "\n")
}
Now print the k1 and k2 Kid objects using our custom print method
print.Kid:
> print(k1)
Alice F 11
> print(k2)
Mark M 12
Next we define the haveBirthday method. Because no
generic haveBirthday method exists, we must first define one before
defining haveBirthday.Kid:
haveBirthday <- function(theObject) {
UseMethod("haveBirthday", theObject)
}
Now we can define haveBirthday.Kid:
haveBirthday.Kid <- function(theObject) {
theObject$age <- theObject$age + 1
return(theObject)
}
Test the haveBirthday.Kid method:
> k1 <- haveBirthday(k1)
> print(k1)
Alice F 12
> k2 <- haveBirthday(k2)
> print(k2)
Mark M 13
For S3 classes, whenever a new method is defined, it is customary to
define a default version of that method in case the method is called on
the wrong type of object. For example:
haveBirthday.default <- function(theObject) {
warning("Default haveBirthday method ",
"called with unrecognized object")
return(theObject)
}
Finally, we define accessor methods (getters) that return the values of the individual
components name, gender, and age:
getName <- function(theObject) {
UseMethod("getName", theObject)
}
getName.Kid <- function(theObject) {
return(theObject$name)
}
getGender <- function(theObject) {
UseMethod("getGender", theObject)
}
getGender.Kid <- function(theObject) {
return(theObject$gender)
}
getAge <- function(theObject) {
UseMethod("getAge", theObject)
}
getAge.Kid <- function(theObject) {
return(theAge$name)
}
Test the accessor methods:
> getName(k1)
[1] "Alice"
> getName(k2)
[1] "Mark"
> getGender(k1)
[1] "F"
> getGender(k2)
[1] "M"
> getAge(k1)
[1] 12
> getAge(k2)
[1] 12
Here is the UML class diagram for the Kid class.
The UML class diagram has three sections: the class name, the fields or components, and
the functions for that class.
For the Kid class UML diagram, the class name is Kid, the fields are name, gender, and age,
and the functions are the constructor function kid, print, haveBirthday, and the accessor methods
getName, getGender, and getAge.
See the Die and
InfantClass examples.
The RollToSuccess Example
shows how to use inheritance to implement the RollToSuccess
base class and the
Die and Coin derived classes.
Details of S4 Classes
getClass Get information about a class. Example:
> getClass("kid")
Class "kid" [in ".GlobalEnv"]
Slots:
Name: name gender age
Class: character character numeric
attributes Older function to get class information about an object. Example:
> attributes(k)
$name
[1] "Alice"
$gender
[1] "F"
$age
[1] 12
$class
[1] "kid"
attr(,"package")
[1] ".GlobalEnv"
getSlots Get the slots from the class definition. Example:
> getSlots("kid")
name gender age
"character" "character" "numeric"
slotNames Get the slot names from an object of the class. Example:
> slotNames(k)
[1] "name" "gender" "age"
showMethods Show all the implementations of a generic method or all methods that
are attached to a class. Examples:
> showMethods("show")
Function: show (package methods)
object="ANY"
object="classRepresentation"
object="envRefClass"
object="function"
(inherited from: object="ANY")
object="genericFunction"
object="genericFunctionWithTrace"
object="kid"
object="MethodDefinition"
object="MethodDefinitionWithTrace"
object="MethodSelectionReport"
object="MethodsList"
(inherited from: object="ANY")
object="MethodWithNext"
object="MethodWithNextWithTrace"
object="namedList"
object="nonstandardGenericFunction"
(inherited from: object="genericFunction")
object="ObjectsWithPackage"
object="oldClass"
object="refClassRepresentation"
object="refMethodDef"
object="refObjectGenerator"
object="signature"
object="sourceEnvironment"
object="traceable"
> showMethods("age")
Function: age (package .GlobalEnv)
x="kid"
> showMethods(classes="kid")
Function: age (package .GlobalEnv)
x="kid"
Function: age<- (package .GlobalEnv)
x="kid"
Function: gender (package .GlobalEnv)
x="kid"
Function: haveBirthday (package .GlobalEnv)
x="kid"
Function: initialize (package methods)
.Object="kid"
(inherited from: .Object="ANY")
Function: name (package .GlobalEnv)
x="kid"
Function: show (package methods)
object="kid"
getMethod Get details of a method with a specified signature. Example:
> getMethod("haveBirthday", "kid")
Method Definition:
function (x)
{
x@age <- x@age + 1
x
}
Signatures:
x
target "kid"
defined "kid"
existsMethod Determine if a method exists with a specified signature. Example:
> existsMethod("haveBirthday", "kid")
[1] TRUE