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

Tre*_*vor 11 r matrix r-s3 r-s4

我想%*%为基本矩阵子类编写一个方法。我的子类是一个 S3 类,并且 的文档help("%*%")说它是一个 S4 泛型,并且需要为名为和%*%的两个参数的函数编写 S4 方法。在使用之前,我已经为 S4 泛型方法编写了 S3 类的方法,并且我查看了包的源代码以获取灵感,但由于某种原因,我无法完全让它在这种情况下工作。 显示我的方法存在于我的目标签名中,但当我尝试使用它时,我的方法似乎从未真正被 R 调用。xymethods::setOldClass()methods::setMethod(){Matrix}methods::showMethods()

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

默认情况%*%下会删除我的类属性,我想保留它。

print(x %*% x)
Run Code Online (Sandbox Code Playgroud)
     [,1] [,2] [,3]
[1,]    1    0    0
[2,]    0    1    0
[3,]    0    0    1
Run Code Online (Sandbox Code Playgroud)

我尝试%*%为我的类创建一个方法,该方法不会删除我的类属性:

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")
# this alternate `setOldClass()` also doesn't work
# methods::setOldClass(c("atm2d", class(matrix()))) 
methods::setMethod("%*%", 
                   c(x = "atm2d", y = "atm2d"), 
                   function(x, y) matmult(x, y))
Run Code Online (Sandbox Code Playgroud)

showMethods()似乎显示创建了具有预期签名的 S4 方法:

showMethods("%*%", class = "atm2d")
Run Code Online (Sandbox Code Playgroud)
Function: %*% (package base)
x="atm2d", y="atm2d"

Run Code Online (Sandbox Code Playgroud)

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

print(x %*% x)
Run Code Online (Sandbox Code Playgroud)
     [,1] [,2] [,3]
[1,]    1    0    0
[2,]    0    1    0
[3,]    0    0    1
Run Code Online (Sandbox Code Playgroud)

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

print(matmult(x, x))
Run Code Online (Sandbox Code Playgroud)
     [,1] [,2] [,3]
[1,]    1    0    0
[2,]    0    1    0
[3,]    0    0    1
attr(,"class")
[1] "atm2d"  "matrix" "array"
Run Code Online (Sandbox Code Playgroud)

Mik*_*gan 14

%*%运算符是内部通用的,这意味着分派发生在 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;
}
Run Code Online (Sandbox Code Playgroud)

如果既不是 S4 对象,x也不是 S4 对象(如您的示例所示),则继续将它们作为传统矩阵处理,而不查看任一参数的属性。您参考的部分:ydo_matprodclasshelp("%*%")

该运算符是 S4 通用的,但不是 S3 通用的。需要为名为x和的两个参数的函数编写 S4 方法y

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

这里有两个主要问题:

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

  • %*%不是 S3 通用的,因此即使您注册了像 之类的 S3 方法%*%.zzz,它也不会被调度。

话虽如此,该%*%运算符将从 R 4.3.0 开始成为 S3 通用运算符,该运算符将于 4 月 21 日发布。您可以在新闻中找到此条

矩阵乘法运算符%*%现在是 S3 泛型,属于新组 generic matrixOps。来自 Tomasz Kalinowski 在PR#18483中的贡献。

当发生这种情况时,它将像组的其他内部通用成员%*%一样表现,因为 S3 方法将在适当的情况下调度。但是,当两个参数都不是 S4 对象时,S4 方法仍然不会被调度。+Ops%*%.zzz

可以说,setMethod当您尝试定义永远不会被调度的 S4 方法时,应该更改为发出警告或错误信号,如示例中所示。


附录

枚举泛型函数的类型和它们调度的方法的类型可能会有所帮助,从而限制对 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)
Run Code Online (Sandbox Code Playgroud)

非内部 S3 通用函数

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

source("objects.R")

h <- .__h__. <- function(x) UseMethod("h")
.S3method("h", "default", function(x) "default")

.S3method("h", "a", function(x) "a3")
setMethod("h", "c", function(x) "c4")
setMethod("h", "d", function(x) "d4")

h <- .__h__. # needed to undo side effect of 'setMethod'
h
## function(x) UseMethod("h")

h(w)
## [1] "a3"
h(x)
## [1] "default"
h(y)
## [1] "default"
h(z)
## [1] "default"
Run Code Online (Sandbox Code Playgroud)

非内部 S4 通用函数

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

source("objects.R")

h <- function(x) UseMethod("h")
.S3method("h", "default", function(x) "default")

.S3method("h", "c", function(x) "c3")
setMethod("h", "d", function(x) "d4")

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] "c3"
h(z)
## [1] "d4"
Run Code Online (Sandbox Code Playgroud)

内部通用函数

这些都在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"
Run Code Online (Sandbox Code Playgroud)

这里,调度 S3 方法。前三个结果是通过S3调度获得的。最后的结果是通过S4调度获得的。

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"
Run Code Online (Sandbox Code Playgroud)

这里,调度 S3 方法。前三个结果是通过内部定义的默认方法获得的。最后的结果是通过S4调度获得的。