library(sloop)S3
In this lab, we will learn how to write methods for S3 generics.
Goal: by the end of this lab, you should be able to write your own S3 generic methods and functions.
Finding out about S3 methods
First, we’ll load the sloop package to get some information about the S3 system.
Since we’ve already loaded the tidyverse, there are lots of S3 generic function and their methods already available to us.
For example, there are many methods for the print() generic available:
s3_methods_generic("print")# A tibble: 303 × 4
generic class visible source
<chr> <chr> <lgl> <chr>
1 print acf FALSE registered S3method
2 print activeConcordance FALSE registered S3method
3 print AES FALSE registered S3method
4 print all_vars FALSE registered S3method
5 print anova FALSE registered S3method
6 print any_vars FALSE registered S3method
7 print aov FALSE registered S3method
8 print aovlist FALSE registered S3method
9 print ar FALSE registered S3method
10 print Arima FALSE registered S3method
# ℹ 293 more rows
In fact, there are so many that we might as well restrict our attention to those relevant to tibbles and data.frames.
s3_methods_generic("print") |>
filter(str_detect(class, "tbl|data.frame"))# A tibble: 3 × 4
generic class visible source
<chr> <chr> <lgl> <chr>
1 print data.frame TRUE base
2 print pillar_tbl_format_setup FALSE registered S3method
3 print tbl FALSE registered S3method
Other common S3 generics include summary() and plot():
s3_methods_generic("summary")# A tibble: 44 × 4
generic class visible source
<chr> <chr> <lgl> <chr>
1 summary aov TRUE stats
2 summary aovlist FALSE registered S3method
3 summary aspell FALSE registered S3method
4 summary check_packages_in_dir FALSE registered S3method
5 summary connection TRUE base
6 summary data.frame TRUE base
7 summary Date TRUE base
8 summary default TRUE base
9 summary Duration FALSE registered S3method
10 summary ecdf FALSE registered S3method
# ℹ 34 more rows
s3_methods_generic("plot")# A tibble: 35 × 4
generic class visible source
<chr> <chr> <lgl> <chr>
1 plot acf FALSE registered S3method
2 plot data.frame FALSE registered S3method
3 plot decomposed.ts FALSE registered S3method
4 plot default TRUE graphics
5 plot dendrogram FALSE registered S3method
6 plot density FALSE registered S3method
7 plot ecdf TRUE stats
8 plot factor FALSE registered S3method
9 plot formula FALSE registered S3method
10 plot function TRUE graphics
# ℹ 25 more rows
- Try to think of another generic function that you’ve worked with in the past. Use
s3_methods_generic()to find its available methods. [Note that you might have to load other packages to find the methods or the generic that you recall.]
In addition to listing the methods for a particular generic, we can find all the methods that are defined for a particular class.
s3_methods_class("tbl_df")# A tibble: 21 × 4
generic class visible source
<chr> <chr> <lgl> <chr>
1 [ tbl_df FALSE registered S3method
2 [[ tbl_df FALSE registered S3method
3 [[<- tbl_df FALSE registered S3method
4 [<- tbl_df FALSE registered S3method
5 $ tbl_df FALSE registered S3method
6 $<- tbl_df FALSE registered S3method
7 arrange_ tbl_df FALSE registered S3method
8 as.data.frame tbl_df FALSE registered S3method
9 distinct_ tbl_df FALSE registered S3method
10 filter_ tbl_df FALSE registered S3method
# ℹ 11 more rows
Note that while the methods for a particular generic are most likely written by many different developers in many different packages, the methods for a particular class are most likely written by the same set of developers of a single package.
Method dispath
If you are ever unsure of which method is actually being run when you invoke a generic, use s3_dispatch() to figure out what is going on.
s3_dispatch(print(starwars)) print.tbl_df
=> print.tbl
* print.data.frame
* print.default
s3_dispatch(print(mtcars))=> print.data.frame
* print.default
Why do the previous two examples show a different list of available methods?
Why doesn’t
print.data.frame()get called onstarwars?
Defining a method
First, we’ll learn about two fun packages: emoji and crayon.
emoji
The emoji package allows you print emoji via the emoji() function.
install.packages("emoji")To display an emoji, call it by name:
library(emoji)
emoji("star")[1] "⭐"
emoji("dog")[1] "🐶"
emoji("basketball")[1] "🏀"
You can see the full list of available emojis in the tibble returned by emojis.
emojis |> View()- Find an emoji that you like and use
emoji()to print it to the console.
crayon
The crayon package allows you to print in color in the console. For a complicated example of what you can do, consider the tidyverse logo:
tidyverse_logo()⬢ __ _ __ . ⬡ ⬢ .
/ /_(_)__/ /_ ___ _____ _______ ___
/ __/ / _ / // / |/ / -_) __(_-</ -_)
\__/_/\_,_/\_, /|___/\__/_/ /___/\__/
⬢ . /___/ ⬡ . ⬢
For a simpler, example, you can write your name in blue. Be sure to wrap the text in cat() (or message()) to send it to the console.
cat(crayon::blue("Ben"))Ben
- Pick a color from the
crayonpackage and write something to the console in that color.
Defining a method
Now we will define a simple print() method that will only be invoked for objects of class beanumber, which is my GitHub username. This method will simply print a colorful message to the console, and then invoke the NextMethod() available.
print.beanumber <- function(x, ...) {
cat(
crayon::magenta(
"Ben",
emoji::emoji("heart"),
"s programming in R!\n"
)
)
NextMethod()
}To use it, we just have to create an object of type beanumber, and then call print(). Remember that in R you can simply modify the class attribute of any object!
class(starwars) <- c("beanumber", class(starwars))
starwarsBen ❤️ s programming in R!
# A tibble: 87 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Luke Sk… 172 77 blond fair blue 19 male mascu…
2 C-3PO 167 75 <NA> gold yellow 112 none mascu…
3 R2-D2 96 32 <NA> white, bl… red 33 none mascu…
4 Darth V… 202 136 none white yellow 41.9 male mascu…
5 Leia Or… 150 49 brown light brown 19 fema… femin…
6 Owen La… 178 120 brown, gr… light blue 52 male mascu…
7 Beru Wh… 165 75 brown light blue 47 fema… femin…
8 R5-D4 97 32 <NA> white, red red NA none mascu…
9 Biggs D… 183 84 black light brown 24 male mascu…
10 Obi-Wan… 182 77 auburn, w… fair blue-gray 57 male mascu…
# ℹ 77 more rows
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
Note that this method works on objects of any underlying class!!
class(Titanic)[1] "table"
class(Titanic) <- c("beanumber", class(Titanic))
TitanicBen ❤️ s programming in R!
, , Age = Child, Survived = No
Sex
Class Male Female
1st 0 0
2nd 0 0
3rd 35 17
Crew 0 0
, , Age = Adult, Survived = No
Sex
Class Male Female
1st 118 4
2nd 154 13
3rd 387 89
Crew 670 3
, , Age = Child, Survived = Yes
Sex
Class Male Female
1st 5 1
2nd 11 13
3rd 13 14
Crew 0 0
, , Age = Adult, Survived = Yes
Sex
Class Male Female
1st 57 140
2nd 14 80
3rd 75 76
Crew 192 20
- Define your own
print()method for objects having class [your GitHub username].
Defining a new generic
In the previous section, we defined a method for an existing generic function. Now, we will define a new generic function.
print_emoji <- function(x, ...) {
UseMethod("print_emoji")
}Since we haven’t defined any methods yet, the function won’t actually work!
print_emoji(starwars)Error in UseMethod("print_emoji"): no applicable method for 'print_emoji' applied to an object of class "c('beanumber', 'tbl_df', 'tbl', 'data.frame')"
However, we can start by writing a default method that will simply call the existing print() generic.
print_emoji.default <- function(x, ...) {
print(x, ...)
}Now our print_emoji() function will work.
print_emoji(starwars)Ben ❤️ s programming in R!
# A tibble: 87 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Luke Sk… 172 77 blond fair blue 19 male mascu…
2 C-3PO 167 75 <NA> gold yellow 112 none mascu…
3 R2-D2 96 32 <NA> white, bl… red 33 none mascu…
4 Darth V… 202 136 none white yellow 41.9 male mascu…
5 Leia Or… 150 49 brown light brown 19 fema… femin…
6 Owen La… 178 120 brown, gr… light blue 52 male mascu…
7 Beru Wh… 165 75 brown light blue 47 fema… femin…
8 R5-D4 97 32 <NA> white, red red NA none mascu…
9 Biggs D… 183 84 black light brown 24 male mascu…
10 Obi-Wan… 182 77 auburn, w… fair blue-gray 57 male mascu…
# ℹ 77 more rows
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
In the
defaultmethod forprint_emoji(), can we useNextMethod()instead ofprint()? Why or why not?Write a
print_emoji()method fordata.frames.
Engagement
Prompt: Paste the code from your
print_emoji()method to the#emojischannel. What does it do?