org - Una encuesta exhaustiva de los tipos de cosas en R. ''mode'' y ''class'' y ''typeof'' son insuficientes
r project (4)
El lenguaje R me confunde. Las entidades tienen modos y clases , pero incluso esto es insuficiente para describir completamente la entidad.
Esta answer dice
En R, cada ''objeto'' tiene un modo y una clase.
Entonces hice estos experimentos:
> class(3)
[1] "numeric"
> mode(3)
[1] "numeric"
> typeof(3)
[1] "double"
Muy bien hasta ahora, pero luego pasé en un vector en su lugar:
> mode(c(1,2))
[1] "numeric"
> class(c(1,2))
[1] "numeric"
> typeof(c(1,2))
[1] "double"
Eso no tiene sentido. Seguramente un vector de enteros debería tener una clase diferente, o un modo diferente, que un solo entero? Mis preguntas son:
- ¿Todo en R tiene (exactamente una) clase ?
- ¿Todo en R tiene (exactamente un) modo ?
- ¿Qué, en todo caso, nos dice ''typeof''?
- ¿Qué otra información se necesita para describir completamente una entidad? (¿Dónde está almacenada la "vectoridad", por ejemplo?)
Actualización : Aparentemente, un literal 3 es solo un vector de longitud 1. No hay escalares. OK Pero ... probé mode("string")
y obtuve "character"
, lo que me llevó a pensar que una cuerda era un vector de caracteres. Pero si eso fuera cierto, entonces esto debería ser cierto, ¡pero no lo es! c(''h'',''i'') == "hi"
Agregando a una de sus subpreguntas:
- ¿Qué otra información se necesita para describir completamente una entidad?
Además de class
, mode
, typeof
, attributes
, str
, etc., is()
también vale la pena destacar.
is(1)
[1] "numeric" "vector"
Si bien es útil, también es insatisfactorio. En este ejemplo, 1
es más que eso; también es atómico, finito y doble. La siguiente función debe mostrar todo lo que es un objeto según todas las funciones disponibles is.(...)
:
what.is <- function(x, show.all=FALSE) {
# set the warn option to -1 to temporarily ignore warnings
op <- options("warn")
options(warn = -1)
on.exit(options(op))
list.fun <- grep(methods(is), pattern = "<-", invert = TRUE, value = TRUE)
result <- data.frame(test=character(), value=character(),
warning=character(), stringsAsFactors = FALSE)
# loop over all "is.(...)" functions and store the results
for(fun in list.fun) {
res <- try(eval(call(fun,x)),silent=TRUE)
if(class(res)=="try-error") {
next() # ignore tests that yield an error
} else if (length(res)>1) {
warn <- "*Applies only to the first element of the provided object"
value <- paste(res,"*",sep="")
} else {
warn <- ""
value <- res
}
result[nrow(result)+1,] <- list(fun, value, warn)
}
# sort the results
result <- result[order(result$value,decreasing = TRUE),]
rownames(result) <- NULL
if(show.all)
return(result)
else
return(result[which(result$value=="TRUE"),])
}
Entonces ahora tenemos una imagen más completa:
> what.is(1)
test value warning
1 is.atomic TRUE
2 is.double TRUE
3 is.finite TRUE
4 is.numeric TRUE
5 is.vector TRUE
> what.is(CO2)
test value warning
1 is.data.frame TRUE
2 is.list TRUE
3 is.object TRUE
4 is.recursive TRUE
También obtienes más información con el argumento show.all=TRUE
. No pegaré ningún ejemplo aquí ya que los resultados tienen más de 50 líneas.
Finalmente, esto se entiende como una fuente de información complementaria, no como un reemplazo de ninguna de las otras funciones mencionadas anteriormente.
EDITAR
Para incluir aún más funciones "is", según el comentario de @ Erdogan, podría agregar este bit a la función:
# right after
# list.fun <- grep(methods(is), pattern = "<-", invert = TRUE, value = TRUE)
list.fun.2 <- character()
packs <- c(''base'', ''utils'', ''methods'') # include more packages if needed
for (pkg in packs) {
library(pkg, character.only = TRUE)
objects <- grep("^is.+//w$", ls(envir = as.environment(paste(''package'', pkg, sep = '':''))),
value = TRUE)
objects <- grep("<-", objects, invert = TRUE, value = TRUE)
if (length(objects) > 0)
list.fun.2 <- append(list.fun.2, objects[sapply(objects, function(x) class(eval(parse(text = x))) == "function")])
}
list.fun <- union(list.fun.1, list.fun.2)
# ...and continue with the rest
result <- data.frame(test=character(), value=character(),
warning=character(), stringsAsFactors = FALSE)
# and so on...
Aquí hay un código para determinar qué devuelven las cuatro funciones de tipo, class , mode , typeof y storage.mode para cada uno de los tipos de objetos R.
library(methods)
library(dplyr)
library(xml2)
setClass("dummy", representation(x="numeric", y="numeric"))
types <- list(
"logical vector" = logical(),
"integer vector" = integer(),
"numeric vector" = numeric(),
"complex vector" = complex(),
"character vector" = character(),
"raw vector" = raw(),
factor = factor(),
"logical matrix" = matrix(logical()),
"numeric matrix" = matrix(numeric()),
"logical array" = array(logical(8), c(2, 2, 2)),
"numeric array" = array(numeric(8), c(2, 2, 2)),
list = list(),
pairlist = .Options,
"data frame" = data.frame(),
"closure function" = identity,
"builtin function" = `+`,
"special function" = `if`,
environment = new.env(),
null = NULL,
formula = y ~ x,
expression = expression(),
call = call("identity"),
name = as.name("x"),
"paren in expression" = expression((1))[[1]],
"brace in expression" = expression({1})[[1]],
"S3 lm object" = lm(dist ~ speed, cars),
"S4 dummy object" = new("dummy", x = 1:10, y = rnorm(10)),
"external pointer" = read_xml("<foo><bar /></foo>")$node
)
type_info <- Map(
function(x, nm)
{
data_frame(
"spoken type" = nm,
class = class(x),
mode = mode(x),
typeof = typeof(x),
storage.mode = storage.mode(x)
)
},
types,
names(types)
) %>% bind_rows
knitr::kable(type_info)
Aquí está el resultado:
|spoken type |class |mode |typeof |storage.mode |
|:-------------------|:-----------|:-----------|:-----------|:------------|
|logical vector |logical |logical |logical |logical |
|integer vector |integer |numeric |integer |integer |
|numeric vector |numeric |numeric |double |double |
|complex vector |complex |complex |complex |complex |
|character vector |character |character |character |character |
|raw vector |raw |raw |raw |raw |
|factor |factor |numeric |integer |integer |
|logical matrix |matrix |logical |logical |logical |
|numeric matrix |matrix |numeric |double |double |
|logical array |array |logical |logical |logical |
|numeric array |array |numeric |double |double |
|list |list |list |list |list |
|pairlist |pairlist |pairlist |pairlist |pairlist |
|data frame |data.frame |list |list |list |
|closure function |function |function |closure |function |
|builtin function |function |function |builtin |function |
|special function |function |function |special |function |
|environment |environment |environment |environment |environment |
|null |NULL |NULL |NULL |NULL |
|formula |formula |call |language |language |
|expression |expression |expression |expression |expression |
|call |call |call |language |language |
|name |name |name |symbol |symbol |
|paren in expression |( |( |language |language |
|brace in expression |{ |call |language |language |
|S3 lm object |lm |list |list |list |
|S4 dummy object |dummy |S4 |S4 |S4 |
|external pointer |externalptr |externalptr |externalptr |externalptr |
Los tipos de objetos disponibles en R se tratan en el manual de R Language Definition . Hay algunos tipos que no se mencionan aquí: no puede probar objetos de tipo "promesa", "..." y "CUALQUIER", y "bytecode" y "weakref" solo están disponibles en el nivel C.
La tabla de tipos disponibles en la fuente R está here .
Estoy de acuerdo en que el sistema de tipos en R es bastante extraño. La razón de que sea así es que ha evolucionado durante (un largo) tiempo ...
Tenga en cuenta que omitió una función más similar a un tipo, storage.mode
y una función más similar a una clase, oldClass
.
Por lo tanto, mode
y storage.mode
son los tipos de estilo antiguo (donde storage.mode
es más preciso) y typeof
es la versión más nueva e incluso más precisa.
mode(3L) # numeric
storage.mode(3L) # integer
storage.mode(`identical`) # function
storage.mode(`if`) # function
typeof(`identical`) # closure
typeof(`if`) # special
Entonces la class
es una historia completamente diferente. class
es principalmente el atributo de class
de un objeto (eso es exactamente lo que devuelve oldClass
). Pero cuando el atributo de clase no está establecido, la función de class
constituye una clase del tipo de objeto y el atributo dim.
oldClass(3L) # NULL
class(3L) # integer
class(structure(3L, dim=1)) # array
class(structure(3L, dim=c(1,1))) # matrix
class(list()) # list
class(structure(list(1), dim=1)) # array
class(structure(list(1), dim=c(1,1))) # matrix
class(structure(list(1), dim=1, class=''foo'')) # foo
Finalmente, la clase puede devolver más de una cadena, pero solo si el atributo de clase es así. El primer valor de cadena es entonces el tipo de la clase principal, y las siguientes son de las que hereda. Las clases inventadas son siempre de longitud 1.
# Here "A" inherits from "B", which inherits from "C"
class(structure(1, class=LETTERS[1:3])) # "A" "B" "C"
# an ordered factor:
class(ordered(3:1)) # "ordered" "factor"
¿Todo en R tiene (exactamente una) clase?
Exactamente uno definitivamente no es correcto:
> x <- 3
> class(x) <- c("hi","low")
> class(x)
[1] "hi" "low"
Todo tiene (al menos una) clase.
¿Todo en R tiene (exactamente un) modo?
No estoy seguro, pero sospecho que sí.
¿Qué, en todo caso, nos dice ''typeof''?
typeof
da el tipo interno de un objeto. Los valores posibles según ?typeof
son:
El vector escribe "lógico", "entero", "doble", "complejo", "personaje", "sin formato" y "lista", "NULO", "cierre" (función), "especial" y "incorporado" ( funciones y operadores básicos), "ambiente", "S4" (algunos objetos S4) y otros que probablemente no se vean a nivel de usuario ("símbolo", "pairlist", "promesa", "idioma", "char", "...", "cualquiera", "expresión", "externalptr", "bytecode" y "weakref").
mode
basa en typeof. Desde el ?mode
Los modos tienen el mismo conjunto de nombres que tipos (vea typeof) excepto que los tipos "integer" y "double" se devuelven como "numéricos". los tipos "especial" y "incorporado" se devuelven como "función". tipo "símbolo" se llama modo "nombre". tipo "idioma" se devuelve como "(" o "llamada".
¿Qué otra información se necesita para describir completamente una entidad? (¿Dónde está almacenada la "lista", por ejemplo?)
Una lista tiene lista de clase:
> y <- list(3)
> class(y)
[1] "list"
¿Te refieres a la vectorización? length
debería ser suficiente para la mayoría de los propósitos:
> z <- 3
> class(z)
[1] "numeric"
> length(z)
[1] 1
Piense en 3
como un vector numérico de longitud 1, en lugar de como un tipo numérico primitivo.
Conclusión
Puede pasarlo bien con class
y length
. Para cuando necesites las otras cosas, probablemente no tengas que preguntar para qué sirven :-)