【问题标题】:Inheritance of non-list based classes in R?R中基于非列表的类的继承?
【发布时间】:2014-09-01 07:25:51
【问题描述】:

在 R 中,继承可以通过以下方式扩展基于列表的类来实现: 假设lmo 是从线性模型拟合获得的lm 类的对象。该类可以简单地通过以下方式扩展:

x <- rnorm(1000)
y <- rexp(1000)
lmo <- lm(x~y)

lmo$addition <- "some more information"
class(lmo) <- c("lmext","lm")

我仍然可以使用所有适用于lm 的方法,例如summary.lm,但也可以定义自定义方法。显然,在很多情况下,您只想添加最少的内容,并且仍然希望能够使用父类中的所有方法。

为不基于列表的类添加其他属性和实现方法继承的最佳方法是什么,例如时间序列? 这是我能想象的:

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

这是实现 + 等运算符的好方法吗?仍然可以在不为新类重新定义所有内容的情况下工作?有更好的吗?当例如将两个系列相互添加而不重新定义所有基本运算符时,有没有办法保留附加的类/属性?

【问题讨论】:

  • 从列表继承没有什么特别之处。 S3 类的继承按照您的描述工作。由于这种继承,父类的方法将可用。但是,正如您所指出的,父类方法可能无法保持您的新属性完好无损。在这种情况下,您可能必须重新定义所有影响您的属性的方法。至少,我不知道有什么不同的方法。
  • @Andrie 感谢您的洞察力。尽管列表确实没有什么特别之处,但不同之处在于保留列表元素比保留属性更容易——至少从我尝试过的情况来看是这样。特别是重新定义所有运算符对我来说似乎很麻烦。像您这样更有经验的用户没有看到不同的方式,这并不完全令人鼓舞......
  • 在添加两个对象的情况下,“保留额外的类属性”是什么意思? R在添加时无法知道您想对附加属性做什么。您将不得不覆盖运算符。但是您可以通过“Ops”缩短代码,例如要替换所有 2 元运算符,您可以使用 "Ops.MyNewClass" &lt;- function(e1, e2) { atts &lt;- MakeAttributes(e1, e2); res &lt;- NextMethod(e1, e2); res &lt;- AddAttributes(res, atts); res } 其中 NextMethod 为父类调用通常的加法/减法/等。
  • @PatrickRoocks 非常酷。我只是希望得到这样的提示。我没有听说过Ops 可以访问所有二元运算符。你从哪里学来的?也许这只是我没有阅读的具体帮助文档。
  • 其实我不知道我是在哪里第一次看到这个的。前段时间我问了question about operator overloading for functions,在那里我得到了一些提示。可能我在谷歌搜索时看到了一些带有“Ops”的示例代码......?S3groupGeneric?S4groupGeneric 告诉我们一些有关机制的信息,但那里的示例并没有真正的帮助:-/

标签: r oop inheritance


【解决方案1】:

这是删除附加 S3 类的基本函数的问题:

> foo=1:10
> class(foo)
[1] "integer"
> class(foo)=c("thing","integer")
> class(foo[1:4])
[1] "integer"

但是Date 是如何解决这个问题的呢?

> 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"

当然,通过为Date 类重新定义[

> get("[.Date")
function (x, ..., drop = TRUE) 
{
    cl <- oldClass(x)
    class(x) <- NULL
    val <- NextMethod("[")
    class(val) <- cl
    val
}

您可能会注意到,此方法实际上根本没有在其代码中提及Date - 它只是获取旧类,调用默认下标方法,然后重新分配原始类。为什么这不是默认行为是一个谜,但这确实意味着如果您想创建一个基于向量的新类,您可以将这个函数复制为您的新子集方法。

这是我所知道的在 R 中创建子类的问题的最简单的例子。这个答案的其余部分将显示更多的危险,我会尽量不要在这个过程中变得太鲁莽。我认为这与您的问题有关。

但遗憾的是,非基类在 R 代码中被滥用很多,你最终将不得不编写一堆其他相当“通用”的方法来让你的类正常工作: p>

> 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

所以现在你必须写as.data.frame.thing,幸运的是可以和as.data.frame.Date一样

> as.data.frame.thing = as.data.frame.Date
> d = data.frame(f=foo,x=1:10)
> d

太好了,现在您已经在数据框中获得了 thing 类。

然后有一天,你会尝试在数据框中使用你的类的向量对dplyr 做一些事情,然后你就会被吐槽:

> d %.% group_by(f) %.% summarise(m=mean(x))
Error in eval(expr, envir, enclos) : column 'f' has unsupported type

但是dplyr 可以与Date 对象一起使用,对吗?这是因为在 C++ 代码的深处,它会检查 Date 类型。此时你绝望了。

这些只是编写继承自现有类的 S3 类的一些缺陷。基本上,不起作用的东西只是起作用,至少如果您有其他语言的 OOP 经验,至少不会像您期望的那样起作用。

【讨论】:

    猜你喜欢
    • 2021-10-29
    • 2020-09-10
    • 2010-10-23
    • 2019-05-14
    • 1970-01-01
    • 1970-01-01
    • 2017-05-23
    • 1970-01-01
    相关资源
    最近更新 更多