R: How to Find What S3 Method Will Be Called on an Object

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 method glm has class glm)
  • 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



Leave a reply



Submit