making sense of methods in R
Some time back, @malco_barrett and I discovered we both had tidyverse::
-integrated functions in development for meta-analysis models, specifically models produced by metafor::rma
.
We started to discuss how to bring together the work we’d done, and then, speaking for myself, I got busy and overwhelmed last year with the general craziness that doing a doctorate is, and this slipped way back on the backburner.
Last week, however, one of the creators of the broom::
package got in touch with Malcolm about porting the code. And, with the Evidence Synthesis Hackathon imminent, it seems as if the time is nigh. If I’m going to be able to collaborate on this, that means finally getting around to learning about methods in R.
Cracks metaphoric R knuckles.
— Charles T. Gray (@cantabile) March 27, 2019
So, S3. What's that all about?
I think I might find this pedagogically interesting in future, to reflect on where I started today, and where I ended up.
Journey begins here: my current impression is that, digging back 8 years ago to my one compsci subject, first year java, S3 is a method for an object.
— Charles T. Gray (@cantabile) March 27, 2019
UseMethod
documentationRecently someone, I think it was @kiersi, was asking on twitter how people learn. Today I learnt that I like to play first, ask questions of the documentation after.
Mike Penguin said something about UseMethod
on twitter, so I thought I’d start there.
Once I realized that myfun <- function(x, …) UseMethod(“myfun”) is the boilerplate needed I was away. It does magic so that the methods you write i.e. myfun.myclass work like myfun(x) where class(x) includes“my class”
— Michael Sumner (@mdsumner) March 27, 2019
# i guess i need an object for my function to act on
fluffyduck <- "i am a duck"
# i wonder if you can define your own class?
class(fluffyduck) <- "duck"
class(fluffyduck)
[1] "duck"
# the UseMethod documentation said the method needs to act on an object
is.object(fluffyduck)
[1] TRUE
# make a function
firstquack <- function() {cat("quack")}
# see if it works
firstquack()
quack
# but this throws an error
UseMethod("firstquack", duck)
Error in eval(expr, envir, enclos): object 'duck' not found
# okay, time to look to the tubes for help
This! I wrote a post about it and used my PR to janitor as an example. https://t.co/Vn7H3BAQjm
— Josiah Parry (@JosiahParry) March 28, 2019
Josiah’s post was most helpful, indeed. These are my notes.
# start by creating a "generic" function
quack <- function(says_the_duck, greeting = "quack!") {
UseMethod("quack")
}
The generic function seems like an instantiation step. I read this as, I will create a function called this that I can control how it behaves for different classes.
quack() # okay, same error as josiah's, so far so good
Error in UseMethod("quack"): no applicable method for 'quack' applied to an object of class "NULL"
Sidenote, set chunk option error=TRUE
to display code and output for an error.
Makes sense that this throws an error, we haven’t told it how to behave for anything yet.
# set up a default method
quack.default <-
function(says_the_duck = "quack!", greeting = "quack! ") {
print(paste0(greeting, says_the_duck))
cat("said the duck")
}
# check this default works for anything
quack(NULL) # yay! it quacked on NULL
[1] "quack! "
said the duck
quack() # and no argument?
[1] "quack! quack!"
said the duck
quack("i'm a duck") # a string?
[1] "quack! i'm a duck"
said the duck
# now to make a fluffyduck-class object
fluffyduck <- "i am fluffy" # create an object
class(fluffyduck) <- "fluffyduck" # set class of object
# trying this before referring back to the post
quack.fluffyduck <- function(says_the_duck, greeting = "quack! ") {
print(paste0(greeting, says_the_duck))
cat("said the fluffy duck\n")
}
# does the duck quack?
quack(fluffyduck)
[1] "quack! i am fluffy"
said the fluffy duck
# try another object
another_fluffy_duck <- "i am the fluffiest!"
# test default
quack(another_fluffy_duck)
[1] "quack! i am the fluffiest!"
said the duck
# but this duck is also fluffy!
class(another_fluffy_duck) <- "fluffyduck"
quack(another_fluffy_duck)
[1] "quack! i am the fluffiest!"
said the fluffy duck
# now to test changing my default greeting, to understand how to parse other arguments
quack(another_fluffy_duck, greeting = "a most fluffy day to you, ")
[1] "a most fluffy day to you, i am the fluffiest!"
said the fluffy duck
I don’t understand what all the words mean in these definitions of S3 and S4.
4 works similarly to S3, but is more formal. There are two major differences to S3. S4 has formal class definitions, which describe the representation and inheritance for each class, and has special helper functions for defining generics and methods. S4 also has multiple dispatch, which means that generic functions can pick methods based on the class of any number of arguments, not just one. - Advanced R
Does multiple dispatch mean that the method can be conditional on the class of more than one argument?
This was fun; despite me finishing with more questions than I began with.
Much obliged to @jacinta for suggesting I update; the internet’s highest honour, gif-phrased praise.
Fluffy duck! :) pic.twitter.com/9HUPVaXAL4
— Dr Jenny Richmond (@JenRichmondPhD) March 28, 2019
# a duck for maelle!
maelles_duck <- "je suis une cane"
class(maelles_duck) <- "frenchduck"
# french ducks speak in french
quack.frenchduck <- function(says_the_duck, greeting = "coin! ") {
print(paste0(greeting, says_the_duck))
cat("dit la cane\n")
}
# coin?
quack(maelles_duck)
[1] "coin! je suis une cane"
dit la cane