¿Cómo utilizar correctamente las listas en R?

320

Breve reseña: Muchos (¿la mayoría?) Lenguajes de programación contemporáneos de uso generalizado 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 mapas (una matriz desordenada que asigna claves a valores)

En el lenguaje de programación R, los dos primeros se implementan como charactery vector, respectivamente.

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

Por un lado, me pareció que el listtipo de datos de R era una implementación directa del mapa ADT ( dictionaryen Python, NSMutableDictionaryen el Objetivo C, hashen Perl y Ruby, object literalen Javascript, etc.).

Por ejemplo, los crea como si fuera un diccionario de Python, pasando pares clave-valor a un constructor (que en Python dictno lo es list):

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

Y se accede a los elementos de una lista de R al igual que lo haría con las de un diccionario de Python, por ejemplo, x['ev1']. Del mismo modo, puede recuperar solo las 'claves' o solo los 'valores' al:

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 los R listtambién son diferentes a otros ADT de tipo mapa (de todos los idiomas que he aprendido de todos modos). Mi conjetura es que esto es una consecuencia de la especificación inicial para S, es decir, una intención de diseñar un DSL de datos / estadísticas [lenguaje específico de dominio] desde cero.

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

primero , lists en R son una colección ordenada , al igual que los vectores, a pesar de que los valores están codificados (es decir, las claves pueden ser cualquier valor hashable no solo enteros secuenciales). Casi siempre, el tipo de datos de mapeo en otros idiomas no está ordenado .

segundo , lists puede ser devuelto desde funciones aunque nunca haya pasado a listcuando llamó a la función, y aunque la función que devolvió el listno contiene un listconstructor ( explícito) (Por supuesto, puede tratar esto en la práctica mediante envolviendo el resultado devuelto en una llamada a 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 R list: no parece que puedan ser miembros de otro ADT, y si intenta hacerlo, el contenedor primario se convierte en a 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 mal con la listestructura de datos o cómo se comporta. Todo lo que busco es corregir mi comprensión de cómo funcionan para poder usarlos correctamente en mi código.

Estas son las cosas que me gustaría entender mejor:

  • ¿Cuáles son las reglas que determinan cuándo una llamada de función devolverá un list(p. Ej., strsplitExpresión mencionada anteriormente)?

  • Si no asigno explícitamente nombres a list(p. Ej. list(10,20,30,40)) , ¿ Son los nombres predeterminados solo enteros secuenciales que comienzan con 1? (Asumo, pero no estoy seguro de que la respuesta sea sí, de lo contrario no podríamos obligar a este tipo lista un vector con una llamada a 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 señale la Documentación R ( ?list, R-intro): la he leído detenidamente y no me ayuda a responder el tipo de preguntas que recité anteriormente.

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

Doug
fuente
3
Con x = list(1, 2, 3, 4), ambos NO devuelven el mismo resultado: x[1]y x[[1]]. El primero devuelve una lista y el segundo devuelve un vector numérico. Al desplazarme a continuación, me parece que Dirk fue el único encuestado que respondió esta pregunta correctamente.
IRTFM
2
No noté que nadie ampliara su lista de formas listen que R no es como un hash. Tengo uno más que creo que es digno de mención. listen R puede tener dos miembros con el mismo nombre de referencia. Considere que obj <- c(list(a=1),list(a=2))es válido y devuelve una lista con dos valores con nombre de 'a'. En este caso, una solicitud obj["a"]solo devolverá el primer elemento de la lista coincidente. Puede obtener un comportamiento similar (tal vez idéntico) a un hash con solo un elemento por cada nombre de referencia utilizando entornos en R. ej .x <- new.env(); x[["a"]] <- 1; x[["a"]] <- 2; x[["a"]]
russellpierce
1
He vuelto a leer esta publicación con las respuestas tres veces en los últimos 6 meses y encontré más iluminación cada vez. Gran pregunta y algunas buenas respuestas. Gracias.
Rich Lysakowski PhD

Respuestas:

150

Sólo para hacer frente a la última parte de su pregunta, ya que realmente señala la diferencia entre una listy vectoren I:

¿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 marco de datos, etc. En este caso, ha creado dos listas diferentes. xtiene cuatro vectores, cada uno de longitud 1. x2tiene 1 vector de longitud 4:

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

Entonces estas son listas completamente diferentes.

Las listas R son muy parecidas a una estructura de datos de mapa hash en que cada valor de índice puede asociarse 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 a data.framees realmente una lista (de la data.framedocumentación):

Un marco de datos es una lista de variables del mismo número de filas con nombres de fila únicos, dada la clase '"data.frame"'

Es por eso que las columnas en un data.framepueden 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
 a   b  
[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"
Shane
fuente
44
Esto ayuda, gracias. (Por cierto, su ejemplo re 'lista complicada', como ya sabrá, es la forma estándar de replicar la declaración 'switch' en C ++, Java, etc. en lenguajes que no tienen una; probablemente sea una buena manera hacer esto en R cuando lo necesite). +1
doug
8
Correcto, aunque hay una switchfunción útil en R que se puede usar para ese propósito (ver help(switch)).
Shane
63

Con respecto a sus preguntas, permítame abordarlas en orden y dar algunos ejemplos:

1 ) Se devuelve una lista si y cuando la instrucción return 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 se establecen:

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 resulta ser un vector de cuatro elementos).

Dirk Eddelbuettel
fuente
1
Muy servicial, gracias. (Reitere el ítem # 1 en su respuesta, estoy de acuerdo, pero lo que tenía en mente eran elementos integrados como 'strsplit', no funciones creadas por el usuario). En cualquier caso, +1 de mi parte.
doug
2
@doug Acerca del elemento # 1 Creo que la única forma es consultar la ayuda para una función específica, sección Value. Como en ?strsplit: "Una lista de la misma longitud que x". Pero debe considerar que puede haber una función que devuelva diferentes valores dependiendo de los argumentos (por ejemplo, sapply puede devolver una lista o un vector).
Marek
34

Solo para tomar un subconjunto de sus preguntas:

Este artículo sobre 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, el x = list(1, 2, 3, 4)'elemento 1 es un entero único, pero x[[1]]devuelve un solo 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
JD Long
fuente
Por cierto, A = array( 11:16, c(2,3) ); A[5]es 15, en la matriz plana ?!
denis
13

Una razón por la que las listas funcionan como lo hacen (ordenadas) es para abordar la necesidad de un contenedor ordenado que pueda contener cualquier tipo en cualquier nodo, que los vectores no hacen. Las listas se reutilizan para una variedad de propósitos en R, incluida la formación de la base de a data.frame, que es una lista de vectores de tipo arbitrario (pero de 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)

Lo que obliga al vector a 1:4formar una lista.

Alex Brown
fuente
11

Solo para agregar un punto más a esto:

R tiene una estructura de datos equivalente al dict de Python en el hashpaquete . Puede leer sobre esto en esta publicación de blog del Grupo de datos abiertos . Aquí hay un ejemplo simple:

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

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

Shane
fuente
1
Conozco el paquete hash: se menciona en mi pregunta original como un proxy adecuado para el tipo hash tradicional.
Doug
También tenga en cuenta que el uso de hash :: hash es de utilidad cuestionable en relación con los entornos hash, rpubs.com/rpierce/hashBenchmarks .
russellpierce
9

Tu dices:

Por otro lado, las listas se pueden devolver de las funciones aunque nunca haya pasado una Lista cuando llamó a la función, y aunque la función no contiene 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, ya que cuando realiza la división de cadenas, tiene una lista con elementos que tienen 1 elemento de largo, por lo que sabe que x[[1]]es lo mismo que unlist(x)[1]. Pero, ¿y si el resultado de strsplitresultados devueltos de diferente longitud en cada bin. 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), se puede decir lo que la segunda "parte" de la tercera cadena era, por ejemplo: x[[3]][2]. ¿Cómo podría hacer lo mismo usando xxahora que los resultados han sido "desentrañados" ( unlist-ed)?

Steve Lianoglou
fuente
5
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)
JeremyS
fuente
4

