¿Por qué, para un vector entero x, hace como(x, "numérico") la carga del disparador de un método S4 adicional para forzar?
(2)
Si bien mi pregunta está relacionada con esta reciente , sospecho que su (s) respuesta (s) tendrá que ver con el funcionamiento detallado del sistema de objetos S4 de R.
Lo que esperaría:
( TLDR; - Todas las indicaciones son que as(4L, "numeric")
debería despacharse a una función cuyo cuerpo usa as.numeric(4L)
para convertirlo en un vector "numeric"
).
Cada vez que uno usa as(object, Class)
para convertir un objeto a la Class
deseada, uno realmente está activando una llamada detrás de escena para coerce()
. coerce()
, a su vez, tiene un montón de métodos que se envían en función de la firma de la llamada de función: aquí la clase de sus argumentos primero y segundo. Para ver una lista de todos los métodos S4 coerce()
, se puede ejecutar showMethods("coerce")
.
Al hacerlo, se muestra que solo hay un método para convertir a clase "numeric"
. Es el que tiene la firma from="ANY", to="numeric"
:
showMethods("coerce")
# Function: coerce (package methods)
# from="ANY", to="array"
# ... snip ...
# from="ANY", to="numeric"
# ... snip ...
Ese método usa as.numeric()
para realizar su conversión:
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"
Dada su firma, y el hecho de que es el único método de coerce()
para la conversión a la clase "numeric"
, hubiera esperado que la función que se muestra arriba fuera a la que se enviaría mediante una llamada a as(4L, "numeric")
Esa expectativa solo se refuerza ejecutando los siguientes dos controles.
## (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"
Lo que realmente sucede:
( TLDR; de hecho, llamar as(4L, "numeric")
carga y distribuye a un método que no hace nada en absoluto).
A pesar de todas las indicaciones mencionadas anteriormente, as(4L, "numeric")
no se envía al método coerce()
para llamadas con la firma c("ANY", "numeric")
.
Aquí hay algunas maneras de mostrar que:
## (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")
¿Qué método, entonces, se usa? Bueno, aparentemente el acto de llamar as(4L, "numeric")
agrega un nuevo método S4 a la lista de métodos para coerce()
, y es lo que se usa.
(Compare los resultados de las siguientes llamadas a lo que produjeron antes de que intentáramos nuestra primera conversión de "integer"
a "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"
Mis preguntas:
¿Por qué no se envía
as(4L, "numeric")
al métodocoerce()
disponible parasignature=c("ANY", "numeric")
?¿Cómo / por qué agrega un nuevo método a la tabla de métodos S4?
¿De dónde viene (en el código fuente de R o en otro lugar) la definición del método
coerce()
parasignature=c("integer", "numeric")
viene?
Mirando el código fuente para as()
, tiene dos partes. (El código fuente se ha acortado para mayor claridad). Primero, busca los métodos existentes para coerce()
, como describió anteriormente.
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))
Si no encuentra ningún método, como en este caso, intenta crear un nuevo método de la siguiente manera:
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)
Por cierto, si solo está tratando con los tipos atómicos básicos, me limitaría a las funciones básicas y evitaría el paquete de methods
; la única razón para usar methods
es tratar con objetos S4.
No estoy seguro de si puedo responder su pregunta exhaustivamente, pero lo intentaré.
La ayuda de la función as()
dice:
La función ''como'' convierte ''objeto'' en un objeto de clase ''Clase''. Al hacerlo, aplica un "método de coerción", utilizando clases y métodos S4, pero de una manera algo especial.
[...]
Suponiendo que el ''objeto'' no es ya de la clase deseada, ''como'' primero busca un método en la tabla de métodos para la función ''coaccionar'' para la firma ''c (de = clase (objeto), a = Clase)'' , de la misma manera, la selección del método haría su búsqueda inicial.
[...]
Si no se encuentra un método, ''como'' busca uno. Primero, si ''Clase'' o ''clase (objeto)'' es una superclase de la otra, la definición de clase contendrá la información necesaria para construir un método de coerción. En el caso habitual de que la subclase contenga la superclase (es decir, tenga todas sus ranuras), el método se construye extrayendo o reemplazando las ranuras heredadas.
Esto es exactamente lo que puede ver si mira el código de la función as()
(para verlo, escriba as
(sin los paréntesis!) En la consola R) - vea a continuación. Primero busca un asMethod
, si no encuentra ninguno intenta construir uno, y finalmente al final lo ejecuta:
if (strict)
asMethod(object)
else asMethod(object, strict = FALSE)
Cuando copie y pegue el código de la función as()
y defina su propia función, llamémosla myas()
, puede insertar una print(asMethod)
encima del if (strict)
acaba de mencionar para obtener la función utilizada para la coerción. En este caso, la salida es:
> 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
Entonces, como puede ver (vea attr(,"source")
), el as(4L, ''numeric'')
simplemente asigna un valor numeric
clase al objeto de entrada y lo devuelve. Por lo tanto, los siguientes dos fragmentos son equivalentes (para este caso):
> # Snippet 1
> x = 4L
> x = as(x, ''numeric'')
> # Snippet 2
> x = 4L
> class(x) <- ''numeric''
Curiosamente, tanto para ''nada''. Más interesante, al revés, funciona:
> x = 4
> class(x)
[1] "numeric"
> class(x) <- ''integer''
> class(x)
[1] "integer"
No estoy exactamente seguro de esto (ya que el método de class
parece implementarse en C
), pero creo que al asignar el valor numeric
clase, primero se verifica si ya es numeric
. Lo cual podría ser el caso ya que el integer
es numeric
(aunque no double
); consulte también la cita de "anomalía histórica" a continuación:
> x = 4L
> class(x)
[1] "integer"
> is.numeric(x)
[1] TRUE
Con respecto a as.numeric
: este es un método genérico y llama a as.double()
, que es la razón por la que ''funciona'' (de la ayuda de as.numeric
en as.numeric
):
Es una anomalía histórica que R tenga dos nombres para sus vectores de coma flotante, ''doble'' y ''numérico'' (y anteriormente tenía ''real'').
''doble'' es el nombre del tipo. ''numérico'' es el nombre del modo y también de la clase implícita.
Con respecto a las preguntas (1) - (3): La magia ocurre en esas cuatro líneas en la parte superior de la función 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)
Me falta el tiempo para cavar allí, lo siento.
Espero que ayude.