R语言 调试意外的S4方法调度

djp7away  于 2023-04-09  发布在  其他
关注(0)|答案(1)|浏览(201)

我试图将一个S4对象(Matrix包中dgCMatrix类的稀疏矩阵)传递给另一个包(Wrench)中的一个函数。我得到了一些非常奇怪的行为,我很难理解。我怀疑这与R中的S3/S4方法调度有关,这是一个我不太理解的主题。
设置示例:

library(Matrix)
library(Wrench)

M <- Matrix(10 + 1:28, 4, 7)
M[, c(2,4:6)] <- 0
sM <- as(M, "sparseMatrix")
print(sM)
# 4 x 7 sparse Matrix of class "dgCMatrix"
#                     
# [1,] 11 . 19 . . . 35
# [2,] 12 . 20 . . . 36
# [3,] 13 . 21 . . . 37
# [4,] 14 . 22 . . . 38

print(rowSums(sM))
# [1] 65 68 71 74

接下来,我调用函数Wrench::wrench来进行分析:

wrench(sM, colData=c('A','B','A','B','B','A','B'))
# Error in h(simpleError(msg, call)) : 
#   error in evaluating the argument 'i' in selecting a method for function '[': 'x' must be an array of at least two dimensions

奇怪...我在RStudio中的那一行设置了一个断点,进入wrench,它开始如下:

function (mat, condition, etype = "w.marg.mean", ebcf = TRUE, 
  z.adj = FALSE, phi.adj = TRUE, detrend = FALSE, ...) 
{
  mat <- mat[rowSums(mat) > 0, ]
  # ...

进入第一行,进入对rowSums(mat)的调用,发现自己在base::rowSums中。

function (x, na.rm = FALSE, dims = 1L) 
{
  if (is.data.frame(x)) 
    x <- as.matrix(x)
  if (!is.array(x) || length(dn <- dim(x)) < 2L) 
    stop("'x' must be an array of at least two dimensions")

is.data.frame(x)FALSEis.array(x)是FALSE,调用了stop()函数,错误就来自于此。好的,所以base::rowSums不知道如何处理稀疏矩阵,并引发了错误...

但为什么在wrench中调用rowSums(mat)时会调用base::rowSums,而在外部作用域中调用rowSums(sM)时会调用Matrix::rowSums.dgCMatrix,并且一切正常?
更一般地说,我如何调试这类问题,方法调度没有按预期发生(特别是在我无法控制的包中)?

我尝试过的其他事情:

  • 我尝试使用debugcall,即外部作用域中的eval(debugcall(rowSums(sM))),它将我带到Matrix中的某个地方,调用一些C代码,最终打印行和。当我在Wrench::wrench中暂停调试器时尝试同样的事情时,我得到一个错误:
Browse[1]> eval(debugcall(rowSums(mat))) 
Error in .signatureFromCall(func, mcall, env) :    
trying to get slot "signature" from an object of a basic class ("function") with no slots
jdzmm42g

jdzmm42g1#

您的NAMESPACE需要

import(Matrix)

importFrom("Matrix", ...)
importClassesFrom("Matrix", dgCMatrix, ...)
importMethodsFrom("Matrix", "[", rowSums, ...)

后者仅在必要时导入(鼓励您遵循此做法)。
至于调试,请注意,有几个附件将函数wrench的求值环境与其调用环境(在本例中是全局环境)分开:

1. the evaluation environment of 'wrench'
2. the 'Wrench' namespace
3. the 'Wrench' imports
4. the 'base' namespace
5. the calling environment of 'wrench'

您可以通过递归调用parent.env来验证,如下所示:

> library(Wrench)
> debugonce(wrench)
> wrench()
Browse[2]> e <- environment()
Browse[2]> repeat { print(e); if (identical(e, parent.frame())) break; e <- parent.env(e) }
debug at #1: print(e)
Browse[3]> c
<environment: 0x11e032028>
<environment: namespace:Wrench>
<environment: 0x11e84bf40>
attr(,"name")
[1] "imports:Wrench"
<environment: namespace:base>
<environment: R_GlobalEnv>
Browse[2]>

当您从Matrix导入rowSums的方法时,S4泛型rowSums会被放置在您的包导入中。在base命名空间中,泛型rowSums会在非泛型rowSums之前找到,因为您的包导入会首先被搜索。调用时,泛型rowSums从对应的方法表中分派一个合适的方法。如果找不到,则调用默认方法,对于rowSums,该方法只是非泛型函数。

Browse[2]> rowSums
standardGeneric for "rowSums" defined from package "base"

function (x, na.rm = FALSE, dims = 1, ...) 
standardGeneric("rowSums")
<bytecode: 0x1182be180>
<environment: 0x1182b78f8>
Methods may be defined for arguments: x
Use  showMethods(rowSums)  for currently available ones.
Browse[2]> environment(rowSums)$.MTable$CsparseMatrix
Method Definition:

function (x, na.rm = FALSE, dims = 1L, ...) 
{
    .local <- function (x, na.rm = FALSE, dims = 1L, sparseResult = FALSE) 
    .Call("CRsparse_rowSums", x, na.rm, FALSE, sparseResult)
    .local(x, na.rm, dims, ...)
}
<bytecode: 0x11b88dd60>
<environment: namespace:Matrix>

Signatures:
        x              
target  "CsparseMatrix"
defined "CsparseMatrix"
Browse[2]> rowSums@default
Method Definition (Class "derivedDefaultMethod"):

function (x, na.rm = FALSE, dims = 1, ...) 
base::rowSums(x, na.rm = na.rm, dims = dims, ...)
<environment: 0x118261ff8>

Signatures:
        x    
target  "ANY"
defined "ANY"

重要的是,如果WrenchMatrix导入,则加载Wrench会自动加载Matrix,因此Matrix导出的rowSums的方法在方法表中始终可用,供Wrench中的函数使用。
这个例子的一个微妙的方面是,[运算符,与rowSums不同,是 * 内部 * S4泛型。当您导入[的方法时,S4泛型[ * 不会 * 放置在您的包导入中,但S4调度无论如何都可以工作。
所以理论上你可以不用从Matrix导入[的方法,只要你从Matrix导入一些东西,以确保它的方法在加载Wrench时被加载。为了清楚起见,do 使用NAMESPACE来导入你的包可能需要的所有方法。

相关问题