提问者:小点点

如何为基矩阵S3子类编写 `%*%` 方法?


我想为基矩阵子类编写一个%*%方法。我的子类是S3类,help("%*%")的留档说%*%是S4泛型。在使用方法::setOldClass()方法之前,我已经为S4泛型方法编写了S3类的方法::setMethod()并且我已经查看了{Matrix}包的源代码以获得灵感,但是由于某种原因,我不能完全让它在这种情况下工作。方法::show方法()显示我的方法存在于我的目标签名中,但是当我尝试使用它时,我的方法似乎从来没有被R调用。

x <- diag(3)
class(x) <- c("atm2d", class(matrix()))
print(x)
     [,1] [,2] [,3]
[1,]    1    0    0
[2,]    0    1    0
[3,]    0    0    1
attr(,"class")
[1] "atm2d"  "matrix" "array" 

默认的%*%摆脱了我的类属性,我想保留它。

print(x %*% x)
     [,1] [,2] [,3]
[1,]    1    0    0
[2,]    0    1    0
[3,]    0    0    1

我尝试为我的类创建一个%*%方法,它不会摆脱我的类属性:

as.matrix.atm2d <- function(x, ...) {
    class(x) <- NULL
    x
}
matmult <- function(x, y) {
    v <- as.matrix(x) %*% as.matrix(y)
    class(v) <- c("atm2d", class(matrix()))
    v
}
methods::setOldClass("atm2d")
# methods::setOldClass(c("atm2d", class(matrix()))) # also doesn't work for me
methods::setMethod("%*%", c(x = "atm2d", y = "atm2d"), function(x, y) matmult(x, y))

show方法()似乎显示了一个创建了预期签名的S4方法:

showMethods("%*%", class = "atm2d")
Function: %*% (package base)
x="atm2d", y="atm2d"

然而,这个方法实际上似乎并没有被%*%调用:

print(x %*% x)
     [,1] [,2] [,3]
[1,]    1    0    0
[2,]    0    1    0
[3,]    0    0    1

如果我的方法被调用,我会期望它也打印出它的类:

print(matmult(x, x))
     [,1] [,2] [,3]
[1,]    1    0    0
[2,]    0    1    0
[3,]    0    0    1
attr(,"class")
[1] "atm2d"  "matrix" "array"

共1个答案

匿名用户

%*%运算符在内部是通用的,这意味着调度发生在C代码中。目前(即在R 4.2.3中),相应的C函数do_matprod(此处定义)包含以下检查:

if (PRIMVAL(op) == 0 && /* %*% is primitive, the others are .Internal() */
   (IS_S4_OBJECT(x) || IS_S4_OBJECT(y))
   && R_has_methods(op)) {
    SEXP s, value;
    /* Remove argument names to ensure positional matching */
    for(s = args; s != R_NilValue; s = CDR(s)) SET_TAG(s, R_NilValue);
    value = R_possible_dispatch(call, op, args, rho, FALSE);
    if (value) return value;
}

如果xy都不是S4对象,如您的示例所示,那么do_matprod继续将它们作为传统矩阵处理,而不是查看任一参数的class属性。您引用的help("%*%")部分:

此运算符是S4泛型但不是S3泛型。需要为名为xy的两个参数的函数编写S4方法。

试图表达这一点,但不是特别清楚。(毕竟你确实定义了一个S4方法。)

这里有两个主要问题:

