Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to list all S3 methods defined in a specific package / namespace for a particular generic function

I know I can list all S3 methods for a particular generic function, say summary, by

.S3methods("summary")
# [1] summary.aov                    summary.aovlist*              
# [3] summary.aspell*                summary.check_packages_in_dir*
# [5] summary.connection             summary.data.frame            
# [7] summary.Date                   summary.default               
# [9] summary.ecdf*                  summary.factor                
#[11] summary.glm                    summary.infl*                 
#[13] summary.lm                     summary.loess*                
#[15] summary.manova                 summary.matrix                
#[17] summary.mlm*                   summary.nls*                  
#[19] summary.packageStatus*         summary.PDF_Dictionary*       
#[21] summary.PDF_Stream*            summary.POSIXct               
#[23] summary.POSIXlt                summary.ppr*                  
#[25] summary.prcomp*                summary.princomp*             
#[27] summary.proc_time              summary.srcfile               
#[29] summary.srcref                 summary.stepfun               
#[31] summary.stl*                   summary.table                 
#[33] summary.tukeysmooth*  

However, I know that some of these functions come from stats, while some from base. If I load more packages into R this list can be much longer. I therefore want to restrict the search in a particular package / namespace, but I don't find a way to do this.

The manual of ?.S3methods appears to be self-contradicted.

 .S3methods(generic.function, class, envir=parent.frame())

envir: the environment in which to look for the definition of the
       generic function, when the generic function is passed as a
       character string.

So I tried the following but still get all methods displayed:

.S3methods("summary", envir = getNamespace("base"))

In "Details" section of the manual, it is said:

‘methods()’ finds S3 and S4 methods associated with either the ’generic.function’ or ‘class’ argument. Methods are found in all packages on the current ‘search()’ path. ‘.S3methods()’ finds only S3 methods, ‘.S4methods()’ finds only only S4 methods.

So basically it denies the use of argument envir.

Is there anyway I can achieve a restricted search and display.

like image 383
Zheyuan Li Avatar asked Aug 07 '18 14:08

Zheyuan Li


People also ask

What is S3 generic function?

S3 implements a style of object oriented programming called generic-function OO. This is different to most programming languages, like Java, C++ and C#, which implement message-passing OO. In message-passing style, messages (methods) are sent to objects and the object determines which function to call.

What is a generic function in R?

A generic function is one which may be applied to different types of inputs producing results depending on the type of input. Examples are plot() and summary() We demonstrate these differences using sample data from. Dalgaard's Introductory Statistics with R.

What is an S3 object R?

An S3 class is the most prevalent and used class in R programming. It is easy to implement this class and most of the predefined classes are of this type. An S3 object is basically a list with its class attributes assigned some names. And the member variable of the object created is the components of the list.

What is a constructor in R?

A constructor is a special method that's executed whenever you create a new object of the class. So, each time you run d <- Dog$new() , a new instance of the Dog class is created, and therefore, the constructor method is executed. In R and R6, the constructor is defined with the initialize function.


1 Answers

MrFlick's answer (now unfortunately deleted) is very helpful. It is my fault to have forgotten to check the returned values of .S3methods. However, his answer does not completely resolve the matter.

xx <- .S3methods("summary")
yy <- attr(xx, "info")
levels(yy$from)
#[1] "base"                            "datasets"                       
#[3] ".GlobalEnv"                      "graphics"                       
#[5] "grDevices"                       "methods"                        
#[7] "stats"                           "utils"                          
#[9] "registered S3method for summary"

The level "registered S3method for summary" is rather ambiguous. With this approach, only 5 results from stats package can be displayed:

xx[yy$from == "stats"]
#[1] "summary.aov"     "summary.glm"     "summary.lm"      "summary.manova" 
#[5] "summary.stepfun"

Just after my posting this question, I realized that there is one way via regex. And this reveals that in fact there are 16 hits.

grep("^summary.", ls(getNamespace("stats")), value = TRUE)
# [1] "summary.aov"         "summary.aovlist"     "summary.ecdf"       
# [4] "summary.glm"         "summary.infl"        "summary.lm"         
# [7] "summary.loess"       "summary.manova"      "summary.mlm"        
#[10] "summary.nls"         "summary.ppr"         "summary.prcomp"     
#[13] "summary.princomp"    "summary.stepfun"     "summary.stl"        
#[16] "summary.tukeysmooth"

So before any alternative solution is found, I would stay with using regex. Here is a function.

## provide (generic) function name and package name as strings
findS3Fun <- function (Fun, pkg) {
  all_fun <- ls(getNamespace(pkg))
  all_fun[startsWith(all_fun, sprintf("%s.", Fun))]
  }

findS3Fun("summary", "stats")

ahem, regex is buggy!

findS3Fun is actually buggy.

findS3Fun("seq", "base")
#[1] "seq.Date"    "seq.default" "seq.int"     "seq.POSIXt" 

findS3Fun("sort", "base")
#[1] "sort.default" "sort.int"     "sort.list"    "sort.POSIXlt"

seq.int is not the "int" method for seq. Neither sort.int nor sort.list is the "int" or "list" method for sort. They are just stand alone functions with . in their function name.

.S3methods("seq")
#[1] seq.Date    seq.default seq.POSIXt 

.S3methods("sort")
#[1] sort.bibentry* sort.default   sort.POSIXlt 

## this function is from package `utils` not `base`
environment(getS3method("sort", "bibentry"))
#<environment: namespace:utils>

