Inheriting non-list classes in R?
R inheritance can be implemented by extending a list-based class as follows: Suppose lmo
is a class object lm
derived from linear model fitting. The class can be simply extended:
x <- rnorm(1000)
y <- rexp(1000)
lmo <- lm(x~y)
lmo$addition <- "some more information"
class(lmo) <- c("lmext","lm")
I could use all the methods like summary.lm
that worked for lm
but also defined custom methods. Obviously there are many situations where you want minimal padding and still want to be able to use all the methods from the parent class.
What is the best way to add additional properties and implement method inheritance for classes that are not list-based like eg. Time series? Here's what I can imagine:
ts1 <- ts(rnorm(100),start = c(1990,1),frequency = 4)
attr(ts1,"additional") <- "some more information"
class(ts1) <- c("tsext","ts")
print.tsext <-
# some method that uses the original print method for ts, plus extracts
# the additional information
Is this a good way to ensure that operators like +
and so on still work without redefining everything for the new class? Is there something better? And is there a way to keep the additional class / attributes, for example adding two series to each other without overriding all the basic operators?
source to share
This is an issue with basic functions dropping extra S3 classes:
> foo=1:10
> class(foo)
[1] "integer"
> class(foo)=c("thing","integer")
> class(foo[1:4])
[1] "integer"
But how do you Date
get around this?
> dv = as.Date(c("2013-01-01","2013-02-02","2013-02-02","2013-02-06"))
> class(dv)
[1] "Date"
> class(dv[2:3])
[1] "Date"
BY overrides [
for class Date
:
> get("[.Date")
function (x, ..., drop = TRUE)
{
cl <- oldClass(x)
class(x) <- NULL
val <- NextMethod("[")
class(val) <- cl
val
}
You might have noticed that this method is not mentioned Date
at all in his code - it just gets the old class, calls the default index method, and then reassigns the original class. Quite why this is not the default behavior is a mystery, but it does mean that if you want to create a new vector-based class, you can simply copy this function as your new subset method.
This is the simplest example I know of about the problem of subclassing in R. The rest of this answer will show a few more dangers, and I'll try not to get too much work in the process. I think this is all pertinent to your question.
But unfortunately, non-base classes overuse LOT in R code, and you'll have to write a bunch of other pretty "generic" methods to get your class to work:
> d = data.frame(f=foo,x=1:10)
Error in as.data.frame.default(x[[i]], optional = TRUE) :
cannot coerce class ""thing"" to a data.frame
now you need to write as.data.frame.thing
which luckily can be the same asas.data.frame.Date
> as.data.frame.thing = as.data.frame.Date
> d = data.frame(f=foo,x=1:10)
> d
Great, now you have a class thing
in your dataframe.
Then one day you try to do something with dplyr
using a vector of your class in a dataframe and you get a flattened one:
> d %.% group_by(f) %.% summarise(m=mean(x))
Error in eval(expr, envir, enclos) : column 'f' has unsupported type
But dplyr
does it work with objects Date
correctly? This is because deep in C ++ code, it checks for types Date
. At this point, you get discouraged.
These are just a few of the downsides to writing S3 classes that inherit from existing classes. Basically, the stuff doesn't work, at least not the way you might expect if you have experience with OOP in another language.
source to share