R
函数rbind()
和cbind()
的调度机制是非标准的。当其中一个论点是rbind.myclass()
时,我探索了编写cbind.myclass()
或data.frame
函数的一些可能性,但到目前为止,我没有一个令人满意的方法。这篇文章专注于rbind
,但同样适用于cbind
。
让我们创建一个rbind.myclass()
函数,它在被调用时简单地回声。
rbind.myclass <- function(...) "hello from rbind.myclass"
我们创建了一个类myclass
的对象,以下调用rbind
所有正确发送到rbind.myclass()
a <- "abc"
class(a) <- "myclass"
rbind(a, a)
rbind(a, "d")
rbind(a, 1)
rbind(a, list())
rbind(a, matrix())
但是,当其中一个参数(这不一定是第一个)时,rbind()
将改为调用base::rbind.data.frame()
:
rbind(a, data.frame())
这种行为有点令人惊讶,但它实际上记录在dispatch
的rbind()
部分。给出的建议是:
如果要将其他对象与数据帧组合在一起,可能需要先将它们强制转换为数据帧。
实际上,这个建议可能难以实施。转换为数据框可能会删除基本类信息。此外,在发出命令rbind(a, x)
之后,可能不知道该建议的用户可能会遇到错误或意外结果。
第一种可能性是警告用户当rbind(a, x)
是数据帧时不应该调用x
。相反,包mypackage
的用户应该显式调用隐藏函数:
mypackage:::rbind.myclass(a, x)
这可以完成,但用户必须记住在需要时进行显式调用。调用隐藏函数是最后的手段,不应该是常规策略。
rbind
或者,我试图通过拦截派遣来保护用户。我的第一次尝试是提供base::rbind.data.frame()
的本地定义:
rbind.data.frame <- function(...) "hello from my rbind.data.frame"
rbind(a, data.frame())
rm(rbind.data.frame)
这失败了,因为rbind()
没有从rbind.data.frame
调用.GlobalEnv
,并像往常一样调用base
版本。
另一种策略是通过rbind()
中提出的本地函数来覆盖S3 dispatching of `rbind` and `cbind`。
rbind <- function (...) {
if (attr(list(...)[[1]], "class") == "myclass") return(rbind.myclass(...))
else return(base::rbind(...))
}
这适用于调度到rbind.myclass()
,因此用户现在可以为任何类型的对象rbind(a, x)
键入x
。
rbind(a, data.frame())
缺点是在library(mypackage)
之后我们得到消息The following objects are masked from ‘package:base’: rbind
。
虽然从技术上讲,一切都按预期工作,但应该有比base
函数覆盖更好的方法。
上述替代方案均不令人满意。我已经阅读了有关使用S4调度的替代方案,但到目前为止我还没有找到任何实现这个想法。任何帮助或指针?
正如你自己提到的那样,使用S4将是一个很好的解决方案。我最近没有调查数据帧,因为我对其他广义矩阵更感兴趣,在我的长期CRAN包'Matrix'(=“推荐”,即每个R分布的一部分)和'Rmpfr'中。
实际上甚至有两种不同的方式:
1)Rmpfr
使用新方法在rbind()/ cbind()中定义'...'的方法。这在?dotsMethods
(助记符:'...'=点)中有详细记载,并在Rmpfr / R / array.R第511行中实现(例如https://r-forge.r-project.org/scm/viewvc.php/pkg/R/array.R?view=annotate&root=rmpfr)
2)Matrix
通过为rbind2()和cbind2()定义(S4)方法来使用旧方法:如果你阅读?rbind
,它确实提到了它并且当使用rbind2 / cbind2时。其中的想法:“2”意味着您定义了S4方法,其中包含两个(“2”)矩阵类对象的签名,rbind / cbind以递归方式使用它们的两个可能的多个参数。
dotsMethod
方法由Martin Maechler提出并在Rmpfr
包中实施。我们需要使用S4定义一个新的泛型,类和方法。
setGeneric("rbind", signature = "...")
mychar <- setClass("myclass", slots = c(x = "character"))
b <- mychar(x = "b")
rbind.myclass <- function(...) "hello from rbind.myclass"
setMethod("rbind", "myclass",
function(..., deparse.level = 1) {
args <- list(...)
if(all(vapply(args, is.atomic, NA)))
return( base::cbind(..., deparse.level = deparse.level) )
else
return( rbind.myclass(..., deparse.level = deparse.level))
})
# these work as expected
rbind(b, "d")
rbind(b, b)
rbind(b, matrix())
# this fails in R 3.4.3
rbind(b, data.frame())
Error in rbind2(..1, r) :
no method for coercing this S4 class to a vector
我无法解决错误。有关相关问题,请参阅R: Shouldn't generic methods work internally within a package without it being attached?。
由于这种方法超越了rbind()
,我们得到警告The following objects are masked from 'package:base': rbind
。
我认为你不会想出一些完全令人满意的东西。您可以做的最好的是导出rbind.myclass
,以便用户可以直接调用它而无需执行mypackage:::rbind.myclass
。如果你愿意,你可以把它叫做其他东西(dplyr
称它的版本为bind_rows
),但是如果你选择这样做,我会使用一个唤起rbind
的名字,比如rbind_myclass
。
即使你可以让r-core同意更改调度行为,以便rbind
调度其第一个参数,仍然会出现用户希望将rbind
多个对象与myclass
对象一起放在第一个参数以外的地方的情况。用户如何发送到rbind.myclass(df, df, myclass)
?
data.table
解决方案似乎很危险;如果CRAN维护者在某个时候进行检查并且不允许这样做,我不会感到惊讶。