Cómo usar correctamente las listas en R?

Breve reseña: muchos (¿la mayoría?) Lenguajes de progtwigción contemporáneos de amplio uso tienen al menos un puñado de ADT [tipos de datos abstractos] en común, en particular,

  • cadena (una secuencia compuesta de caracteres)

  • lista (una colección ordenada de valores), y

  • tipo basado en mapa (una matriz desordenada que asigna claves a los valores)

En el lenguaje de progtwigción R, los primeros dos se implementan como character y vector , respectivamente.

Cuando comencé a aprender R, dos cosas eran obvias casi desde el principio: la list es el tipo de datos más importante en R (porque es la clase padre del R data.frame ), y segundo, simplemente no podía entender cómo funcionó, al menos no lo suficientemente bien como para usarlos correctamente en mi código.

Por un lado, me pareció que el tipo de datos de la list de R era una implementación directa del mapa ADT ( dictionary en Python, NSMutableDictionary en Objective C, hash en Perl y Ruby, object literal en Javascript, y así sucesivamente).

Por ejemplo, los crea como lo haría con un diccionario de Python, pasando pares clave-valor a un constructor (que en Python es dict no list ):

 x = list("ev1"=10, "ev2"=15, "rv"="Group 1") 

Y accede a los elementos de una Lista R tal como lo haría con los de un diccionario de Python, por ejemplo, x['ev1'] . Del mismo modo, puede recuperar solo las “claves” o solo los “valores” de la siguiente manera:

 names(x) # fetch just the 'keys' of an R list # [1] "ev1" "ev2" "rv" unlist(x) # fetch just the 'values' of an R list # ev1 ev2 rv # "10" "15" "Group 1" x = list("a"=6, "b"=9, "c"=3) sum(unlist(x)) # [1] 18 

pero las list R también se diferencian de otras ADT de tipo mapa (de entre los idiomas que he aprendido de todos modos). Supongo que esto es una consecuencia de la especificación inicial para S, es decir, la intención de diseñar una DSL de datos / estadísticas [lenguaje específico de dominio] desde cero.

tres diferencias significativas entre las list R y los tipos de mapeo en otros idiomas de uso generalizado (p. ej., Python, Perl, JavaScript):

primero , las list s en R son una colección ordenada , como los vectores, aunque los valores están codificados (es decir, las claves pueden ser cualquier valor que se pueda cargar, no solo enteros secuenciales). Casi siempre, el tipo de datos de mapeo en otros idiomas está desordenado .

en segundo lugar , la list s puede devolverse desde funciones aunque nunca haya pasado en una list cuando llamó a la función, y aunque la función que devolvió la list no contiene un constructor de list (explícito) (por supuesto, puede tratar con esto en la práctica al envolver el resultado devuelto en una llamada para unlist ):

 x = strsplit(LETTERS[1:10], "") # passing in an object of type 'character' class(x) # returns 'list', not a vector of length 2 # [1] list 

Una tercera característica peculiar de las listas de R: no parece que puedan ser miembros de otro ADT, y si intentas hacer eso, entonces el contenedor primario es forzado a una list . P.ej,

 x = c(0.5, 0.8, 0.23, list(0.5, 0.2, 0.9), recursive=TRUE) class(x) # [1] list 

mi intención aquí no es criticar el lenguaje o cómo está documentado; Del mismo modo, no estoy sugiriendo que haya algo incorrecto en la estructura de datos de la list o en cómo se comporta. Todo lo que busco es corregir mi comprensión de cómo funcionan, así puedo usarlos correctamente en mi código.

Aquí están los tipos de cosas que me gustaría entender mejor:

  • ¿Cuáles son las reglas que determinan cuándo una llamada a función devolverá una list (por ejemplo, expresión strsplit recitada arriba)?

  • Si no asigno explícitamente nombres a una list (p. Ej., list(10,20,30,40) ), ¿los nombres predeterminados son enteros secuenciales que comienzan con 1? (Supongo, pero estoy muy seguro de que la respuesta es sí, de lo contrario no podríamos forzar este tipo de list a un vector con una llamada para unlist ).

  • ¿Por qué estos dos operadores diferentes, [] y [[]] , devuelven el mismo resultado?

    x = list(1, 2, 3, 4)

    ambas expresiones devuelven “1”:

    x[1]

    x[[1]]

  • ¿Por qué estas dos expresiones no devuelven el mismo resultado?

    x = list(1, 2, 3, 4)

    x2 = list(1:4)