>

  • setOldClass允许您在签名中使用S3类定义S4方法,但内部泛型函数仅在其中一个参数是S4对象时查找S4方法(为了速度)。

    %*%不是S3通用的,因此即使您注册了像%*%. zzz这样的S3方法,它也不会被分派。

    话虽如此,R-core已经promise在不久的将来使%*%运算符S3通用。如果发生这种情况,那么%*%的行为将类似于Ops组的其他内部通用成员,因为S3方法%*%. zzz将在适当的地方被分派。但是,当两个参数都不是S4对象时,S4方法仍然不会被分派。

    可以说,当您尝试定义永远不会分派的S4方法时,应该更改setMethod以发出警告或错误信号,就像您在示例中所说的那样。

    枚举泛型函数的类型和它们调度的方法类型可能会有所帮助,从而将注意力限制在S3和S4上。我们将使用此脚本为我们的测试定义对象,每个对象都应该在新的R进程中运行:

    ## objects.R
    w <- structure(0, class = "a")
    x <- structure(0, class = "b")
    
    setOldClass("c")
    y <- structure(0, class = "c")
    
    setClass("d", contains = "numeric")
    z <- new("d", 0)
    

    这些通过UseMethod为S3类和S4类调度S3方法。当没有找到方法时,它们会调度默认方法*. default或(如果没有找到)抛出错误。他们从不调度S4方法。

    source("objects.R")
    
    h <- .__h__. <- function(x) UseMethod("h")
    .S3method("h", "default", function(x) "default")
    
    .S3method("h", "a", function(x) "a")
    .S3method("h", "b", function(x) "b")
    setMethod("h", "c", function(x) "c")
    setMethod("h", "d", function(x) "d")
    
    h <- .__h__. # needed to undo side effect of 'setMethod'
    h
    ## function(x) UseMethod("h")
    
    h(w)
    ## [1] "a"
    h(x)
    ## [1] "b"
    h(y)
    ## [1] "default"
    h(z)
    ## [1] "default"
    

    这些分派S3类(用setOldClass正式定义)和S4类的S4方法通过标准通用。当没有找到方法时,它们分派默认方法*@default。如果默认方法是S3通用的,那么再次分派,这次分派到任何可用的S3方法。但是,通常默认方法只是调用stop来发出错误信号。

    source("objects.R")
    
    h <- function(x) UseMethod("h")
    .S3method("h", "default", function(x) "default")
    
    .S3method("h", "c", function(x) "c")
    setMethod("h", "d", function(x) "d")
    
    h
    ## standardGeneric for "h" defined from package ".GlobalEnv"
    ## 
    ## function (x) 
    ## standardGeneric("h")
    ## <environment: 0x1044650b0>
    ## Methods may be defined for arguments: x
    ## Use  showMethods(h)  for currently available ones.
    
    h@default
    ## Method Definition (Class "derivedDefaultMethod"):
    ## 
    ## function (x) 
    ## UseMethod("h")
    ## 
    ## Signatures:
    ##         x    
    ## target  "ANY"
    ## defined "ANY"
    
    h(w)
    ## [1] "default"
    h(x)
    ## [1] "default"
    h(y)
    ## [1] "c"
    h(z)
    ## [1] "d"
    

    这些都是在base中定义的。您可以参考帮助页面或源代码来确定它们是否仅是S3泛型,仅是S4泛型,还是S3和S4泛型兼而有之。在第三种情况下,只有在没有找到合适的S4方法时,才会发生S3分派。而且,正如我已经解释过的,只有当签名中的一个参数是S4对象时,才会发生S4分派。

    让我们以%*%为例。两者都是S4通用的,但只有是S3通用的。

    source("objects.R")
    
    .S3method("+", "default", function(e1, e2) "default")
    
    .S3method("+", "a", function(e1, e2) "a3")
    .S3method("+", "b", function(e1, e2) "b3")
    .S3method("+", "c", function(e1, e2) "c3")
    .S3method("+", "d", function(e1, e2) "d3")
    
    setMethod("+", c("c", "c"), function(e1, e2) "c4")
    setMethod("+", c("d", "d"), function(e1, e2) "d4")
    
    w + w
    ## [1] "a3"
    x + x
    ## [1] "b3"
    y + y
    ## [1] "c3"
    z + z
    ## [1] "d4"
    
    source("objects.R")
    
    .S3method("%*%", "default", function(x, y) "default")
    
    .S3method("%*%", "a", function(x, y) "a3")
    .S3method("%*%", "b", function(x, y) "b3")
    .S3method("%*%", "c", function(x, y) "c3")
    .S3method("%*%", "d", function(x, y) "d3")
    
    setMethod("%*%", c("c", "c"), function(x, y) "c4")
    setMethod("%*%", c("d", "d"), function(x, y) "d4")
    
    w %*% w
    ##      [,1]
    ## [1,]    0
    x %*% x
    ##      [,1]
    ## [1,]    0
    y %*% y
    ##      [,1]
    ## [1,]    0
    z %*% z
    ## [1] "d4"