r语言 - 为什么对于整数向量 x,as(x, "numeric" ) 会触发加载额外的 S4 方法进行强制?



虽然我的问题与最近的一个问题有关,但我怀疑它的答案与R的S4对象系统的详细工作有关。

我所期望的:

TLDR;-所有指示都表明as(4L, "numeric")应该分派给一个函数,该函数的主体使用as.numeric(4L)将其转换为"numeric"矢量。)

每当使用as(object, Class)将对象转换为所需的Class时,实际上就是在触发对coerce()的幕后调用。反过来,coerce()有一组方法,这些方法根据函数调用的签名被分派到——这里是它的第一个和第二个参数的类。要查看所有可用的S4 coerce()方法的列表,可以运行showMethods("coerce")

这样做表明只有一种方法可以转换为类"numeric"。这是签名为from="ANY", to="numeric":的

showMethods("coerce")
# Function: coerce (package methods)
# from="ANY", to="array"
#      ... snip ... 
# from="ANY", to="numeric"
#      ... snip ...

该方法使用as.numeric()进行转换:

getMethod("coerce", c("ANY", "numeric"))
# Method Definition:
# 
# function (from, to, strict = TRUE) 
# {
#     value <- as.numeric(from)
#     if (strict) 
#         attributes(value) <- NULL
#     value
# }
# <environment: namespace:methods>
# 
# Signatures:
#         from  to       
# target  "ANY" "numeric"
# defined "ANY" "numeric"

给定它的签名以及它是唯一用于转换为类"numeric"coerce()方法的事实,我本以为上面显示的函数就是通过调用as(4L, "numeric")来分派的函数。只有通过进行以下两项检查,这种期望才会得到加强。

## (1) There isn't (apparently!) any specific method for "integer"-->"numeric"
##     conversion
getMethod("coerce", c("integer", "numeric"))
# Error in getMethod("coerce", c("integer", "numeric")) : 
#   no method found for function 'coerce' and signature integer, numeric
## (2) This says that the "ANY"-->"numeric" method will be used for "integer"-->"numeric"
##     conversion    
selectMethod("coerce",  signature=c("integer", "numeric"))
# Method Definition:
# 
# function (from, to, strict = TRUE) 
# {
#     value <- as.numeric(from)
#     if (strict) 
#         attributes(value) <- NULL
#     value
# }
# <environment: namespace:methods>
# 
# Signatures:
#         from      to       
# target  "integer" "numeric"
# defined "ANY"     "numeric"

实际发生的情况:

TLDR;事实上,调用as(4L, "numeric")加载并调度到一个什么都不做的方法。)

不管上面提到的所有指示是什么,对于具有签名c("ANY", "numeric")的调用,as(4L, "numeric")不调度到coerce()方法。

这里有几种方法可以证明这一点:

## (1) as.numeric() would do the job, but as(..., "numeric") does not
class(as(4L, "numeric"))
#[1] "integer"
class(as.numeric(4L))
# [1] "numeric"
## (2) Tracing shows that the "generic" method isn't called
trace("coerce", signature=c("ANY", "numeric"))
as(c(FALSE, TRUE), "numeric")        ## <-- It's called for "logical" vectors
# Tracing asMethod(object) on entry   
# [1] 0 1
as(c("1", "2"), "numeric")           ## <-- and for "character" vectors
# Tracing asMethod(object) on entry   
# [1] 1 2    
as(c(1L, 2L), "numeric")             ## <-- but not for "integer" vectors 
# [1] 1 2
untrace("coerce")

那么,使用了什么方法?显然是打电话给as(4L, "numeric")将一个新的S4方法添加到coerce()的方法列表中,这就是所使用的方法
(将以下调用的结果与我们尝试第一次"integer""character"的转换。)

## At least one conversion needs to be attempted before the  
## "integer"-->"numeric" method appears.
as(4L, "numeric")  
## (1) Now the methods table shows a new "integer"-->"numeric" specific method   
showMethods("coerce")    
# Function: coerce (package methods)
# from="ANY", to="array"
#      ... snip ... 
# from="ANY", to="numeric"
#      ... snip ...
# from="integer", to="numeric"        ## <-- Here's the new method
#      ... snip ...
## (2) selectMethod now tells a different story
selectMethod("coerce",  signature=c("integer", "numeric"))
# Method Definition:
# 
# function (from, to = "numeric", strict = TRUE) 
# if (strict) {
#     class(from) <- "numeric"
#     from
# } else from
# <environment: namespace:methods>
# 
# Signatures:
#         from      to       
# target  "integer" "numeric"
# defined "integer" "numeric"

我的问题:

  1. 为什么as(4L, "numeric")没有为signature=c("ANY", "numeric")调度到可用的coerce()方法?

  2. 它是如何/为什么在S4方法表中添加新方法的?

  3. signature=c("integer", "numeric")coerce()方法的定义来自哪里(在R的源代码或其他地方)?

我不确定是否能详尽地回答你的问题,但我会尽力的。

as()功能状态的帮助:

函数"as"将"object"转换为类"class"的对象。在这样做的过程中,它应用了一个"胁迫方法",使用S4类和方法,但方式有些特殊。

[…]

假设"object"还不是所需的类,"as"首先在方法表中为签名"c(from=class(object),to=class)"的函数"converter"查找方法,方法选择将以相同的方式进行初始查找。

[…]