Por favor, no me apunte a la Documentación R ( ?list R-intro ) – Lo he leído detenidamente y no me ayuda a responder el tipo de preguntas que recité arriba.

(Por último, recientemente me enteré y comencé a usar un paquete R (disponible en CRAN) llamado hash que implementa un comportamiento convencional de tipo mapa a través de una clase S4; ciertamente puedo recomendar este paquete).

Solo para abordar la última parte de su pregunta, ya que eso realmente señala la diferencia entre una list y un vector en R:

¿Por qué estas dos expresiones no devuelven el mismo resultado?

x = lista (1, 2, 3, 4); x2 = lista (1: 4)

Una lista puede contener cualquier otra clase como cada elemento. Entonces puede tener una lista donde el primer elemento es un vector de caracteres, el segundo es un dataframe, etc. En este caso, ha creado dos listas diferentes. x tiene cuatro vectores, cada uno de longitud 1. x2 tiene 1 vector de longitud 4:

 > length(x[[1]]) [1] 1 > length(x2[[1]]) [1] 4 

Entonces estas son listas completamente diferentes.

Las listas R se parecen mucho a una estructura de datos de mapas hash porque cada valor de índice se puede asociar con cualquier objeto. Aquí hay un ejemplo simple de una lista que contiene 3 clases diferentes (incluida una función):

 > complicated.list <- list("a"=1:4, "b"=1:3, "c"=matrix(1:4, nrow=2), "d"=search) > lapply(complicated.list, class) $a [1] "integer" $b [1] "integer" $c [1] "matrix" $d [1] "function" 

Dado que el último elemento es la función de búsqueda, puedo llamarlo así:

 > complicated.list[["d"]]() [1] ".GlobalEnv" ... 

Como comentario final sobre esto: debe tenerse en cuenta que un data.frame es realmente una lista (de la documentación de data.frame ):

Un dataframe es una lista de variables del mismo número de filas con nombres de filas únicos, dada la clase ” data.frame ”.

Es por eso que las columnas en un data.frame pueden tener diferentes tipos de datos, mientras que las columnas en una matriz no pueden. Como ejemplo, aquí trato de crear una matriz con números y caracteres:

 > a <- 1:4 > class(a) [1] "integer" > b <- c("a","b","c","d") > d <- cbind(a, b) > d ab [1,] "1" "a" [2,] "2" "b" [3,] "3" "c" [4,] "4" "d" > class(d[,1]) [1] "character" 

Tenga en cuenta que no puedo cambiar el tipo de datos en la primera columna a numérico porque la segunda columna tiene caracteres:

 > d[,1] <- as.numeric(d[,1]) > class(d[,1]) [1] "character" 

En cuanto a sus preguntas, permítanme abordarlas en orden y dar algunos ejemplos:

1 ) Se devuelve una lista si y cuando la statement de devolución agrega uno. Considerar

  R> retList <- function() return(list(1,2,3,4)); class(retList()) [1] "list" R> notList <- function() return(c(1,2,3,4)); class(notList()) [1] "numeric" R> 

2 ) Los nombres simplemente no están establecidos:

 R> retList <- function() return(list(1,2,3,4)); names(retList()) NULL R> 

3 ) No devuelven lo mismo. Tu ejemplo da

 R> x <- list(1,2,3,4) R> x[1] [[1]] [1] 1 R> x[[1]] [1] 1 

donde x[1] devuelve el primer elemento de x – que es lo mismo que x . Cada escalar es un vector de longitud uno. Por otro lado, x[[1]] devuelve el primer elemento de la lista.

4 ) Por último, los dos son diferentes entre ellos crean, respectivamente, una lista que contiene cuatro escalares y una lista con un solo elemento (que pasa a ser un vector de cuatro elementos).

Solo para tomar un subconjunto de sus preguntas:

Este artículo sobre la indexación aborda la cuestión de la diferencia entre [] y [[]] .

En resumen [[]] selecciona un solo elemento de una lista y [] devuelve una lista de los elementos seleccionados. En su ejemplo, x = list(1, 2, 3, 4)' elemento 1 es un entero único pero x[[1]] devuelve un único 1 y x[1] devuelve una lista con un solo valor.

 > x = list(1, 2, 3, 4) > x[1] [[1]] [1] 1 > x[[1]] [1] 1 

