R: how to find what S3 method will be called on an object?
findMethod
defined below is not a one-liner but its body has only 4 lines of code (and if we required that the generic be passed as a character string it could be reduced to 3 lines of code). It will return a character string representing the name of the method that would be dispatched by the input generic given that generic and its arguments. (Replace the last line of the body of findMethod
with get(X(...))
if you want to return the method itself instead.) Internally it creates a generic X and an X method corresponding to each method of the input generic such that each X method returns the name of the method of the input generic that would be run. The X generic and its methods are all created within the findMethod
function so they disappear when findMethod
exits. To get the result we just run X with the input argument(s) as the final line of the findMethod
function body.
findMethod <- function(generic, ...) {
ch <- deparse(substitute(generic))
f <- X <- function(x, ...) UseMethod("X")
for(m in methods(ch)) assign(sub(ch, "X", m, fixed = TRUE), "body<-"(f, value = m))
X(...)
}
Now test it. (Note that the one-liner in the question fails with an error in several of these tests but findMethod
gives the expected result.)
findMethod(as.ts, iris)
## [1] "as.ts.default"
findMethod(print, iris)
## [1] "print.data.frame"
findMethod(print, Sys.time())
## [1] "print.POSIXct"
findMethod(print, 22)
## [1] "print.default"
# in this example it looks at 2nd component of class vector as no print.ordered exists
class(ordered(3))
## [1] "ordered" "factor"
findMethod(print, ordered(3))
## [1] "print.factor"
findMethod(`[`, BOD, 1:2, "Time")
## [1] "[.data.frame"
How to find out which method is dispatched to an S3 object?
The sloop
package can show you more details. For example
sloop::s3_dispatch(print(df))
# => print.data.frame
# * print.default
this shows you all the possible matches, and highlights the one that was actually used (print.data.frame
)
But in general you need to look at the class()
of the object you pass to the function. The first class that matches up with the methods()
listed for the function will be the one called. If you want to see the code of the function that would be used, you could use
getS3method("print", "data.frame")
What does S3 methods mean in R?
Most of the relevant information can be found by looking at ?S3
or ?UseMethod
, but in a nutshell:
S3 refers to a scheme of method dispatching. If you've used R for a while, you'll notice that there are print
, predict
and summary
methods for a lot of different kinds of objects.
In S3, this works by:
- setting the class of objects of
interest (e.g.: the return value of a
call to methodglm
has classglm
) - providing a method with the general
name (e.g.print
), then a dot, and
then the classname (e.g.:print.glm
) - some preparation has to have been
done to this general name (print
)
for this to work, but if you're
simply looking to conform yourself to
existing method names, you don't need
this (see the help I refered to
earlier if you do).
To the eye of the beholder, and particularly, the user of your newly created funky model fitting package, it is much more convenient to be able to type predict(myfit, type="class")
than predict.mykindoffit(myfit, type="class")
.
There is quite a bit more to it, but this should get you started. There are quite a few disadvantages to this way of dispatching methods based upon an attribute (class) of objects (and C purists probably lie awake at night in horror of it), but for a lot of situations, it works decently. With the current version of R, newer ways have been implemented (S4 and reference classes), but most people still (only) use S3.
function to return all S3 methods applicable to an object
Here's an attempt to replicate the "standard" behavior
classMethods <- function(cl) {
if(!is.character(cl)) {
cl<-class(cl)
}
ml<-lapply(cl, function(x) {
sname <- gsub("([.[])", "\\\\\\1", paste0(".", x, "$"))
m <- methods(class=x)
data.frame(
m=as.vector(m),
c=x, n=sub(sname, "", as.vector(m)),
attr(m,"info"),
stringsAsFactors=F
)
})
df<-do.call(rbind, ml)
df<-df[!duplicated(df$n),]
structure(df$m,
info=data.frame(visible=df$visible, from=df$from),
class="MethodsFunction")
}
And then you can try it out with
g <- glm(y~x,data=data.frame(x=1:10,y=1:10))
classMethods(g)
#or classMethods(c("glm","lm"))
and that will return
[1] add1.glm* anova.glm confint.glm* cooks.distance.glm*
[5] deviance.glm* drop1.glm* effects.glm* extractAIC.glm*
[9] family.glm* formula.glm* influence.glm* logLik.glm*
[13] model.frame.glm nobs.glm* predict.glm print.glm
[17] residuals.glm rstandard.glm rstudent.glm summary.glm
[21] vcov.glm* weights.glm* alias.lm* case.names.lm*
[25] dfbeta.lm* dfbetas.lm* dummy.coef.lm* hatvalues.lm
[29] kappa.lm labels.lm* model.matrix.lm plot.lm
[33] proj.lm* qr.lm* simulate.lm* variable.names.lm*
Non-visible functions are asterisked
It's not as elegant or short as Josh's, but I think its a good recreation of the default behavior. It's funny to see that the methods
function is itself mostly just a grep across all known function names. I borrowed the gsub
stuff from there.
How does R find S3 methods? Why can't R find my S3 `+` method?
If you aren't registering an S3 method as part of a package namespace or in the global environment, you'll need to explicitly register it using the .S3method()
function. So in this case you would do
.S3method("+", "Foo", FooEnv$`+.Foo`)
This issue is further discussed here: https://developer.r-project.org/Blog/public/2019/08/19/s3-method-lookup/
Define a show method for an S3 class
Maybe
setOldClass("aClass")
setMethod(show, "aClass", function(object) cat("S4\n"))
print.aClass <- function(object) { cat("S3... "); show(object) }
and then
> structure(1:5, class="aClass")
S3... S4
But I'm not really understanding what you want to do.
type/origin of R's 'as' function
as
is not an S3 generic, but notice that you got a TRUE. (I got a FALSE.) That means you have loaded a package that definesas
as an S4-generic. S3-generics work via class dispatch that employs a *.default
function and the UseMethod
-function. The FALSE I get means there is no method defined for a generic as
that would get looked up. One arguable reason for the lack of a generic as
is that calling such a function with only one data object would not specify a "coercion destination". That means the destination needs to be built into the function name.
After declaring as
to be Generic (note the capitalization which is a hint that this applies to S4 features:
setGeneric("as") # note that I didn't really even need to define any methods
get('as')
#--- output----
standardGeneric for "as" defined from package "methods"
function (object, Class, strict = TRUE, ext = possibleExtends(thisClass,
Class))
standardGeneric("as")
<environment: 0x7fb1ba501740>
Methods may be defined for arguments: object, Class, strict, ext
Use showMethods("as") for currently available ones.
If I reboot R (and don't load any libraries that call setGeneric for 'as') I get:
get('as')
#--- output ---
function (object, Class, strict = TRUE, ext = possibleExtends(thisClass,
Class))
{
if (.identC(Class, "double"))
Class <- "numeric"
thisClass <- .class1(object)
if (.identC(thisClass, Class) || .identC(Class, "ANY"))
return(object)
where <- .classEnv(thisClass, mustFind = FALSE)
coerceFun <- getGeneric("coerce", where = where)
coerceMethods <- .getMethodsTable(coerceFun, environment(coerceFun),
inherited = TRUE)
asMethod <- .quickCoerceSelect(thisClass, Class, coerceFun,
coerceMethods, where)
.... trimmed the rest of the code
But you ask "why", always a dangerous question when discussing language design, of course. I've flipped through the last chapter of Statistical Models in S which is the cited reference for most of the help pages that apply to S3 dispatch and find no discussion of either coercion or the as
function. There is an implicit definition of "S3 generic" requiring the use of UseMethod
but no mention of why as
was left out of that strategy. I think of two possibilities: it is to prevent any sort of inheritance ambiguity in the application of the coercion, or it is an efficiency decision.
I should probably add that there is an S4 setAs
-function and that you can find all the S4-coercion functions with showMethods("coerce")
.
Related Topics
Create and Call Linear Models from List
How to Import Only One Function from Another Package, Without Loading the Entire Namespace
Replicate a List to Create a List-Of-Lists
Can You Pass a Vector to a Vararg: Vector to Sprintf
R - Scaling Numeric Values Only in a Dataframe with Mixed Types
Plot Multiple Datasets with Ggplot
Alignment of Numbers on the Individual Bars with Ggplot2
R: Replacing Foreign Characters in a String
Ggplot2': Label Values of Barplot That Uses 'Fun.Y="Mean"' of 'Stat_Summary'
R Output Without [1], How to Nicely Format
Removing Traces by Name Using Plotlyproxy (Or Accessing Output Schema in Reactive Context)
Solving a System of Nonlinear Equations in R
How to Install R-Packages Not in the Conda Repositories
Replace Nas with Mean of the Same Column of a Data.Table
How to Dynamically Change Plotly Axis Based on Crosstalk Conditions