Esta es una pregunta muy antigua, pero creo que una nueva respuesta podría agregar algo de valor ya que, en mi opinión, nadie abordó directamente algunas de las preocupaciones en el PO.

A pesar de lo que sugieren las respuestas aceptadas, los listobjetos en R no son mapas hash. Si quiere hacer un paralelo con python, listes más, supongo, python lists (o tuples en realidad).

Es mejor describir cómo la mayoría de los objetos R se almacenan internamente (el tipo C de un objeto R es SEXP). Se componen básicamente de tres partes:

  • un encabezado, que declara el tipo R del objeto, la longitud y algunos otros metadatos;
  • la parte de datos, que es una matriz estándar asignada en el montón C (bloque contiguo de memoria);
  • los atributos, que son una lista vinculada de punteros a otros objetos R (o NULLsi el objeto no tiene atributos).

Desde un punto de vista interno, hay poca diferencia entre a listy un numericvector, por ejemplo. Los valores que almacenan son simplemente diferentes. Dividamos dos objetos en el paradigma que describimos antes:

x <- runif(10)
y <- list(runif(10), runif(3))

Para x:

  • El encabezado dirá que el tipo es numeric( REALSXPen el lado C), la longitud es 10 y otras cosas.
  • La parte de datos será una matriz que contiene 10 doublevalores.
  • Los atributos son NULL, ya que el objeto no tiene ninguno.

Para y:

  • El encabezado dirá que el tipo es list( VECSXPen el lado C), la longitud es 2 y otras cosas.
  • La parte de datos será una matriz que contiene 2 punteros a dos tipos de SEXP, apuntando al valor obtenido por runif(10)y runif(3)respectivamente.
  • Los atributos son NULL, como para x.