So the safest approach is probably still to work with the returned values of .S3methods.

use getAnywhere (getS3method)

Back to the example with "summary". Functions that are not exported from namespaces, i.e., those with yy$visible = FALSE, have yy$from = "registered S3method for summary".

with(yy, from[!visible])
# [1] registered S3method for summary registered S3method for summary
# [3] registered S3method for summary registered S3method for summary
# [5] registered S3method for summary registered S3method for summary
# [7] registered S3method for summary registered S3method for summary
# [9] registered S3method for summary registered S3method for summary
#[11] registered S3method for summary registered S3method for summary
#[13] registered S3method for summary registered S3method for summary
#[15] registered S3method for summary registered S3method for summary
#8 Levels: base datasets graphics grDevices methods stats ... registered S3method for summary

However, since we know the names of these functions, why not apply getAnywhere on them?

zz <- lapply(xx[!yy$visible], getAnywhere)

Then the package / namespace information can be extracted from zz with some effort. However, this lapply + getAnywhere is quite slow. Since getAnywhere returns more things than I needed, I dug into its source code to see whether I could do some trimming. It turns out that I can.

hidden <- xx[!yy$visible]
# [1] "summary.aovlist"               "summary.aspell"               
# [3] "summary.check_packages_in_dir" "summary.ecdf"                 
# [5] "summary.infl"                  "summary.loess"                
# [7] "summary.mlm"                   "summary.nls"                  
# [9] "summary.packageStatus"         "summary.PDF_Dictionary"       
#[11] "summary.PDF_Stream"            "summary.ppr"                  
#[13] "summary.prcomp"                "summary.princomp"             
#[15] "summary.stl"                   "summary.tukeysmooth"  

CLASS <- substr(hidden, nchar("summary") + 2L, nchar(hidden))
#[1] "aovlist"               "aspell"                "check_packages_in_dir"
# [4] "ecdf"                  "infl"                  "loess"                
# [7] "mlm"                   "nls"                   "packageStatus"        
#[10] "PDF_Dictionary"        "PDF_Stream"            "ppr"                  
#[13] "prcomp"                "princomp"              "stl"                  
#[16] "tukeysmooth"          

vapply(CLASS,
       function (u) getNamespaceName(environment(getS3method("summary", u)))[[1L]],
       "", USE.NAMES = FALSE)
# [1] "stats" "utils" "tools" "stats" "stats" "stats" "stats" "stats" "utils"
#[10] "tools" "tools" "stats" "stats" "stats" "stats" "stats"

Final solution

Now let me wrap up these ideas into a function.

findS3Fun <- function (Fun, pkg) {
  xx <- .S3methods(Fun)
  yy <- attr(xx, "info")[1:2]
  where <- yy[[2L]]  ## yy$from
  where <- levels(where)[where]  ## factor to character
  hidden <- !yy[[1L]]  ## !yy$visible
  hidden_xx <- xx[hidden]  ## hidden functions
  if (length(hidden) > 0L) {
    CLASS <- substr(hidden_xx, nchar(Fun) + 2L, nchar(hidden_xx))
    aux <- function (u) getNamespaceName(environment(getS3method(Fun, u)))[[1L]]
    where[hidden] <- vapply(CLASS, aux, "", USE.NAMES = FALSE)
    }
  export <- where == pkg
  xx <- xx[export]
  visible <- yy[[1L]][export]
  ## use "regex" to find functions with "." in their names but not methods
  all_fun <- ls(getNamespace(pkg))
  all_fun <- all_fun[startsWith(all_fun, sprintf("%s.", Fun))]
  misc <- all_fun[!(all_fun %in% xx)]
  ## return functions by category
  list(visible = xx[visible], invisible = xx[!visible], misc = misc)
  }

In the end I still use regex to catch functions with . in their names but are not rightful methods. They are classified as "misc", besides "visible" and "invisible".

Test

findS3Fun("summary", "stats")
#$visible
#[1] "summary.aov"     "summary.glm"     "summary.lm"      "summary.manova" 
#[5] "summary.stepfun"
#
#$invisible
# [1] "summary.aovlist"     "summary.ecdf"        "summary.infl"       
# [4] "summary.loess"       "summary.mlm"         "summary.nls"        
# [7] "summary.ppr"         "summary.prcomp"      "summary.princomp"   
#[10] "summary.stl"         "summary.tukeysmooth"
#
#$misc
#character(0)

findS3Fun("sort", "base")
#$visible
#[1] "sort.default" "sort.POSIXlt"
#
#$invisible
#character(0)
#
#$misc
#[1] "sort.int"  "sort.list"

findS3Fun("[", "base")
#$visible
# [1] "[.AsIs"            "[.data.frame"      "[.Date"           
# [4] "[.difftime"        "[.Dlist"           "[.factor"         
# [7] "[.hexmode"         "[.listof"          "[.noquote"        
#[10] "[.numeric_version" "[.octmode"         "[.POSIXct"        
#[13] "[.POSIXlt"         "[.simple.list"     "[.table"          
#[16] "[.warnings"       
#
#$invisible
#character(0)
#
#$misc
#character(0)

findS3Fun("[[", "base")
#$visible
#[1] "[[.data.frame"      "[[.Date"            "[[.factor"         
#[4] "[[.numeric_version" "[[.POSIXct"        
#
#$invisible
#character(0)
#
#$misc
#character(0)
like image 99
Zheyuan Li Avatar answered Sep 20 '22 23:09

Zheyuan Li