Подтвердить что ты не робот

Почему для целочисленного вектора x, как (x, "numeric" ) запускает загрузку дополнительного метода S4 для принуждения?

В то время как мой вопрос связан с этим недавним, я подозреваю, что его ответ будет связан с подробными разработками объектной системы R S4.

Что бы я ожидал:

( TL;DR; - все указывает на то, что 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"

Учитывая его подпись и тот факт, что он единственный метод coerce() для преобразования в класс "numeric", Я бы ожидал, что функция, показанная выше, будет отправлена ​​вызовом 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"

Что на самом деле происходит:

( TL;DR; На самом деле вызов as(4L, "numeric") загружает и отправляет метод, который ничего не делает.)

Несмотря на то, что все признаки, упомянутые выше, as(4L, "numeric") не отправляет метод coerce() для вызовов с сигнатурой c("ANY", "numeric").

Вот несколько способов показать, что:

## (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 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 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"

Мои вопросы:

  • Почему as(4L, "numeric") не отправляет доступный метод coerce() для signature=c("ANY", "numeric")?

  • Как/почему он вместо этого добавляет новый метод в таблицу методов S4?

  • Откуда (в исходном коде R или где-нибудь еще) выполняется ли определение метода coerce() для signature=c("integer", "numeric")?

4b9b3361

Ответ 1

Я не уверен, могу ли я ответить на ваш вопрос исчерпывающе, но я попробую.

С помощью функции as() указано:

Функция "как превращает" объект в объект класса "Класс". При этом он применяет "метод принуждения", используя классы и методы S4, но несколько особым образом.

[...]

Предполагая, что "объект еще не имеет нужного класса", как сначала ищет метод в таблице методов для функции "принуждение для подписи" c (from = class (object), to = Class) таким же образом выбор метода выполнит первоначальный поиск.

[...]

Если метод не найден, 'как его ищет. Во-первых, если "класс" или "класс" (объект) является суперклассом другого, определение класса будет содержать информацию, необходимую для построения метода принуждения. В обычном случае, когда подкласс содержит суперкласс (т.е. Имеет все его слоты), метод строится либо путем извлечения или замены унаследованных слотов.

Это именно то, что вы можете увидеть, если посмотреть на код функции as() (чтобы увидеть его, введите as (без круглых скобок!) в консоль R) - см. ниже. Сначала он ищет asMethod, если он не может найти какой-либо объект, который пытается его построить, и, наконец, в конце он выполняет его:

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

Когда вы скопируете код функции as() и определите свою собственную функцию - позвоните по телефону myas() - вы можете вставить print(asMethod) выше упомянутого if (strict), чтобы получить функцию, используемую для принуждения, В этом случае вывод:

> 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. Это может быть как integer равно numeric (хотя не double) - см. Также цитату из "исторической аномалии" ниже:

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

Относительно as.numeric: Это общий метод и вызывает as.double(), поэтому он "работает" (из справки R на as.numeric):

Это историческая аномалия, что R имеет два имени для своих векторов с плавающей запятой, "double" и "numeric" (и ранее имел "реальный" ).

'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)

Мне не хватает времени, чтобы там копаться, извините.

Надеюсь, что это поможет.

Ответ 2

Рассматривая исходный код для 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 <- [email protected]

                  # Create S4 coercion method here
                  asMethod <- .makeAsMethod([email protected], [email protected], 
                    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; единственная причина использования methods связана с объектами S4.