Rai
Rai

Reputation: 133

R different arguments for the same S4 method applied to different classes

I have just started working with S4 objects and I would like to achieve something that with S3 seems really straightforward, but I cannot figure out how to do it with S4 classes.

With S3, by using UseMethod() is really easy to create a constructor which behave differently depending on the classes of the inputs, for example:

.Foo <- function(...) UseMethod(".Foo")

.Foo.character <- function(x, y, z) {
    y <- as.factor(y)
    structure(data.frame(y, z, x, stringsAsFactors = FALSE),
        class = c('Foo', 'data.frame'))
}
.Foo.numeric <- function(x, ...) {
    .Foo(x = as.character(x), ...)
}
.Foo.data.frame <- function(df) {
    do.call(what = .Foo, args = df[, c('x', 'y', 'z')]) 
}

And this way, given a data frame we can apply .Foo() on it and get an object of class Foo:

df <- data.frame(x = 1:4, y = c(2,2,3,3), z = 5:8)
tmp <- .Foo(df)
str(tmp)

Which prints:

Classes ‘Foo’ and 'data.frame': 4 obs. of  3 variables:
 $ y: Factor w/ 2 levels "2","3": 1 1 2 2
 $ z: int  2 3 4 5
 $ x: chr  "1" "2" "3" "4"

Now, trying to do the same using S4 I have:

setOldClass("data.frame")
Foo <- setClass("Foo",
          contains = "data.frame"
         )
setGeneric(".Foo", function(x, ...) standardGeneric(".Foo"))
setMethod(".Foo", signature("character"), function(x, y, z) {
    y <- as.factor(y)
    Foo(data.frame(y, z, x, stringsAsFactors = FALSE))
})
setMethod(".Foo", signature("numeric"), function(x, ...) {
    .Foo(x = as.character(x), ...)
})
setMethod(".Foo", signature("data.frame"), function(x) {
    do.call(what = .Foo, args = x[, c("x", "y", "z")])
})

After str(.Foo(df)) we get:

'data.frame':   4 obs. of  3 variables:
Formal class 'Foo' [package ".GlobalEnv"] with 4 slots
  ..@ .Data    :List of 3
  .. ..$ : Factor w/ 2 levels "2","3": 1 1 2 2
  .. ..$ : int  2 3 4 5
  .. ..$ : chr  "1" "2" "3" "4"
  ..@ names    : chr  "y" "z" "x"
  ..@ row.names: int  1 2 3 4
  ..@ .S3Class : chr "data.frame"

So this approach works. Now my question and problem is: is this done correctly? If I want to have as an argument for .Foo.data.frame df instead of x, can I do it? Or I cannot because all the first inputs must have the same name as the defined in the Generic? Can I somehow do something like:

setGeneric(".Foo", function(x, df, ...) standardGeneric(".Foo"))
setMethod(".Foo", signature("character"), function(x, y, z) {
  y <- as.factor(y)
  Foo(data.frame(y, z, x, stringsAsFactors = FALSE))
})
setMethod(".Foo", signature("numeric"), function(x, ...) {
  .Foo(x = as.character(x), ...)
})
setMethod(".Foo", signature(x = "missing", df = "data.frame"), function(df) {
  do.call(what = .Foo, args = df[, c('x', 'y', 'z')])
})

.Foo(df)

But this fails with the error

 Error in (function (classes, fdef, mtable)  : 
  unable to find an inherited method for function ‘.Foo’ for signature ‘"data.frame", "missing"’

Thanks in advance! Sorry if I overextended or if I explained things incorrectly but is my first time working with S4 and I cannot find information but really simple one.

Upvotes: 1

Views: 226

Answers (1)

JDL
JDL

Reputation: 1654

The reason for your errors is due to argument matching.

.Foo(df)

is interpreted as

.Foo(
  x=df,
  df=(missing),
  ...=(empty list)
)

This is what R is trying to tell you when it returns the signature (data.frame, missing) --- it looked for a method for signature (foo, missing), didn't find one, tried the superclasses (i.e. data frame) and didn't find a method for that signature either.

If you want to specify the object df as the argument df then you can either name it explicitly

.Foo(df=df)

or use positional matching:

.Foo(,df)

both of which should work for you.

Upvotes: 2

Related Questions