Chapter 3 R programming
This chapter is about base R stuff that I find important and that is often overlooked or unknown to most R users.
Learn more with the Advanced R book.
3.1 Common mistakes
If you are using R and you think you’re in hell, this is a map for you.
– Patrick Burns
3.1.1 Equality
#> [1] FALSE
#> [1] 0.10000000000000000555 0.20000000000000001110 0.29999999999999998890
#> [1] TRUE
#> [1] "Mean relative difference: 1.850372e-16"
#> [1] "Mean relative difference: 0.3333333"
#> [1] FALSE
#> [1] TRUE
3.1.2 Arguments
#> [1] 0
#> [1] 10
#> [1] 0
#> [1] 0
How to explain the issue with mean and median? Let us look at the parameters of these functions:
#> function (..., na.rm = FALSE)
#> NULL
#> function (x, ...)
#> NULL
#> function (x, na.rm = FALSE, ...)
#> NULL
One solution is to always use a vector:
#> [1] 0
#> [1] 10
#> [1] 5
#> [1] 5
3.2 R base objects
3.2.1 Types
There are several “atomic” types of data: logical, integer, double and character (in this order, see below). There are also raw and complex, but they are rarely used.
You cannot mix types in an atomic vector, but you can in a list. Coercion will automatically occur when you mix types in a vector:
#> [1] FALSE
#> [1] "logical"
#> [1] 1 2 3 4 5 6 7 8 9 10
#> [1] "integer"
#> [1] 0 1 2 3 4 5 6 7 8 9 10
#> [1] 10.5
#> [1] "double"
#> [1] 1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0 10.5
#> [1] "1" "2" "3" "4" "5" "6" "7" "8" "9" "10" "10.5" "a"
#> [[1]]
#> [1] 1
#>
#> [[2]]
#> [1] "a"
#> [1] TRUE
3.2.2 Exercise
Use the automatic type coercion to convert this boolean matrix to a numeric one (with 0s and 1s). [What do you need to change in your code to get an integer matrix instead of a numeric one?]
#> [,1] [,2] [,3] [,4]
#> [1,] FALSE TRUE TRUE FALSE
#> [2,] TRUE FALSE TRUE TRUE
#> [3,] FALSE FALSE TRUE FALSE
3.3 Base objects and accessors
3.3.1 Objects
“atomic” vector: vector of one base type (see above).
scalar: this doesn’t exist in R, this is a vector of length 1.
matrices / arrays: a vector with some dimensions (as attributes).
#> [1] 1 2 3 4 5 6 7 8 9 10 11 12
#> [,1] [,2] [,3] [,4]
#> [1,] 1 4 7 10
#> [2,] 2 5 8 11
#> [3,] 3 6 9 12
#> [1] "matrix" "array"
#> , , 1
#>
#> [,1] [,2]
#> [1,] 1 4
#> [2,] 2 5
#> [3,] 3 6
#>
#> , , 2
#>
#> [,1] [,2]
#> [1,] 7 10
#> [2,] 8 11
#> [3,] 9 12
#> [1] "array"
list: vector of elements with possibly different types in it.
data.frame: a list whose elements have the same lengths, and formatted somewhat as a matrix.
#> Sepal.Length Sepal.Width Petal.Length Petal.Width Species
#> 1 5.1 3.5 1.4 0.2 setosa
#> 2 4.9 3.0 1.4 0.2 setosa
#> 3 4.7 3.2 1.3 0.2 setosa
#> 4 4.6 3.1 1.5 0.2 setosa
#> 5 5.0 3.6 1.4 0.2 setosa
#> 6 5.4 3.9 1.7 0.4 setosa
#> [1] 150 5
#> [1] 5
3.3.2 Accessors
- The
[accessor is used to access a subset of the data with the same class.
#> [1] 1 2 3 4 5
#> [1] 2 3
#> [1] 2 3 4 5 NA NA NA
#> [,1] [,2] [,3] [,4]
#> [1,] 1 4 7 10
#> [2,] 2 5 8 11
#> [3,] 3 6 9 12
#> [1] 4 5 6 7 8 9
#> $a
#> [1] 1
#>
#> $b
#> [1] "I love R"
#>
#> $c
#> [,1] [,2] [,3]
#> [1,] 1 3 5
#> [2,] 2 4 6
#> $b
#> [1] "I love R"
#>
#> $c
#> [,1] [,2] [,3]
#> [1,] 1 3 5
#> [2,] 2 4 6
#> Sepal.Length Sepal.Width Petal.Length Petal.Width Species
#> 1 5.1 3.5 1.4 0.2 setosa
#> 2 4.9 3.0 1.4 0.2 setosa
#> 3 4.7 3.2 1.3 0.2 setosa
#> 4 4.6 3.1 1.5 0.2 setosa
#> 5 5.0 3.6 1.4 0.2 setosa
#> 6 5.4 3.9 1.7 0.4 setosa
#> Petal.Length Petal.Width
#> 1 1.4 0.2
#> 2 1.4 0.2
#> 3 1.3 0.2
#> 4 1.5 0.2
#> 5 1.4 0.2
#> 6 1.7 0.4
#> [1] "data.frame"
You can also use a logical and character vectors to index these objects.
#> [1] 1 2 3 4
#> [1] 2 4
#> [1] 2 4
#> Petal.Length Species
#> 1 1.4 setosa
#> 2 1.4 setosa
#> 3 1.3 setosa
#> 4 1.5 setosa
#> 5 1.4 setosa
#> 6 1.7 setosa
- The
[[accessor is used to access a single element.
#> [1] 1 2 3 4 5 6 7 8 9 10
#> [1] 3
#> [1] "I love R"
#> [1] setosa setosa setosa setosa setosa setosa setosa
#> [8] setosa setosa setosa setosa setosa setosa setosa
#> [15] setosa setosa setosa setosa setosa setosa setosa
#> [22] setosa setosa setosa setosa setosa setosa setosa
#> [29] setosa setosa setosa setosa setosa setosa setosa
#> [36] setosa setosa setosa setosa setosa setosa setosa
#> [43] setosa setosa setosa setosa setosa setosa setosa
#> [50] setosa versicolor versicolor versicolor versicolor versicolor versicolor
#> [57] versicolor versicolor versicolor versicolor versicolor versicolor versicolor
#> [64] versicolor versicolor versicolor versicolor versicolor versicolor versicolor
#> [71] versicolor versicolor versicolor versicolor versicolor versicolor versicolor
#> [78] versicolor versicolor versicolor versicolor versicolor versicolor versicolor
#> [85] versicolor versicolor versicolor versicolor versicolor versicolor versicolor
#> [92] versicolor versicolor versicolor versicolor versicolor versicolor versicolor
#> [99] versicolor versicolor virginica virginica virginica virginica virginica
#> [106] virginica virginica virginica virginica virginica virginica virginica
#> [113] virginica virginica virginica virginica virginica virginica virginica
#> [120] virginica virginica virginica virginica virginica virginica virginica
#> [127] virginica virginica virginica virginica virginica virginica virginica
#> [134] virginica virginica virginica virginica virginica virginica virginica
#> [141] virginica virginica virginica virginica virginica virginica virginica
#> [148] virginica virginica virginica
#> Levels: setosa versicolor virginica
Figure 3.1: Indexing lists in R. [Source: https://goo.gl/8UkcHq]
- Beware partial matching
#> [1] 1 2 3 4 5
#> NULL
#> [1] 1 2 3 4 5
- Special use of the
[accessor for array-like data.
#> [,1] [,2] [,3] [,4]
#> [1,] 1 4 7 10
#> [2,] 2 5 8 11
#> [3,] 3 6 9 12
#> [1] 1 4 7 10
#> [,1] [,2]
#> [1,] 1 4
#> [2,] 2 5
#> [3,] 3 6
#> [1] 1 4
#> [,1] [,2]
#> [1,] 1 4
#> [,1] [,2]
#> [1,] 1 1
#> [2,] 3 4
#> [3,] 2 2
#> [1] 1 12 5
#> [,1] [,2] [,3] [,4]
#> [1,] 1 4 7 10
#> [2,] 2 5 8 11
#> [3,] 3 6 9 12
#> [,1] [,2] [,3] [,4]
#> [1,] 2 2 2 2
#> [2,] 2 2 2 2
#> [3,] 2 2 2 2
If you use arrays with more than two dimensions, simply add an additional comma for every new dimension.
3.3.3 Exercises
Use the dimension attribute to make a function that computes the sums every n elements of a vector. In which order are matrix elements stored? [Which are the special cases that you should consider?]
#> [1] 3 7 11 15 19Compute the means of every numeric columns of the
irisdataset. Expected result:#> Sepal.Length Sepal.Width Petal.Length Petal.Width #> 5.843333 3.057333 3.758000 1.199333Convert the following matrix to a vector by replacing (0, 0) -> 0; (0, 1) -> 1; (1, 1) -> 2; (1, 0) -> NA.
#> [,1] [,2] #> [1,] 0 0 #> [2,] 0 1 #> [3,] 0 0 #> [4,] 0 0 #> [5,] 1 1 #> [6,] 0 1 #> [7,] 0 1 #> [8,] 1 0 #> [9,] 1 1 #> [10,] 0 0by using this matrix:
#> [,1] [,2] #> [1,] 0 1 #> [2,] NA 2Start by doing it for one row, then by using
apply(), finally replace it by a special accessor; what is the benefit?Expected result:
#> [1] 0 1 0 0 2 1 1 NA 2 0
3.4 Useful R base functions
In this section, I present some useful R base functions (also see this comprehensive list in French and this one in English):
3.4.1 General
#> 'data.frame': 150 obs. of 5 variables:
#> $ Sepal.Length: num 5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
#> $ Sepal.Width : num 3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
#> $ Petal.Length: num 1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
#> $ Petal.Width : num 0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
#> $ Species : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...
#> [1] "a" "b" "c" "d" "decode" "l"
#> [7] "mat" "n" "two_col_ind" "vec" "x" "y"
# Remove objects from the environment
rm(list = ls()) ## remove all objects in the global environment#> [[1]]
#> [1] 1
#>
#> [[2]]
#> [1] 2
#>
#> [[3]]
#> [1] 3
#>
#> [[4]]
#> [1] 4
#>
#> [[5]]
#> [1] 5
#> [1] 1 2 3 4 5
3.4.2 Sequence and vector operations
#> [1] 1 2 3 4 5 6 7 8 9 10
#> [1] 1 3 5 7 9
#> [1] 1 12 23 34 45 56 67 78 89 100
#> [1] 1 2 3 4 5
#> [1] 1 2 3 4
#> [1] 1 2 3 4 1 2 3 4
#> [1] 1 1 2 2 3 3 4 4
#> [1] 1 1 1 1 2 2 2 3 3 4
#> [1] 1 2 3 1 2 3 1 2
#> [,1] [,2] [,3] [,4] [,5]
#> [1,] -0.6689740 -0.4792658 -0.65377553 -0.76029435 -1.27104438
#> [2,] -0.6407446 -0.5294750 -0.29562221 0.45823845 2.36731663
#> [3,] -1.6717486 0.5246035 -0.64552843 1.50054729 -0.59296727
#> [4,] 0.6042713 1.3331884 -0.15065175 0.41379520 0.15209942
#> [5,] -2.0414681 -0.2745396 0.73334771 1.06564065 0.44743923
#> [6,] 0.6979305 1.3629202 0.85166789 1.17955222 2.18240239
#> [7,] -0.5530186 0.2788471 1.13060052 -1.38361139 -0.57900056
#> [8,] 0.9768841 0.8698298 -0.70392194 1.26538840 -0.72843084
#> [9,] -1.2326939 -1.0729836 0.09595354 0.06376201 0.06014036
#> [10,] -0.6518550 1.3418630 -0.97091892 1.19473729 0.81623075
#> [1] 1 2 2 6 8
#> [1] 1 5 4 2 3
#> [1] 1.0 4.0 5.0 2.5 2.5
#> [1] 1 4 5 2 3
#> [1] "a1" "a10" "a2"
#> [1] "a1" "a2" "a10"
#> [1] 4
#> [1] 6
#> [1] 1 NA 2 3
#>
#> 1 2
#> 3 2
#> B
#> A 1 2
#> 1 2 1
#> 2 1 1
#> [1] 4 5 1 8 3 9 7 10 6 2
#> [1] 8 6 4 10 7
#> [1] 3 8 8 4 10 10 8 8 4 7 10 10 8 5 6 5 3 5 4 3 9 6 4 9 4 4 7 9
#> [29] 4 7 7 9 8 9 5 5 8 9 4 8 9 6 5 5 7 10 3 5 6 10
#> [1] 88.0264446 0.3174689 7.9633695 83.7439313 78.1579941 10.2566550 50.1465144
#> [8] 30.8318450 62.8808896 29.2527095
#> [1] 88 0 8 84 78 10 50 31 63 29
#> [1] 88.03 0.32 7.96 83.74 78.16 10.26 50.15 30.83 62.88 29.25
#> [1] 90 0 10 80 80 10 50 30 60 30
#> [1] 1 2 2 1
#> [1] 4 3 3 4
#> [,1] [,2] [,3]
#> [1,] 2 3 4
#> [2,] 3 4 5
#> [3,] 4 5 6
#> [4,] 5 6 7
#> param1 param2
#> 1 5 1
#> 2 50 1
#> 3 5 3
#> 4 50 3
#> 5 5 10
#> 6 50 10
Also see this nice Q/A on grouping functions and the *apply family and this book chapter about looping.
3.4.3 Character operations
#> [1] "I am me"
#> [1] "test0"
#> [1] "PC1" "PC2" "PC3" "PC4" "PC5" "PC6" "PC7" "PC8" "PC9" "PC10"
#> I am Florian
#> [1] "./good-practices.Rmd" "./index.Rmd" "./intro.Rmd"
#> [4] "./packages.Rmd" "./performance.Rmd" "./presentation_project.Rmd"
#> [7] "./rprog.Rmd" "./shiny.Rmd" "./tidyverse.Rmd"
#> [1] "./good-practices.pdf" "./index.pdf" "./intro.pdf"
#> [4] "./packages.pdf" "./performance.pdf" "./presentation_project.pdf"
#> [7] "./rprog.pdf" "./shiny.pdf" "./tidyverse.pdf"
#> [1] "c" "b" "b" "c" "d" "b" "d" "b" "d" "b"
#> [1] 3 2 2 3 4 2 4 2 4 2
#> [1] FALSE TRUE TRUE FALSE FALSE TRUE FALSE TRUE FALSE TRUE
#> $a
#> [1] 1 4 7 10
#>
#> $b
#> [1] 2 5 8 11
#>
#> $c
#> [1] 3 6 9 12
#> [1] "c" "d"
#> [1] "a" "b" "c" "d" "e"
#> [1] "a" "b"
3.4.4 Logical operators
#> Error: will go there
#> [1] TRUE
#> [1] FALSE FALSE TRUE FALSE
#> Warning in c(TRUE, FALSE, TRUE, TRUE) && c(FALSE, TRUE, TRUE, FALSE): 'length(x) = 4 > 1'
#> in coercion to 'logical(1)'
#> Warning in c(TRUE, FALSE, TRUE, TRUE) && c(FALSE, TRUE, TRUE, FALSE): 'length(x) = 4 > 1'
#> in coercion to 'logical(1)'
#> [1] FALSE
#> [1] 0.5207624 -0.8074154 0.5964654 1.0513697 0.8341501 0.6020957 0.7028814
#> [8] -0.6246347 -1.4445850 1.0026974
#> [1] 0.5207624 0.8074154 0.5964654 1.0513697 0.8341501 0.6020957 0.7028814 0.6246347
#> [9] 1.4445850 1.0026974
Beware with ifelse() (learn more there), for example
#> [1] 1
#> [1] 1 2 3 4 5
#> [1] 1 2 3 4 5
3.4.5 Exercises
Use
sample(),rep_len()andsplit()to make a function that randomly splits some indices in a list ofKgroups of indices (like for cross-validation). [Which are the special cases that you should consider?]#> $`1` #> [1] 2 4 5 6 7 12 13 23 26 29 31 35 36 37 #> #> $`2` #> [1] 8 10 14 17 18 19 24 28 30 33 34 38 40 #> #> $`3` #> [1] 1 3 9 11 15 16 20 21 22 25 27 32 39Use
replicate()andsample()to get a 95% confidence interval (using bootstrapping) for the mean of the following vector:#> [1] -0.6264538 0.1836433 -0.8356286 1.5952808 0.3295078 -0.8204684 0.4874291 #> [8] 0.7383247 0.5757814 -0.3053884#> [1] 0.1322028Expected output (approximately):
#> 2.5% 97.5% #> -0.3145143 0.5998608In the following data frame
df(recall that a data frame is also a list), for the first 3 columns, replace letters by corresponding numbers based on thecode:df <- data.frame( id1 = c("a", "f", "a"), id2 = c("b", "e", "e"), id3 = c("c", "d", "f"), inter = c(7.343, 2.454, 3.234), stringsAsFactors = FALSE ) df#> id1 id2 id3 inter #> 1 a b c 7.343 #> 2 f e d 2.454 #> 3 a e f 3.234#> a b c d e f #> 1 2 3 4 5 6Expected result:
#> id1 id2 id3 inter #> 1 1 2 3 7.343 #> 2 6 5 4 2.454 #> 3 1 5 6 3.234
3.5 Environments and scoping
Lexical scoping determines where to look for values, not when to look for them. R looks for values when the function is run, not when it’s created. This means that the output of a function can be different depending on objects outside its environment:
#> [1] 11
Variable x is not defined inside f so R will look at the environment of f (where f was defined) and then at the parent environment, and so on. Here, the first x that is found has value 10.
Be aware that for functions, packages environments are checked last so that you can redefine functions without noticing.
#> [1] "123"
#> [1] 1 2 3
#> [1] 1 2 3
You can use the <<- operator to change the value of an object in an upper environment:
count1 <- 0
count2 <- 0
f <- function(i) {
count1 <- count1 + 1 ## will assign a new (temporary) count1 each time
count2 <<- count2 + 1 ## will increment count2 on top
i
}
sapply(1:10, f)#> [1] 1 2 3 4 5 6 7 8 9 10
#> [1] 0 10
Finally, how does ... work? Basically, you copy and paste what is put in ...:
#> $a
#> [1] 2
#>
#> $b
#> [1] 3
#> $a
#> [1] 2
#>
#> $b
#> [1] 3
Learn more about functions and scoping rules of R with the R Programming for Data Science book.
3.6 Attributes and classes
Attributes are metadata associated with an object. You can get/set the list of attributes with attributes() or one particular attribute with attr().
#> $names
#> [1] "Sepal.Length" "Sepal.Width" "Petal.Length" "Petal.Width" "Species"
#>
#> $class
#> [1] "data.frame"
#>
#> $row.names
#> [1] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
#> [22] 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
#> [43] 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
#> [64] 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
#> [85] 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
#> [106] 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
#> [127] 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
#> [148] 148 149 150
#> [1] "data.frame"
#> [1] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
#> [22] 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
#> [43] 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
#> [64] 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
#> [85] 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
#> [106] 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
#> [127] 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
#> [148] 148 149 150
You can use structure() to create an object and add some arbitrary attributes.
#> [1] 1 2 3 4 5 6 7 8 9 10
#> attr(,"my_fancy_attribute")
#> [1] "blabla"
There are also some attributes with specific accessor functions to get and set values. For example, use names(x), dim(x) and class(x) instead of attr(x, "names"), attr(x, "dim") and attr(x, "class").
#> [1] "lm"
I’ve just fitted a linear model in order to predict the sepal length variable of the iris dataset based on the other variables. Using lm() gets me an object of class lm. What are the methods I can use for this object?
#> [1] summary.aov summary.aovlist*
#> [3] summary.aspell* summary.check_packages_in_dir*
#> [5] summary.connection summary.data.frame
#> [7] summary.Date summary.default
#> [9] summary.ecdf* summary.factor
#> [11] summary.glm summary.infl*
#> [13] summary.lm summary.loess*
#> [15] summary.manova summary.matrix
#> [17] summary.mlm* summary.nls*
#> [19] summary.packageStatus* summary.POSIXct
#> [21] summary.POSIXlt summary.ppr*
#> [23] summary.prcomp* summary.princomp*
#> [25] summary.proc_time summary.rlang:::list_of_conditions*
#> [27] summary.rlang_error* summary.rlang_message*
#> [29] summary.rlang_trace* summary.rlang_warning*
#> [31] summary.srcfile summary.srcref
#> [33] summary.stepfun summary.stl*
#> [35] summary.table summary.tukeysmooth*
#> [37] summary.vctrs_sclr* summary.vctrs_vctr*
#> [39] summary.warnings
#> see '?methods' for accessing help and source code
#> [1] add1 alias anova case.names coerce
#> [6] confint cooks.distance deviance dfbeta dfbetas
#> [11] drop1 dummy.coef effects extractAIC family
#> [16] formula hatvalues influence initialize kappa
#> [21] labels logLik model.frame model.matrix nobs
#> [26] plot predict print proj qr
#> [31] residuals rstandard rstudent show simulate
#> [36] slotsFromS3 summary variable.names vcov
#> see '?methods' for accessing help and source code
#>
#> Call:
#> lm(formula = Sepal.Length ~ ., data = iris)
#>
#> Residuals:
#> Min 1Q Median 3Q Max
#> -0.79424 -0.21874 0.00899 0.20255 0.73103
#>
#> Coefficients:
#> Estimate Std. Error t value Pr(>|t|)
#> (Intercept) 2.17127 0.27979 7.760 1.43e-12 ***
#> Sepal.Width 0.49589 0.08607 5.761 4.87e-08 ***
#> Petal.Length 0.82924 0.06853 12.101 < 2e-16 ***
#> Petal.Width -0.31516 0.15120 -2.084 0.03889 *
#> Speciesversicolor -0.72356 0.24017 -3.013 0.00306 **
#> Speciesvirginica -1.02350 0.33373 -3.067 0.00258 **
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> Residual standard error: 0.3068 on 144 degrees of freedom
#> Multiple R-squared: 0.8673, Adjusted R-squared: 0.8627
#> F-statistic: 188.3 on 5 and 144 DF, p-value: < 2.2e-16
R has the easiest way to create a class and to use methods on objects of this class; it is called S3. If you want to know more about the other types of classes, see the Advanced R book.
#> $first
#> [1] "James"
#>
#> $last
#> [1] "Bond"
class(agent007) <- "Person" ## "agent007" is now an object of class "Person"
# Just make a function called <method_name>.<class_name>()
print.Person <- function(x) {
print(glue::glue("My name is {x$last}, {x$first} {x$last}."))
invisible(x)
}
agent007#> My name is Bond, James Bond.
# Constructor of class as simple function
Person <- function(first, last) {
structure(list(first = first, last = last), class = "Person")
}
(me <- Person("Florian", "Privé"))#> My name is Privé, Florian Privé.
An object can have many classes:
Worker <- function(first, last, job) {
obj <- Person(first, last)
obj$job <- job
class(obj) <- c("Worker", class(obj))
obj
}
print.Worker <- function(x) {
print.Person(x)
print(glue::glue("I am a {x$job}."))
invisible(x)
}
(worker_007 <- Worker("James", "Bond", "secret agent"))#> My name is Bond, James Bond.
#> I am a secret agent.
#> My name is Privé, Florian Privé.
#> I am a researcher.