user32259
user32259

Reputation: 1143

R: Extending function to other (S3) classes

EDIT: The question has changed slightly to why a specific extension doesn't work, rather than why a general method doesn't work.

As indicated by the title, I'm having trouble extending functions to other (S3) classes.

For example:

x <- y <- runif(10)
loessModel = loess(y ~ x)

methods("cor")
## [1] cor.test cor.test.default* cor.test.formula*

cor.loess = function(loessModel, ...) { cor(loessModel$x, loessModel$y, ...) }
cor(loessModel)
## Error in cor(loessModel) : supply both 'x' and 'y' or a matrix-like 'x'

However, the following works:

getCor = function(x, y, ...) { UseMethod("getCor") }
getCor.default = function(x, y, ...) { cor(x, y, ...) }
getCor.loess = function(loessModel, ...) getCor(loessModel$x, loessModel$y, ...)
getCor(loessModel)
## [,1]
## x    1

So... as explained by Josh, the first method of extending cor didn't work because cor isn't a generic function. The second method works, but I can't extend it to a class of LoessList. This is puzzling to me, particularly as it works 'outside the function':

set.seed(13)
df = data.frame(id = rep.int(1:2, 10), 
                x = runif(20), 
                y = runif(20))

loessList = structure(dlply(df, "id", loess, formula = as.formula("y ~ x")),
                      class = "LoessList")

getCor.LoessList = function(loessList, ...) { ldply(loessList, getCor, ...) }
getCor(loessList)
## Error in is.data.frame(y) : argument "y" is missing, with no default

ldply(loessList, getCor)
##   id           1
## 1  1 -0.01552707
## 2  2 -0.38997723

On a more general note, are there any good guides to OOP in R? I have been using http://logic.sysbiol.cam.ac.uk/teaching/advancedR/slides.pdf as my main point of reference, but other sources are always appreciated.

Cheers

Upvotes: 1

Views: 916

Answers (2)

G. Grothendieck
G. Grothendieck

Reputation: 269481

1. Generics in S3 can be extended but if you want to turn a non-generic into a generic and extend it then you will need S4. Here we define an S4 generic which defaults to stats::cor. Then we make the S3 method loess available to S4 and define a loess method:

setGeneric("cor", function(x, ...) stats::cor(x, ...))
setOldClass("loess") # let S4 use an S3 class
setMethod("cor", list(x = "loess"), 
   def = function(x, ...) callNextMethod(x$x, x$y, ...))

Now we can do this:

example(loess)
cor(cars.lo)

2. The other approach if you want to stick with S3 is to clobber cor with your own S3 generic:

cor <- function(x, ...) UseMethod("cor")
cor.default <- stats::cor
cor.loess <- function(x, ...) stats::cor(x$x, x$y, ...)

example(loess)
cor(cars.lo)

3. Of course you could just redefine cor appropriately and forget about OO dispatch. There are actually some potential problems with this since it makes cor the parent frame of call to the function doing the real work but in many cases it won't matter. (See lm source code to find out how to circumvent this.)

cor <- function(x, ...) {
    if (inherits(x, "loess")) return(cor(x$x, x$y, ...))
    stats::cor(x, ...)
}

Info: For info on S3 see ?UseMethod and references and links on that page. For info on S4 see ?Methods and references and links on that page.

UPDATE: Added additional approaches and references.

Upvotes: 4

Joshua Ulrich
Joshua Ulrich

Reputation: 176648

Pay attention to the warning when you run methods(cor).

R> methods("cor")
[1] cor.test          cor.test.default* cor.test.formula*

   Non-visible functions are asterisked
Warning message:
In methods("cor") : function 'cor' appears not to be generic

cor isn't generic, so you can't just define a method. You'd need to define a generic too, which is what you did in the solution in your question.

Upvotes: 3

Related Questions