Una encuesta exhaustiva de los tipos de cosas en R. ‘mode’ y ‘class’ y ‘typeof’ son insuficientes

El lenguaje R me confunde. Las entidades tienen modos y clases , pero incluso esto es insuficiente para describir completamente la entidad.

Esta respuesta 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"

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" 

Aquí hay un código para determinar qué devuelven las cuatro funciones de tipo, clase , modo , 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("")$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á aquí .

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... 

¿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 🙂