如果找不到任何方法,"as"将查找一个方法。首先,如果"Class"或"Class(object)"是另一个的超类,则类定义将包含构造强制方法所需的信息。在子类包含超类(即具有其所有槽)的通常情况下,该方法是通过提取或替换继承的槽来构造的。

如果您查看as()函数的代码(要查看它,请在R控制台中键入as(不带括号!)),这正是您可以看到的内容-请参阅下文。首先,它寻找一个asMethod,如果找不到,它会尝试构建一个,最后执行它:

if (strict) 
    asMethod(object)
else asMethod(object, strict = FALSE)

当你复制粘贴as()函数的代码并定义你自己的函数——让我们称之为myas()——你可以在刚才提到的if (strict)之上插入一个print(asMethod),以获得用于强制的函数。在这种情况下,输出为:

> myas(4L, 'numeric')
function (from, to = "numeric", strict = TRUE) 
if (strict) {
    class(from) <- "numeric"
    from
} else from
<environment: namespace:methods>
attr(,"target")
An object of class “signature”
     from        to 
"integer" "numeric" 
attr(,"defined")
An object of class “signature”
     from        to 
"integer" "numeric" 
attr(,"generic")
[1] "coerce"
attr(,"generic")attr(,"package")
[1] "methods"
attr(,"class")
[1] "MethodDefinition"
attr(,"class")attr(,"package")
[1] "methods"
attr(,"source")
[1] "function (from, to = "numeric", strict = TRUE) "
[2] "if (strict) {"                                    
[3] "    class(from) <- "numeric""                   
[4] "    from"                                         
[5] "} else from"                                      
[1] 4

因此,正如您所看到的(参见attr(,"source")),as(4L, 'numeric')只是将类numeric分配给输入对象并返回它

> # Snippet 1
> x = 4L
> x = as(x, 'numeric')
> # Snippet 2
> x = 4L
> class(x) <- 'numeric'

有趣的是,两者都是"一无所有"。更有趣的是,它的另一种工作方式是:

> x = 4
> class(x)
[1] "numeric"
> class(x) <- 'integer'
> class(x)
[1] "integer"

我对此并不完全确定(因为class方法似乎是在C中实现的),但我的猜测是,当分配类numeric时,它首先检查它是否已经是numeric。情况可能是integernumeric(尽管不是double)-另请参阅下面的"历史异常"引用:

> x = 4L
> class(x)
[1] "integer"
> is.numeric(x)
[1] TRUE

关于as.numeric:这是一个通用方法,调用as.double(),这就是它"工作"的原因(来自as.numeric上的R帮助):

R的浮点向量有两个名称,"double"one_answers"numeric"(以前有"real"),这是一个历史异常。

'double'是类型的名称。'numeric'是模式的名称,也是隐式类的名称。

关于问题(1)-(3):魔术发生在as函数顶部的四行:

where <- .classEnv(thisClass, mustFind = FALSE)
coerceFun <- getGeneric("coerce", where = where)
coerceMethods <- .getMethodsTable(coerceFun, environment(coerceFun), inherited = TRUE)
asMethod <- .quickCoerceSelect(thisClass, Class, coerceFun, coerceMethods, where)

对不起,我没有时间深入研究。

希望能有所帮助。

查看as()的源代码,它有两部分。(为清晰起见,源代码已缩短)。首先,它查找coerce()的现有方法,如上所述。

function (object, Class, strict = TRUE, ext = possibleExtends(thisClass, 
    Class)) 
{
    thisClass <- .class1(object)
    where <- .classEnv(thisClass, mustFind = FALSE)
    coerceFun <- getGeneric("coerce", where = where)
    coerceMethods <- .getMethodsTable(coerceFun, environment(coerceFun), 
        inherited = TRUE)
    asMethod <- .quickCoerceSelect(thisClass, Class, coerceFun, 
        coerceMethods, where)
    # No matching signatures from the coerce table!!!
    if (is.null(asMethod)) {
        sig <- c(from = thisClass, to = Class)
        asMethod <- selectMethod("coerce", sig, optional = TRUE, 
            useInherited = FALSE, fdef = coerceFun, mlist = getMethodsForDispatch(coerceFun))

如果它找不到任何方法,如本例所示,那么它将尝试创建一个新方法,如下所示:

        if (is.null(asMethod)) {
            canCache <- TRUE
            inherited <- FALSE
            # The integer vector is numeric!!!
            if (is(object, Class)) {
                ClassDef <- getClassDef(Class, where)
                if (identical(ext, FALSE)) {}
                else if (identical(ext, TRUE)) {}
                else {
                  test <- ext@test
                  # Create S4 coercion method here
                  asMethod <- .makeAsMethod(ext@coerce, ext@simple, 
                    Class, ClassDef, where)
                  canCache <- (!is(test, "function")) || identical(body(test), 
                    TRUE)
                }
            }
            if (is.null(asMethod)) {}
            else if (canCache) 
                asMethod <- .asCoerceMethod(asMethod, thisClass, 
                  ClassDef, FALSE, where)
            if (is.null(asMethod)) {}
            else if (canCache) {
                cacheMethod("coerce", sig, asMethod, fdef = coerceFun, 
                  inherited = inherited)
            }
        }
    }
    # Use newly created method on object here
    if (strict) 
        asMethod(object)
    else asMethod(object, strict = FALSE)

顺便说一句,如果你只处理基本的原子类型,我会坚持使用基本函数,避免使用methods包;使用CCD_ 54的唯一原因是处理S4对象。

最新更新