Una de las razones por las que las listas funcionan tal como lo hacen (ordenadas) es abordar la necesidad de un contenedor ordenado que pueda contener cualquier tipo en cualquier nodo, y los vectores no lo hacen. Las listas se vuelven a utilizar para una variedad de propósitos en R, incluida la formación de la base de un data.frame , que es una lista de vectores de tipo arbitrario (pero con la misma longitud).

¿Por qué estas dos expresiones no devuelven el mismo resultado?

 x = list(1, 2, 3, 4); x2 = list(1:4) 

Para agregar a la respuesta de @Shane, si desea obtener el mismo resultado, intente:

 x3 = as.list(1:4) 

Que coacciona el vector 1:4 en una lista.

Solo para agregar un punto más a esto:

R tiene una estructura de datos equivalente al dict de Python en el paquete hash . Puede leer sobre esto en esta publicación de blog del Open Data Group . Aquí hay un ejemplo simple:

 > library(hash) > h <- hash( keys=c('foo','bar','baz'), values=1:3 ) > h[c('foo','bar')]  containing 2 key-value pairs. bar : 2 foo : 1 

En términos de usabilidad, la clase de hash es muy similar a una lista. Pero el rendimiento es mejor para grandes conjuntos de datos.

Tu dices:

Por otro lado, las listas pueden ser devueltas desde funciones aunque nunca pasaste en una Lista cuando llamaste a la función, y aunque la función no contenga un constructor de Lista, por ejemplo,

 x = strsplit(LETTERS[1:10], "") # passing in an object of type 'character' class(x) # => 'list' 

Y supongo que sugieres que esto es un problema (?). Estoy aquí para decirte por qué no es un problema :-). Su ejemplo es un poco simple, en el sentido de que cuando realiza el split de cadena, tiene una lista con elementos que tienen 1 elemento de longitud, por lo que sabe que x[[1]] es lo mismo que unlist(x)[1] . Pero, ¿y si el resultado de strsplit resultados de diferente longitud en cada contenedor? Simplemente devolver un vector (frente a una lista) no funcionará en absoluto.

Por ejemplo:

 stuff <- c("You, me, and dupree", "You me, and dupree", "He ran away, but not very far, and not very fast") x <- strsplit(stuff, ",") xx <- unlist(strsplit(stuff, ",")) 

En el primer caso ( x : que devuelve una lista), puede decir cuál fue la segunda "parte" de la 3ra secuencia, por ejemplo: x[[3]][2] . ¿Cómo podría hacer lo mismo utilizando xx ahora que los resultados se han "desenredado" (sin unlist -ed)?

 x = list(1, 2, 3, 4) x2 = list(1:4) all.equal(x,x2) 

no es lo mismo porque 1: 4 es lo mismo que c (1,2,3,4). Si quieres que sean iguales, entonces:

 x = list(c(1,2,3,4)) x2 = list(1:4) all.equal(x,x2) 

Respecto a los vectores y el concepto hash / array de otros idiomas:

  1. Los vectores son los átomos de R. Eg, rpois(1e4,5) (5 números aleatorios), numeric(55) (longitud-55 vector cero sobre dobles) y character(12) (12 cadenas vacías), son todos “básicos” “.

  2. Cualquiera de las listas o vectores pueden tener names .

     > n = numeric(10) > n [1] 0 0 0 0 0 0 0 0 0 0 > names(n) NULL > names(n) = LETTERS[1:10] > n ABCDEFGHIJ 0 0 0 0 0 0 0 0 0 0 
  3. Los vectores requieren que todo sea del mismo tipo de datos. Ver este:

     > i = integer(5) > v = c(n,i) > v ABCDEFGHIJ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 > class(v) [1] "numeric" > i = complex(5) > v = c(n,i) > class(v) [1] "complex" > v ABCDEFGHIJ 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 
  4. Las listas pueden contener diferentes tipos de datos, como se ve en otras respuestas y la pregunta del OP en sí.

He visto idiomas (ruby, javascript) en los que los “arreglos” pueden contener tipos de datos variables, pero por ejemplo en C ++ “matrices” deben ser del mismo tipo de datos. Creo que esto es una numeric(1e6) velocidad / eficiencia: si tiene un valor numeric(1e6) , conoce su tamaño y la ubicación de cada elemento a priori ; si la cosa puede contener "Flying Purple People Eaters" en una porción desconocida, entonces tienes que analizar realmente las cosas para conocer los hechos básicos al respecto.

