-
Notifications
You must be signed in to change notification settings - Fork 118
/
13_S3.Rmd
executable file
·738 lines (530 loc) · 24 KB
/
13_S3.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
```{r, include = FALSE}
source("common.R")
```
# (PART) Object-oriented programming {-}
\stepcounter{chapter}
# S3
<!-- 13 -->
## Prerequisites {-}
<!-- 13.0 -->
To interact with S3 objects, we will mainly use the `{sloop}` package [@sloop].
```{r setup, message = FALSE}
library(sloop)
```
\stepcounter{section}
## Basics
<!-- 13.2 -->
__[Q1]{.Q}__: Describe the difference between `t.test()` and `t.data.frame()`? When is each function called?
__[A]{.solved}__: Because of S3's `generic.class()` naming scheme, both functions may initially look similar, while they are in fact unrelated.
- `t.test()` is a *generic* function that performs a t-test.
- `t.data.frame()` is a *method* that gets called by the generic `t()` to transpose data frame input.
Due to R's S3 dispatch rules, `t.test()` would also get called when `t()` is applied to an object of class `test`.
__[Q2]{.Q}__: Make a list of commonly used base R functions that contain `.` in their name but are not S3 methods.
__[A]{.solved}__: In recent years "snake_case"-style has become increasingly common when naming functions and variables in R. But many functions in base R will continue to be "point.separated", which is why some inconsistency in your R code most likely cannot be avoided.[@RJ-2012-018]
```{r, eval = FALSE}
# Some base R functions with point.separated names
install.packages()
read.csv()
list.files()
download.file()
data.frame()
as.character()
Sys.Date()
all.equal()
do.call()
on.exit()
```
__[Q3]{.Q}__: What does the `as.data.frame.data.frame()` method do? Why is it confusing? How could you avoid this confusion in your own code?
__[A]{.solved}__: The function `as.data.frame.data.frame()` implements the `data.frame()` *method* for the `as.data.frame()` *generic*, which coerces objects to data frames.
The name is confusing, because it does not clearly communicate the type of the function, which could be a regular function, a generic or a method. Even if we assume a method, the amount of `.`'s makes it difficult to separate the generic- and the class-part of the name. Is it the `data.frame.data.frame()` method for the `as()` generic? Is it the `frame.data.frame()` method for the `as.data()` generic?
We could avoid this confusion by applying a different naming convention (e.g. "snake_case") for our class and function names.
__[Q4]{.Q}__: Describe the difference in behaviour in these two calls.
```{r}
some_days <- as.Date("2017-01-31") + sample(10, 5)
mean(some_days)
mean(unclass(some_days))
```
__[A]{.solved}__: `mean()` is a generic function, which will select the appropriate method based on the class of the input. `some_days` has the class `Date` and `mean.Date(some_days)` will be used to calculate the mean date of `some_days`.
After `unclass()` has removed the class attribute from `some_date`, the default method is chosen. `mean.default(unclass(some_days))` then calculates the mean of the underlying double.
__[Q5]{.Q}__: What class of object does the following code return? What base type is it built on? What attributes does it use?
```{r}
x <- ecdf(rpois(100, 10))
x
```
__[A]{.solved}__: It returns an object of the class `ecdf` (empirical cumulative distribution function) with the superclasses `stepfun` and `function`. The `ecdf` object is built on the base type `closure` (a function). The expression, which was used to create it (`rpois(100, 10)`), is stored in the `call` attribute.
```{r}
typeof(x)
attributes(x)
```
__[Q6]{.Q}__: What class of object does the following code return? What base type is it built on? What attributes does it use?
```{r}
x <- table(rpois(100, 5))
x
```
__[A]{.solved}__: This code returns a `table` object, which is built upon the `integer` type. The attribute `dimnames` is used to name the elements of the integer vector.
```{r}
typeof(x)
attributes(x)
```
## Classes
<!-- 13.3 -->
__[Q1]{.Q}__: Write a constructor for `data.frame` objects. What base type is a data frame built on? What attributes does it use? What are the restrictions placed on the individual elements? What about the names?
__[A]{.solved}__: Data frames are built on named lists of vectors, which all have the same length. Besides the `class` and the column names (`names`), the `row.names` are their only further attribute. This must be a character vector with the same length as the other vectors.
We need to provide the number of rows as an input to make it possible to create data frames with 0 columns but multiple rows.
This leads to the following constructor:
```{r, error = TRUE}
new_data.frame <- function(x, n, row.names = NULL) {
# Check if the underlying object is a list
stopifnot(is.list(x))
# Check all inputs are the same length
# (This check also allows that x has length 0)
stopifnot(all(lengths(x) == n))
if (is.null(row.names)) {
# Use special row names helper from base R
row.names <- .set_row_names(n)
} else {
# Otherwise check that they're a character vector with the
# correct length
stopifnot(is.character(row.names), length(row.names) == n)
}
structure(
x,
class = "data.frame",
row.names = row.names
)
}
# Test
x <- list(a = 1, b = 2)
new_data.frame(x, n = 1)
new_data.frame(x, n = 1, row.names = "l1")
# Create a data frame with 0 columns and 2 rows
new_data.frame(list(), n = 2)
```
There are two additional restrictions we could implement if we were being very strict: both the row names and column names should be unique.
__[Q2]{.Q}__: Enhance my `factor()` helper to have better behaviour when one or more `values` is not found in `levels`. What does `base::factor()` do in this situation?
__[A]{.solved}__: `base::factor()` converts these values (silently) into `NA`s:
```{r}
factor(c("a", "b", "c"), levels = c("a", "b"))
```
The `factor()` helper including the constructor (`new_factor()`) and its validator (`validate_factor()`) were given in *Advanced R*. However, as the goal of this question is to throw an early error within the helper, we only repeat the code for the helper:
```{r, eval = FALSE}
# Simplified version of the factor() helper, as defined in Advanced R
factor <- function(x = character(), levels = unique(x)) {
ind <- match(x, levels)
validate_factor(new_factor(ind, levels))
}
```
To improve the `factor()` helper we choose to return an informative error message instead.
```{r, error = TRUE}
factor2 <- function(x, levels = unique(x)) {
new_levels <- match(x, levels)
# Error if levels don't include all values
missing <- unique(setdiff(x, levels))
if (length(missing) > 0) {
stop(
"The following values do not occur in the levels of x: ",
paste0("'", missing, "'", collapse = ", "), ".",
call. = FALSE
)
}
validate_factor(new_factor(new_levels, levels))
}
# Test
factor2(c("a", "b", "c"), levels = c("a", "b"))
```
__[Q3]{.Q}__: Carefully read the source code of `factor()`. What does it do that our constructor does not?
__[A]{.solved}__: The original implementation (`base::factor()`) allows more flexible input for `x`. It coerces `x` to character or replaces it with `character(0)` (in case of `NULL`). It also ensures that the `levels` are unique. This is achieved by setting them via `base::levels<-`, which fails when duplicate values are supplied.
__[Q4]{.Q}__: Factors have an optional “contrasts” attribute. Read the help for `C()`, and briefly describe the purpose of the attribute. What type should it have? Rewrite the `new_factor()` constructor to include this attribute.
__[A]{.solved}__: When factor variables (representing nominal or ordinal information) are used in statistical models, they are typically encoded as dummy variables and by default each level is compared with the first factor level. However, many different encodings ("contrasts") are possible, see <https://en.wikipedia.org/wiki/Contrast_(statistics)>.
Within R's formula interface you can wrap a factor in `stats::C()` and specify the contrast of your choice. Alternatively, you can set the `contrasts` attribute of your factor variable, which accepts matrix input. (See `?contr.helmert` or similar for details.)
The `new_factor()` constructor was given in *Advanced R* as:
```{r}
# new_factor() constructor from Advanced R
new_factor <- function(x = integer(), levels = character()) {
stopifnot(is.integer(x))
stopifnot(is.character(levels))
structure(
x,
levels = levels,
class = "factor"
)
}
```
Our updated `new_factor()` constructor gets a `contrasts` argument, which accepts a numeric matrix or `NULL` (default).
```{r}
# Updated new_factor() constructor
new_factor <- function(
x = integer(),
levels = character(),
contrasts = NULL
) {
stopifnot(is.integer(x))
stopifnot(is.character(levels))
if (!is.null(constrasts)) {
stopifnot(is.matrix(contrasts) && is.numeric(contrasts))
}
structure(
x,
levels = levels,
class = "factor",
contrasts = contrasts
)
}
```
__[Q5]{.Q}__: Read the documentation for `utils::as.roman()`. How would you write a constructor for this class? Does it need a validator? What might a helper do?
__[A]{.solved}__: This function transforms numeric input into Roman numbers. It is built on the integer type, which results in the following constructor.
```{r}
new_roman <- function(x = integer()) {
stopifnot(is.integer(x))
structure(x, class = "roman")
}
```
The documentation tells us, that only values between 1 and 3899 are uniquely represented, which we then include in our validation function.
```{r}
validate_roman <- function(x) {
values <- unclass(x)
if (any(values < 1 | values > 3899)) {
stop(
"Roman numbers must fall between 1 and 3899.",
call. = FALSE
)
}
x
}
```
For convenience, we allow the user to also pass real values to a helper function.
```{r, error = TRUE}
roman <- function(x = integer()) {
x <- as.integer(x)
validate_roman(new_roman(x))
}
# Test
roman(c(1, 753, 2019))
roman(0)
```
## Generics and methods
<!-- 13.4 -->
__[Q1]{.Q}__: Read the source code for `t()` and `t.test()` and confirm that `t.test()` is an S3 generic and not an S3 method. What happens if you create an object with class `test` and call `t()` with it? Why?
```{r, eval = FALSE}
x <- structure(1:10, class = "test")
t(x)
```
__[A]{.solved}__: We can see that `t.test()` is a generic because it calls `UseMethod()`:
```{r}
t.test
# or simply call
ftype(t.test)
# The same holds for t()
t
```
Interestingly, R also provides helpers, which list functions that look like methods, but in fact are not:
```{r}
tools::nonS3methods("stats")
```
When we create an object with class `test`, `t()` dispatches to the `t.default()` method. This happens, because `UseMethod()` simply searches for functions named `paste0("generic", ".", c(class(x), "default"))`.
```{r, error = TRUE}
x <- structure(1:10, class = "test")
t(x)
```
However, in older versions of R (pre R 4.0.0; when *Advanced R* was written) this behaviour was slightly different. Instead of dispatching to the `t.default()` method, the `t.test()` generic was erroneously treated as a method of `t()` which then dispatched to `t.test.default()` or (when defined) to `t.test.test()`.
```{r, eval = FALSE}
# Output in R version 3.6.2
x <- structure(1:10, class = "test")
t(x)
#>
#> One Sample t-test
#>
#> data: x
#> t = 5.7446, df = 9, p-value = 0.0002782
#> alternative hypothesis: true mean is not equal to 0
#> 95 percent confidence interval:
#> 3.334149 7.665851
#> sample estimates:
#> mean of x
#> 5.5
t.test.test <- function(x) "Hi!"
t(x)
#>[1] "Hi!"
```
__[Q2]{.Q}__: What generics does the `table` class have methods for?
__[A]{.solved}__: This is a simple application of `sloop::s3_methods_class()`:
```{r}
s3_methods_class("table")
```
Interestingly, the `table` class has a number of methods designed to help plotting with base graphics.
```{r}
x <- rpois(100, 5)
plot(table(x))
```
__[Q3]{.Q}__: What generics does the `ecdf` class have methods for?
__[A]{.solved}__: We use the same approach as above:
```{r}
s3_methods_class("ecdf")
```
The methods are primarily designed for display (`plot()`, `print()`, `summary()`), but you can also extract quantiles with `quantile()`.
__[Q4]{.Q}__: Which base generic has the greatest number of defined methods?
__[A]{.solved}__: A little experimentation (and thinking about the most popular functions) suggests that the `print()` generic has the most defined methods.
```{r}
nrow(s3_methods_generic("print"))
nrow(s3_methods_generic("summary"))
nrow(s3_methods_generic("plot"))
```
Let's verify this programmatically with the tools we have learned in this and the previous chapters.
```{r, message = FALSE}
library(purrr)
ls(all.names = TRUE, env = baseenv()) %>%
mget(envir = baseenv()) %>%
keep(is_function) %>%
names() %>%
keep(is_s3_generic) %>%
map(~ set_names(nrow(s3_methods_generic(.x)), .x)) %>%
flatten_int() %>%
sort(decreasing = TRUE) %>%
head()
```
__[Q5]{.Q}__: Carefully read the documentation for `UseMethod()` and explain why the following code returns the results that it does. What two usual rules of function evaluation does `UseMethod()` violate?
```{r}
g <- function(x) {
x <- 10
y <- 10
UseMethod("g")
}
g.default <- function(x) c(x = x, y = y)
x <- 1
y <- 1
g(x)
```
__[A]{.solved}__: Let's take this step by step. If you call `g.default(x)` directly you get `c(1, 1)` as you might expect.
The value bound to `x` comes from the argument, the value from `y` comes from the global environment.
```{r}
g.default(x)
```
But when we call `g(x)` we get `c(1, 10)`:
```{r}
g(x)
```
This is seemingly inconsistent: why does `x` come from the value defined inside of `g()`, and `y` still come from the global environment? It's because `UseMethod()` calls `g.default()` in a special way so that variables defined inside the generic are available to methods. The exception are arguments supplied to the function: they are passed on as is and cannot be affected by code inside the generic.
__[Q6]{.Q}__: What are the arguments to `[`? Why is this a hard question to answer?
__[A]{.solved}__: The subsetting operator `[` is a primitive and a generic function, which can be confirmed via `ftype()`.
```{r}
ftype(`[`)
```
For primitive functions `formals([)` returns `NULL` so we need to find another way to determine the functions arguments. One possible way to figure out `[`'s arguments would be to inspect the underlying C source code, which can be searched for via `pryr::show_c_source(.Primitive("["))`.
When we inspect the arguments of some of `[`'s methods, we see that the arguments vary with the class of `x`.
```{r}
names(formals(`[.data.frame`))
names(formals(`[.table`))
names(formals(`[.Date`))
names(formals(`[.AsIs`))
```
To finally get a better overview, we have to put in a little more effort and also use `s3_methods_generic()` again.
```{r, message = FALSE, warning = FALSE}
library(dplyr)
s3_methods_generic("[") %>%
filter(visible) %>%
mutate(
method = paste0("[.", class),
argnames = purrr::map(method, ~ names(formals(.x))),
args = purrr::map(method, ~ formals(.x)),
args = purrr::map2(
argnames, args,
~ paste(.x, .y, sep = " = ")
),
args = purrr::set_names(args, method)
) %>%
pull(args) %>%
head()
```
## Object styles
<!-- 13.5 -->
__[Q1]{.Q}__: Categorise the objects returned by `lm()`, `factor()`, `table()`, `as.Date()`, `as.POSIXct()`, `ecdf()`, `ordered()`, `I()` into the styles described above.
__[A]{.solved}__: We can categorise the return values into the various object styles by observing how the [number of observations](https://vctrs.r-lib.org/articles/type-size.html#size) is calculated: For vector style classes, `length(x)` represents the number of observations. Record style objects use a list of equal length elements to represent individual components. For data frames and matrices, the observations are represented by the rows. Scalar style objects use a list to represent a single thing.
This leads us to:
- Vector object-style: `factor()`, `table()`, `as.Date()`, `as.POSIXct()`, `ordered()`
- Record object-style: not observed
- Data frame object-style: not observed
- Scalar object-style: `lm()`, `ecdf()`
The object style of `I()` depends on the input since this function returns a "copy of the object with class `AsIs` prepended to the class(es)".
__[Q2]{.Q}__: What would a constructor function for `lm` objects, `new_lm()`, look like? Use `?lm` and experimentation to figure out the required fields and their types.
__[A]{.solved}__: The constructor needs to populate the attributes of an `lm` object and check their types for correctness. Let's start by creating a simple `lm` object and explore its underlying base type and attributes:
```{r}
mod <- lm(cyl ~ ., data = mtcars)
typeof(mod)
attributes(mod)
```
As `mod` is built upon a list, we can simply use `map(mod, typeof)` to find out the base types of its elements. (Additionally, we inspect `?lm`, to learn more about the individual attributes.)
```{r}
map_chr(mod, typeof)
```
Now we should have enough information to write a constructor for new `lm` objects.
```{r}
new_lm <- function(
coefficients, residuals, effects, rank, fitted.values, assign,
qr, df.residual, xlevels, call, terms, model
) {
stopifnot(
is.double(coefficients), is.double(residuals),
is.double(effects), is.integer(rank), is.double(fitted.values),
is.integer(assign), is.list(qr), is.integer(df.residual),
is.list(xlevels), is.language(call), is.language(terms),
is.list(model)
)
structure(
list(
coefficients = coefficients,
residuals = residuals,
effects = effects,
rank = rank,
fitted.values = fitted.values,
assign = assign,
qr = qr,
df.residual = df.residual,
xlevels = xlevels,
call = call,
terms = terms,
model = model
),
class = "lm"
)
}
```
## Inheritance
<!-- 13.6 -->
__[Q1]{.Q}__: How does `[.Date` support subclasses? How does it fail to support subclasses?
__[A]{.solved}__: `[.Date` calls `.Date` with the result of calling `[` on the parent class, along with `oldClass()`:
```{r}
`[.Date`
```
`.Date` is kind of like a constructor for date classes, although it doesn't check the input is the correct type:
```{r}
.Date
```
`oldClass()` is basically the same as `class()`, except that it doesn't return implicit classes, i.e. it's basically `attr(x, "class")` (looking at the C code that's exactly what it does, except that it also handles S4 objects).
As `oldClass()` is "basically" `class()`, we can rewrite `[.Date` to make the implementation more clear:
```{r}
`[.Date` <- function(x, ..., drop = TRUE) {
out <- NextMethod("[")
class(out) <- class(x)
out
}
```
So, `[.Date` ensures that the output has the same class as in the input. But what about other attributes that a subclass might possess? They get lost:
```{r}
x <- structure(1:4, test = "test", class = c("myDate", "Date"))
attributes(x[1])
```
__[Q2]{.Q}__: R has two classes for representing date time data, `POSIXct` and `POSIXlt`, which both inherit from `POSIXt`. Which generics have different behaviours for the two classes? Which generics share the same behaviour?
__[A]{.solved}__: To answer this question, we have to get the respective generics
```{r}
generics_t <- s3_methods_class("POSIXt")$generic
generics_ct <- s3_methods_class("POSIXct")$generic
generics_lt <- s3_methods_class("POSIXlt")$generic
```
The generics in `generics_t` with a method for the superclass `POSIXt` potentially share the same behaviour for both subclasses. However, if a generic has a specific method for one of the subclasses, it has to be subtracted:
```{r}
# These generics provide subclass-specific methods
union(generics_ct, generics_lt)
# These generics share (inherited) methods for both subclasses
setdiff(generics_t, union(generics_ct, generics_lt))
```
__[Q3]{.Q}__: What do you expect this code to return? What does it actually return? Why?
```{r, results = "hide"}
generic2 <- function(x) UseMethod("generic2")
generic2.a1 <- function(x) "a1"
generic2.a2 <- function(x) "a2"
generic2.b <- function(x) {
class(x) <- "a1"
NextMethod()
}
generic2(structure(list(), class = c("b", "a2")))
```
__[A]{.solved}__: When we execute the code above, this is what is happening:
- we pass an object of classes `b` and `a2` to `generic2()`, which prompts R to look for a method`generic2.b()`
- the method `generic2.b()` then changes the class to `a1` and calls `NextMethod()`
- One would think that this will lead R to call `generic2.a1()`, but in fact, as mentioned in *Advanced R*, `NextMethod()`
> doesn’t actually work with the class attribute of the object, but instead uses a special global variable (.Class) to keep track of which method to call next.
This is why `generic2.a2()` is called instead.
```{r}
generic2(structure(list(), class = c("b", "a2")))
```
Let's just double check the statement above and evaluate `.Class` explicitly within the `generic2.b()` method.
```{r}
generic2.b <- function(x) {
class(x) <- "a1"
print(.Class)
NextMethod()
}
generic2(structure(list(), class = c("b", "a2")))
```
## Dispatch details
<!-- 13.7 -->
__[Q1]{.Q}__: Explain the differences in dispatch below:
```{r}
length.integer <- function(x) 10
x1 <- 1:5
class(x1)
s3_dispatch(length(x1))
x2 <- structure(x1, class = "integer")
class(x2)
s3_dispatch(length(x2))
```
__[A]{.solved}__: `class()` returns `integer` in both cases. However, while the class of `x1` is created implicitly and inherits from the `numeric` class, the class of `x2` is set explicitly. This is important because `length()` is an internal generic and internal generics only dispatch to methods when the class attribute has been set, i.e. internal generics do not use implicit classes.
An object has no explicit class if `attr(x, "class")` returns `NULL`:
```{r}
attr(x1, "class")
attr(x2, "class")
```
To see the relevant classes for the S3 dispatch, one can use `sloop::s3_class()`:
```{r}
s3_class(x1) # implicit
s3_class(x2) # explicit
```
For a better understanding of `s3_dipatch()`'s output we quote from `?s3_dispatch`:
- => method exists and is found by UseMethod().
- -> method exists and is used by NextMethod().
- \* method exists but is not used.
- Nothing (and greyed out in console): method does not exist.
__[Q2]{.Q}__: What classes have a method for the `Math()` group generic in base R? Read the source code. How do the methods work?
__[A]{.solved}__: The following functions belong to this group (see ?`Math`):
- `abs`, `sign`, `sqrt`, `floor`, `ceiling`, `trunc`, `round`, `signif`
- `exp`, `log`, `expm1`, `log1p`, `cos`, `sin`, `tan`, `cospi`, `sinpi`, `tanpi`, `acos`, `asin`, `atan`, `cosh`, `sinh`, `tanh`, `acosh`, `asinh`, `atanh`
- `lgamma`, `gamma`, `digamma`, `trigamma`
- `cumsum`, `cumprod`, `cummax`, `cummin`
The following classes have a method for this group generic:
```{r}
s3_methods_generic("Math")
```
To explain the basic idea, we just overwrite the data frame method:
```{r}
Math.data.frame <- function(x) "hello"
```
Now all functions from the math generic group, will return `"hello"`
```{r}
abs(mtcars)
exp(mtcars)
lgamma(mtcars)
```
Of course, different functions should perform different calculations. Here `.Generic` comes into play, which provides us with the calling generic as a string
```{r}
Math.data.frame <- function(x, ...) {
.Generic
}
abs(mtcars)
exp(mtcars)
lgamma(mtcars)
rm(Math.data.frame)
```
The original source code of `Math.data.frame()` is a good example on how to invoke the string returned by `.Generic` into a specific method. `Math.factor()` is a good example of a method, which is simply defined for better error messages.
__[Q3]{.Q}__: `Math.difftime()` is more complicated than I described. Why?
__[A]{.solved}__: `Math.difftime()` also excludes cases apart from `abs`, `sign`, `floor`, `ceiling`, `trunc`, `round` and `signif` and needs to return a fitting error message.
For comparison: `Math.difftime()` as defined in *Advanced R*:
```{r}
Math.difftime <- function(x, ...) {
new_difftime(NextMethod(), units = attr(x, "units"))
}
rm(Math.difftime)
```
`Math.difftime()` as defined in the `{base}` package:
```{r}
Math.difftime
```