Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

dispatching S4 methods with an expression as argument

I'm trying to convince an S4 method to use an expression as an argument, but I always get an error returned. A trivial example that illustrates a bit what I'm trying to do here :

setGeneric('myfun',function(x,y)standardGeneric('myfun'))

setMethod('myfun',c('data.frame','expression'),
          function(x,y) transform(x,y) )

If I now try :

> myfun(iris,NewVar=Petal.Width*Petal.Length)
Error in myfun(iris, NewVar = Petal.Width * Petal.Length) : 
  unused argument(s) (NewVar = Petal.Width * Petal.Length)

> myfun(iris,{NewVar=Petal.Width*Petal.Length})
Error in myfun(iris, list(NewVar = Petal.Width * Petal.Length)) : 
 error in evaluating the argument 'y' in selecting a method for 
 function 'myfun': Error: object 'Petal.Width' not found

It seems the arguments are evaluated in the generic already if I understand it right. So passing expressions down to methods seems at least tricky. Is there a possibility to use S4 dispatching methods using expressions?


edit : changed to transform, as it is a better example.

like image 848
Joris Meys Avatar asked Aug 09 '11 16:08

Joris Meys


2 Answers

You've specified "expression" as the class of the second argument in this example method. The first example returns an error because

NewVar=Petal.Width*Petal.Length

is being interpreted as a named argument to myfun with value

Petal.Width*Petal.Length

that doesn't get the chance to be evaluated, because NewVar isn't an argument for this method or generic.

In the second example, I'm not sure what is going on with the closed curly braces, as my error differs from the one shown:

Error in myfun(iris, { : error in evaluating the argument 'y' in selecting a method for function 'myfun': Error: object 'Petal.Width' not found

However, I receive no error and get the iris data.frame as output when I force your expression to be an expression object:

myfun(iris, expression(NewVar=Petal.Width*Petal.Length))

I think this only partially answers your question, because trivially returning the iris data.frame was not what you wanted. The expression is not being evaluated properly by transform(). I suspect you want the output to match exactly the output from the following hard-coded version:

transform(iris, NewVar=Petal.Width*Petal.Length)

Here is a short example evaluating the expression using eval

z <- expression(NewVar = Petal.Width*Petal.Length)
test <- eval(z, iris)
head(test, 2)

[1] 0.28 0.28

Here is a version that works for adding one variable column to the data.frame:

setGeneric('myfun',function(x,y)standardGeneric('myfun'))
setMethod('myfun',c('data.frame','expression'), function(x,y){
    etext <- paste("transform(x, ", names(y), "=", as.character(y), ")", sep="")
    eval(parse(text=etext))
})
## now try it.
test <- myfun(iris, expression(NewVar=Petal.Width*Petal.Length))
names(test)

[1] "Sepal.Length" "Sepal.Width" "Petal.Length" "Petal.Width" "Species" "NewVar"

head(test)

    Sepal.Length Sepal.Width Petal.Length Petal.Width Species NewVar
1          5.1         3.5          1.4         0.2  setosa   0.28
2          4.9         3.0          1.4         0.2  setosa   0.28

Again, this implementation has essentially hard-coded that one, and only one, variable column will be added to the input data.frame, although the name of that variable column and the expression are arbitrary, and provided as an expression. I'm certain there is a better, more general answer that would evaluate the expression held in y as if it were a direct call in the transform() function, but I'm stumped at the moment what to use as the appropriate "inverse" function to expression( ).

There is always the standard ... , if you don't actually want to dispatch on y:

setGeneric('myfun', function(x, ...) standardGeneric('myfun'))
setMethod('myfun', 'data.frame', function(x, ...){
    transform(x, ...)
})

And this works great. But your question was about actually dispatching on an expression object.

The following does not work, but I think it is getting closer. Perhaps someone can jump in and make some final tweaks:

setGeneric('myfun', function(x, y) standardGeneric('myfun'))
setMethod('myfun',c('data.frame', 'expression'), function(x, y){
    transform(x, eval(y, x, parent.frame()))
})
## try out the new method
z <- expression(NewVar = Petal.Width*Petal.Length)
test <- myfun(iris, z)
names(test)

[1] "Sepal.Length" "Sepal.Width" "Petal.Length" "Petal.Width" "Species"

Essentially, the "NewVar=" piece of the expression was not passed to transform() when we called myfun().

After much trial and error, I figured out a way that works for real. First convert the expression object into a list with as.list(), then build up the call that you want with the marvelous

do.call()

The complete example looks like this:

setGeneric('myfun', function(x, y) standardGeneric('myfun'))
setMethod('myfun',c('data.frame', 'expression'), function(x, y){
    do.call("transform", c(list(x), as.list(y)))
})
# try out the new method
z <- expression(NewVar = Petal.Width*Petal.Length)
test <- myfun(iris, z)
names(test)
[1] "Sepal.Length" "Sepal.Width"  "Petal.Length" "Petal.Width"  "Species"     
[6] "NewVar"

And the new data.frame object "test" has the "NewVar" column we wanted.

like image 54
Paul 'Joey' McMurdie Avatar answered Oct 21 '22 12:10

Paul 'Joey' McMurdie


It's not S4, or the argument evaluation, its that R can't tell if you mean to pass a named parameter or if your expression is of the form a=b.

If you look at the help for "within", it uses curly brackets to make sure the expression is parsed as an expression.

I also think calling within inside a function won't do the replacement in the function's caller...

I also think I don't know enough about expressions.

like image 43
Spacedman Avatar answered Oct 21 '22 10:10

Spacedman