为新 class 定义 S3 "Math" 组泛型时出现意外的 log2 错误

Unexpected log2 error when defining S3 "Math" group generics for a new class

我正在尝试使用 S3 "Math" 组泛型进行自定义 class。但是我得到了一个奇怪的结果:log() 有效,而 log2log10 产生错误。下面是一个最小的例子:

# simple class with just the new name
lameclass <- function(x) {
  class(x) <- append(class(x), "lame")
  x
}

# It prints something when Math generics methods are used
Math.lame <- function(x, ...) {
  print("I am lame")
  NextMethod()
}

# an object of the class
lamevector <- lameclass(1:10)

> class(lamevector)
[1] "integer" "lame"

现在尝试调用 log:

log(lamevector)
[1] "I am lame"
[1] 0.0000000 0.6931472 1.0986123 1.3862944 1.6094379 1.7917595 1.9459101 2.0794415 2.1972246 2.3025851

基数 2:

log(lamevector, 2)
[1] "I am lame"
[1] 0.000000 1.000000 1.584963 2.000000 2.321928 2.584963 2.807355 3.000000 3.169925 3.321928

以上都有效。但是现在 log2 包装器:

log2(lamevector)
[1] "I am lame"
[1] "I am lame"
Error in log2.default(1:10, 2) :
  2 arguments passed to 'log2' which requires 1

也许有人可以帮我弄清楚这里发生了什么? log2 是否真的经历了 2 次通用数学定义并失败了?

如评论 log2 中所述,log10 不在 S3 Math 泛型中。事实上,expexpm1loglog10log2log1p就是S4 generic 并且是数学组 generic 的成员。

实现您想做的事情的一种方法是将您 class 定义为 S4 class。

setClass("lame4", slots = c(x = "numeric"))

并定义方法 Math group generic :

setMethod("Math","lame4",function(x) {
                x@x <- callGeneric(x@x)
                x
          }) 
## pretty print 
setMethod("show", "lame4",function(object)print(object@x))

现在让我们测试一下:

l1 <- new("lame4",x=1:10)

然后:

log2(l1)
 [1] 0.000000 1.000000 1.584963 2.000000 2.321928 2.584963 2.807355 3.000000 3.169925 3.321928
> log10(l1)
 [1] 0.0000000 0.3010300 0.4771213 0.6020600 0.6989700 0.7781513 0.8450980 0.9030900 0.9542425
[10] 1.0000000 

这当然不是对您问题的直接回答,而是解释了为什么您的实施不起作用。在这里,我认为使用 S4 范式是一个好主意,因为您将拥有更强的打字能力,这对数学非常有帮助。 S4 方法也适用于 R.C/Rcpp 接口。但是如果你是新手,有一定的学习曲线(取决于你的开发背景)

似乎正在发生的事情是 NextMethod 没有剥离 lame class,所以当 log2 调用 log 时,它会重新调度lame 方法现在不再有效,因为它使用 base = 2L 调用 log2,参数 log2 没有。

强制调度正常工作并不需要太多工作——只需删除并重新添加 class。 (另外:Subclasses 应该放在前面,而不是附加。)

lameclass <- function(x) {
    class(x) <- c("lame", class(x))    # prepend new class
    x
}

Math.lame <- function(x, ...) {
    print("I am lame")
    class(x) <- class(x)[class(x) != "lame"]    # strip lame class
    lameclass(NextMethod())    # re-add lame class to result
}

lamevector <- lameclass(1:5)

log(lamevector)
#> [1] "I am lame"
#> [1] 0.0000000 0.6931472 1.0986123 1.3862944 1.6094379
#> attr(,"class")
#> [1] "lame"    "numeric"
log(lamevector, 2)
#> [1] "I am lame"
#> [1] 0.000000 1.000000 1.584963 2.000000 2.321928
#> attr(,"class")
#> [1] "lame"    "numeric"
log2(lamevector)
#> [1] "I am lame"
#> [1] 0.000000 1.000000 1.584963 2.000000 2.321928
#> attr(,"class")
#> [1] "lame"    "numeric"

我不太确定为什么它是这样调度的。组泛型有点奇怪,并且在 oldClass 而不是 class 上调度,这可能是也可能不是问题的一部分。这可能只是一个错误。在其他Math方法中使用了剥离和重新添加class的成语,可能是因为这个原因:

MASS:::Math.fractions
#> function (x, ...) 
#> {
#>     x <- unclass(x)
#>     fractions(NextMethod())
#> }
#> <bytecode: 0x7ff8782a1558>
#> <environment: namespace:MASS>