Entonces, la única diferencia entre un numericvector y un a listes que la numericparte de datos está hecha de doublevalores, mientras que para la listparte de datos es una matriz de punteros a otros objetos R.

¿Qué pasa con los nombres? Bueno, los nombres son solo algunos de los atributos que puede asignar a un objeto. Veamos el objeto a continuación:

z <- list(a=1:3, b=LETTERS)
  • El encabezado dirá que el tipo es list( VECSXPen el lado C), la longitud es 2 y otras cosas.
  • La parte de datos será una matriz que contiene 2 punteros a dos tipos de SEXP, apuntando al valor obtenido por 1:3y LETTERSrespectivamente.
  • Los atributos ahora están presentes y son un namescomponente que es un characterobjeto R con valor c("a","b").

Desde el nivel R, puede recuperar los atributos de un objeto con la attributesfunción.

El valor clave típico de un mapa hash en R es solo una ilusión. Cuando tu dices:

z[["a"]]

esto es lo que pasa:

  • la [[función de subconjunto se llama;
  • el argumento de la función ( "a") es de tipo character, por lo que se le indica al método que busque dicho valor desde el namesatributo (si está presente) del objeto z;
  • si el namesatributo no está allí, NULLse devuelve;
  • si está presente, "a"se busca el valor en él. Si "a"no es un nombre del objeto, NULLse devuelve;
  • si está presente, se determina la posición (1 en el ejemplo). Entonces se devuelve el primer elemento de la lista, es decir, el equivalente de z[[1]].

La búsqueda de valor clave es bastante indirecta y siempre es posicional. Además, útil para tener en cuenta:

  • En los mapas hash, el único límite que debe tener una clave es que debe ser hashaable . namesen R deben ser cadenas ( charactervectores);
  • en los mapas hash no puede tener dos claves idénticas. En R, puede asignar namesa un objeto con valores repetidos. Por ejemplo:

    names(y) <- c("same", "same")

    es perfectamente válido en R. Cuando intenta y[["same"]], se recupera el primer valor. Deberías saber por qué en este punto.

En conclusión, la capacidad de otorgar atributos arbitrarios a un objeto le da la apariencia de algo diferente desde un punto de vista externo. Pero las R listno son mapas hash de ninguna manera.

nicola
fuente
2

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

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

  2. Pueden tener listas o vectores 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
    A B C D E F G H I J 
    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
    A B C D E F G H I J           
    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
       A    B    C    D    E    F    G    H    I    J                          
    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 en la pregunta del OP en sí.

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

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


En cuanto a tu segunda pregunta:

Las funciones pueden devolverse de las funciones aunque nunca haya pasado una Lista cuando llamó a la función

Las funciones devuelven diferentes tipos de datos de los que se ingresan todo el tiempo. plotdevuelve una trama aunque no tome una trama como entrada. Argdevuelve un numerica pesar de que aceptó a complex. Etc.

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

isomorfismos
fuente
2

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 expresar datos en mi mano como objeto en R o cómo seleccionar de los objetos existentes. No es fácil para un novato R pensar "en una caja R" desde el principio.

Entonces, yo mismo comencé a usar muletas a continuación, lo que me ayudó mucho a descubrir qué objeto usar para qué datos, y básicamente a imaginar el uso en el mundo real.

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

  • Vector atómico ... Llamé a esa "secuencia" para mí, sin dirección, solo secuencia del mismo tipo. [subconjuntos.
  • Vector ... secuencia con una dirección de 2D, [subconjuntos.
  • Matriz ... grupo de vectores con la misma longitud formando filas o columnas, [subconjuntos por filas y columnas, o por secuencia.
  • Matrices ... matrices en capas que forman 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 un tiempo realmente reconocí que el marco de datos es una implementación inteligente de listdonde puedo subconjunto usando [filas y columnas, pero incluso usando [[.
  • Lista ... para ayudarme a mí mismo, pensé en la lista a partir de tree structuredonde [i]selecciona y devuelve ramas enteras y [[i]]devuelve el artículo de la rama. Y debido a que es así tree like structure, incluso puede usar un index sequencepara abordar cada hoja de un complejo listusando su [[index_vector]]. Las listas pueden ser simples o muy complejas y pueden mezclar varios tipos de objetos en uno.

Entonces lists, puede terminar con más formas de seleccionar una leafsituación 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.

Petr Matousu
fuente
1

Si ayuda, tiendo a concebir las "listas" en R como "registros" en otros lenguajes pre-OO:

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

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

Francisco J. Valverde Albacete
fuente
1

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

x = list(1, 2, 3, 4)
  1. [ ]proporciona una operación de configuración secundaria. En general, el subconjunto de cualquier objeto tendrá el mismo tipo que el objeto original. Por lo tanto, x[1] proporciona una lista. Del mismo modo 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 subconfiguración como [ ].

     x[[2]] [1] 2 
    
    > x[[2:3]] Error in x[[2:3]] : subscript out of bounds
HariG
fuente