3 Coin Objects
3.1 Introduction
In this chapter we describe how to create object classes in R. Specifically, we will focus on the so-called S3 classes or S3 system. This is one of the three types of Object Oriented (OO) systems available in R, and it is the most common among R packages.
3.2 Objects and Classes
In the previous chapter we learned how to create a toss()
function, and also
how to document it with roxygen comments. So far, we have the following code:
#' Coin toss function
#'
#' Simulates tossing a coin a given number of times
#'
#' @param x coin object (a vector)
#' @param times number of tosses
#' @param prob vector of probabilities for each side of the coin
#' @return vector of tosses
toss <- function(x, times = 1, prob = NULL) {
sample(x, size = times, replace = TRUE, prob = prob)
}
We can invoke toss()
to generate a first series of five tosses, and then
compute the total proportion of heads:
# random seed
set.seed(534)
# five tosses
five <- toss(coin, times = 5)
five
#> [1] "tails" "tails" "tails" "heads" "heads"
# proportion of heads in five
sum(five == "heads") / length(five)
#> [1] 0.4
We can also get a second series of tosses, but this time involving tossing a coin six times. Similarly, we compute the total proportion of heads:
# six tosses
six <- toss(coin, times = 6)
six
#> [1] "heads" "heads" "heads" "tails" "heads" "tails"
# prop of heads in six
sum(six == "heads") / length(five)
#> [1] 0.8
The above code works … except that there is an error; the number of heads in
six
is being divided by 5 instead of 6. R hasn’t detected this error: it
doesn’t know that the division has to be done using length(six)
.
Wouldn’t it be prefarable to have some mechanism that prevented this type of error from happening? Bugs will always be part of any programming activity, but it is better to minimize certain types of errors like the one above.
3.3 S3 Classes
R has two (plus one) object oriented systems, so it can be a bit intimidatin gwhen you read and learn about them for the first time. The goal of this section is not to make you an expert in all R’s OO systems, but to help you become familiar with the so-called “S3 class”.
S3 implements a style of object oriented programming called generic-function OO. S3 uses a special type of function called a generic function that decides which method to call. Keep in mind that S3 is a very casual system: it does not really have a formal definition of classes.
S3 classes are widely-used, in particular for statistical models in the
"stats"
package. S3 classes are very informal in that there is not a formal
definition for an S3 class. Usually, S3 objects are built on top of lists, or
atomic vectors with attributes. But you can also turn functions into S3 objects.
Note that in more formal OOP languages, all functions are associated with a class, while in R, only some are.
3.3.1 Making an object
To make an object an instance of a class, you just take an existing base object
and set a "class"
attribute for it. You can do that during creation of the
object with the function structure()
and its class
argument. For example,
we can create an object of class "coin"
like so:
# object coin via structure()
coin1 <- structure(c("heads", "tails"), class = "coin")
coin1
#> [1] "heads" "tails"
#> attr(,"class")
#> [1] "coin"
You can also create an object first, and then specify its class with the
homonym function class()
:
# object coin via class()
coin2 <- c("heads", "tails")
class(coin2) <- "coin"
coin2
#> [1] "heads" "tails"
#> attr(,"class")
#> [1] "coin"
As any object in R, you can inspect the class of objects coin1
and coin2
with the class()
function:
You can also determine if an object inherits from a specific class using
inherits()
Having a "coin"
object, we can pass it to the toss()
function to simulate
flipping the coin:
3.4 A more robust "coin"
class
Let’s review our class "coin"
. The way we defined a coin object was like this:
While this definition is good to illustrate the concept of an object, its class,
and how to define generic methods, it is a very loose-defined class. One could
create a "coin"
out of c('tic', 'tac', 'toe')
, and then use toss()
on it:
We need a more formal definition of a coin object. For instance, it makes more
sense to require that a coin should only have two sides. In this way, a vector
like ttt
would not be a valid coin.
For convenience purposes, we can define a class constructor function to
initialize a "coin"
object:
# constructor function (version 1)
coin <- function(object = c("heads", "tails")) {
class(object) <- "coin"
object
}
# default coin
coin()
#> [1] "heads" "tails"
#> attr(,"class")
#> [1] "coin"
# another coin
coin(c("h", "t"))
#> [1] "h" "t"
#> attr(,"class")
#> [1] "coin"
Think of this type of function as an auxiliary function that you can use to
generate a default object of class coin
.
3.5 Improving "coin"
objects
To implement the requirement that a coin must have two sides, we can add an
if()
condition to the constructor function in order to check for the length
of the input vector. If the length of the input object is different from two,
then we stop execution; otherwise we proceed with the creation of a coin
object.
# constructor function (version 2)
coin <- function(object = c("heads", "tails")) {
if (length(object) != 2) {
stop("\n'object' must be of length 2")
}
class(object) <- "coin"
object
}
Let’s try our modified constructor function coin()
to create a virtual version
of the US penny like the one in the image below:

