Reputation: 133
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"
)
.Foo
: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
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