Ciertas operaciones R estándar también tienen más sentido cuando el tipo está garantizado. Por ejemplo cumsum(1:9) tiene sentido, mientras que cumsum(list(1,2,3,4,5,'a',6,7,8,9)) no lo hace, sin que se garantice que el tipo es doble.


En cuanto a su segunda pregunta:

Las listas pueden devolverse desde funciones aunque nunca pasó en una Lista cuando llamó a la función

Las funciones devuelven diferentes tipos de datos de los que ingresan todo el tiempo. plot devuelve una ttwig aunque no tome una ttwig como entrada. Arg devuelve un valor numeric aunque aceptó un complex . Etc.

(Y en cuanto a strsplit : el código fuente está aquí ).

Si ayuda, tiendo a concebir “listas” en R como “registros” en otros idiomas pre-OO:

  • no hacen suposiciones sobre un tipo general (o más bien el tipo de todos los registros posibles de cualquier aridad y nombres de campo está disponible).
  • sus campos pueden ser anónimos (luego puede acceder a ellos por orden de definición estricta).

El nombre “registro” chocaría con el significado estándar de “registros” (también conocido como filas) en el lenguaje de la base de datos, y puede ser por eso que su nombre se sugirió a sí mismo: como listas (de campos).

Aunque esta es una pregunta bastante antigua, debo decir que está tocando exactamente el conocimiento que me faltaba durante mis primeros pasos en R, es decir, cómo express datos en mi mano como objeto en R o cómo seleccionar de objetos existentes. No es fácil para un principiante de R pensar “en una caja R” desde el principio.

Así que yo mismo comencé a usar muletas debajo, lo cual me ayudó mucho a descubrir qué objeto utilizar para qué datos, y básicamente a imaginar el uso del mundo real.

Aunque no estoy dando respuestas exactas a la pregunta, el texto breve a continuación podría ayudar al lector que acaba de comenzar con R y está haciendo preguntas similares.

  • Vector atómico … Llamé a esa “secuencia” por mí mismo, sin dirección, solo secuencia de los mismos tipos. [ subconjuntos.
  • Vector … secuencia con una dirección desde 2D, [ subconjuntos.
  • Matriz … grupo de vectores con la misma longitud formando filas o columnas, [ subconjuntos por filas y columnas, o por secuencia.
  • Arrays … matrices en capas formando 3D
  • Dataframe … una tabla 2D como en excel, donde puedo ordenar, agregar o eliminar filas o columnas o hacer arit. operaciones con ellos, solo después de algún tiempo realmente reconocí que dataframe es una implementación inteligente de la list donde puedo subconjuntar usando [ por filas y columnas, pero incluso usando [[ .
  • Lista … para ayudarme a mí mismo, pensé en la lista como tree structure de tree structure donde [i] selecciona y devuelve twigs enteras y [[i]] devuelve un elemento de la twig. Y debido a que es una tree like structure , incluso puede usar una index sequence para abordar cada hoja individual en una list muy compleja usando su [[index_vector]] . Las listas pueden ser simples o muy complejas y pueden mezclar varios tipos de objetos en uno solo.

Entonces, para las lists puede terminar con más formas de seleccionar una leaf dependiendo de la situación, como en el siguiente ejemplo.

 l <- list("aaa",5,list(1:3),LETTERS[1:4],matrix(1:9,3,3)) l[[c(5,4)]] # selects 4 from matrix using [[index_vector]] in list l[[5]][4] # selects 4 from matrix using sequential index in matrix l[[5]][1,2] # selects 4 from matrix using row and column in matrix 

Esta forma de pensar me ayudó mucho.

¿Por qué estos dos operadores diferentes, [ ] y [[ ]] , devuelven el mismo resultado?

 x = list(1, 2, 3, 4) 
  1. [ ] proporciona una operación de ajuste secundario. En general, el subconjunto de cualquier objeto tendrá el mismo tipo que el objeto original. Por lo tanto, x[1] proporciona una lista. De manera similar, x[1:2] es un subconjunto de la lista original, por lo tanto, es una lista. Ex.

     x[1:2] [[1]] [1] 1 [[2]] [1] 2 
  2. [[ ]] es para extraer un elemento de la lista. x[[1]] es válido y extrae el primer elemento de la lista. x[[1:2]] no es válido, ya que [[ ]] no proporciona una configuración secundaria como [ ] .

      x[[2]] [1] 2 > x[[2:3]] Error in x[[2:3]] : subscript out of bounds 
    Intereting Posts