Figure 3.1: Example of a US penny (www.usacoinbook.com)
# US penny
penny <- coin(c("lincoln", "shield"))
penny
#> [1] "lincoln" "shield"
#> attr(,"class")
#> [1] "coin"
Now let’s try coin()
with an invalid input vector. In this case, the constructor function will stop()
execution with an error message because the input argument has more than 2 elements.
# invalid coin
ttt <- c('tic', 'tac', 'toe')
coin(ttt)
#> Error in coin(ttt):
#> 'object' must be of length 2
3.5.1 Attributes
Notice how everytime you print the name of a "coin"
object, its class is
displayed in the form of attr(,"class")
.
Interestingly, an R object can have multiple attributes. Right now our coin
objects have just one attribute—its class. But we can add more attributes if
we want to. For example, we could add an attribute prob
. Let’s see why and how.
Recall that the toss()
function simulates flips using sample()
. Also, recall
that one of the arguments of sample()
is prob
which lets you specify
probabilities for each of the elements in the input vector. In order to take
advantage of sample()
’s argument prob
, and being able to create loaded
(i.e. biased) coins, we can add an attribute to our coin object to specify
probabilities for each of its sides.
In other words, in addition to the class
attribute of a coin, the idea is to
assign another attribute for the probability values. We can do this by adding a
prob
argument to the constructor function, and then pass it as an attribute
of the coin object inside the class-constructor function. Here’s how:
# constructor function (version 3)
coin <- function(object=c("heads", "tails"), prob=c(0.5, 0.5)) {
if (length(object) != 2) {
stop("\n'object' must be of length 2")
}
attr(object, "prob") <- prob
class(object) <- "coin"
return(object)
}
coin()
#> [1] "heads" "tails"
#> attr(,"prob")
#> [1] 0.5 0.5
#> attr(,"class")
#> [1] "coin"
In the previous code, the prob
argument takes a vector of probabilities for
each element in object
. This vector is passed to object
via the function
attr()
inside the body of coin()
. Notice the use of a default argument
prob = c(0.5, 0.5)
, that is, a fair coin by default.
3.5.2 Using a list
Another way to implement a constructor function coin()
that returns an object
containing values for both the sides and the probabilities, is to use an R
list. Here’s the code for this option:
# constructor function (version 4)
coin <- function(sides=c("heads", "tails"), prob=c(0.5, 0.5)) {
if (length(sides) != 2) {
stop("\n'sides' must be of length 2")
}
res <- list(sides = sides, prob = prob)
class(res) <- "coin"
return(res)
}
coin()
#> $sides
#> [1] "heads" "tails"
#>
#> $prob
#> [1] 0.5 0.5
#>
#> attr(,"class")
#> [1] "coin"
Personally, I prefer this latter option because it allows you to create more
complex objects as an R list. The important detail is to assign the name of a
class to the created object with the function class()
.
3.5.3 Auxiliary Checker Function
Once again, while constructing an object of class "coin"
we need to check its
validity which involves checking for the validity of prob
. We basically need
to check that prob
and its elements meet the following requirements:
must be numeric and of length 2
probability values must be between 0 and 1
the sum of these values must add up to 1
Here is one possible function to verify the aspects of prob
listed above:
check_prob <- function(prob) {
if (length(prob) != 2 | !is.numeric(prob)) {
stop("\n'prob' must be a numeric vector of length 2")
}
if (any(prob < 0) | any(prob > 1)) {
stop("\n'prob' values must be between 0 and 1")
}
if (sum(prob) != 1) {
stop("\nelements in 'prob' must add up to 1")
}
TRUE
}
Note that I’m adding a TRUE
statement at the end of the function. This is just
an auxiliary value to determine whether the function returns a valid prob
.
Now let’s test check_prob
with valid and invalid values:
# Valid -----------------------
check_prob(c(0.5, 0.5))
#> [1] TRUE
check_prob(c(0.1, 0.9))
#> [1] TRUE
check_prob(c(1/3, 2/3))
#> [1] TRUE
check_prob(c(1/3, 6/9))
#> [1] TRUE
# Invalid -----------------------
# bad length
check_prob(1)
#> Error in check_prob(1):
#> 'prob' must be a numeric vector of length 2
# bad length
check_prob(c(0.1, 0.2, 0.3))
#> Error in check_prob(c(0.1, 0.2, 0.3)):
#> 'prob' must be a numeric vector of length 2
# negative probability
check_prob(c(-0.2, 0.8))
#> Error in check_prob(c(-0.2, 0.8)):
#> 'prob' values must be between 0 and 1
# what should we do in this case?
check_prob(c(0.33, 0.66))
#> Error in check_prob(c(0.33, 0.66)):
#> elements in 'prob' must add up to 1
With the definition of the checker function check_prob()
, we keep refining
our constructor function coin()
:
# constructor function (version 5)
coin <- function(sides=c("heads", "tails"), prob=c(0.5, 0.5)) {
if (length(sides) != 2) {
stop("\n'sides' must be of length 2")
}
check_prob(prob)
res <- list(sides = sides, prob = prob)
class(res) <- "coin"
return(res)
}
coin1 <- coin()
coin1
#> $sides
#> [1] "heads" "tails"
#>
#> $prob
#> [1] 0.5 0.5
#>
#> attr(,"class")
#> [1] "coin"
3.6 Print Method for "coin"
Objects
Every time you type in the name of an object "coin"
, like our penny
example,
the output is displayed in a default, “quick and dirty”, way. In this case R
simply displays the values associated to the sides and their probabilities like
any other list:
# US penny
penny <- coin(c("lincoln", "shield"))
penny
#> $sides
#> [1] "lincoln" "shield"
#>
#> $prob
#> [1] 0.5 0.5
#>
#> attr(,"class")
#> [1] "coin"
Sometimes the default displayed output is all you need. However, there are occasions in which you need to customize the amount and format of information displayed on the screen when you type in the name of an object.
Instead of keeping the default printed values, it would be nice to print penny
and see some cleaner output, perhaps something like this:
object "coin"
side prob
1 "lincoln" 0.5
2 "shield" 0.5
How can we obtain such type of printed output? The answer involves writing a
print
method for objects of class "coin"
. Because print()
is actually a
generic function, what you need to do is to create a a specific print flavor
for class "coin"
. Basically, you define a print.coin()
function, and then
include commands to print information in the desired way:
# print method for objects of class "coin"
print.coin <- function(x) {
cat('object "coin"\n\n')
cd <- data.frame(
side = x$sides, prob = x$prob
)
print(cd)
invisible(x)
}
The next time you print the name of an object of class "coin"
, R will
look for a print
method (which now exists) and dispatch such method.
If you look at the code of print.coin()
, you’ll notice that I’ve decided to
use cat()
to display a short text letting the user know that the printed
object is a "coin"
object. Also, I’m assembling a data frame with the
sides
and prob
vectors so that their content looks nicely organized in a
tabular format when printed by R.
3.7 Extending classes
We can extend the class "coin"
and create a derived class for special types
of coins. For instance, say we want to create a class "quarter"
. One side of
the coin refers to George Washington, while the other side refers to the bald
eagle:
https://en.wikipedia.org/wiki/Quarter_(United_States_coin)
We can create a quarter by first starting with a coin()
of sides
washington and bald-eagle, and then assign a "quarter"
class:
quarter1 <- coin(c("washington", "bald-eagle"))
class(quarter1) <- c("quarter", "coin")
quarter1
#> object "coin"
#>
#> side prob
#> 1 washington 0.5
#> 2 bald-eagle 0.5
Interestingly, our coin quarter1
inherits from "coin"
:
In other words, quartier1
is of class "quarter"
but it is also a "coin"
object.
Likewise, we can create a class for a slightly unbalanced "dime"
:
dime1 <- coin(c("roosevelt", "torch"), prob = c(0.48, 0.52))
class(dime1) <- c("dime", "coin")
dime1
#> object "coin"
#>
#> side prob
#> 1 roosevelt 0.48
#> 2 torch 0.52
Here’s another coin example with a peso from Mexico (where I grew up). When you flip a peso, mexicans don’t really talk about about cara (heads) or cruz (tail). Instead, they say aguila (eagle) or sol (sun):