2010-12-22 9 views
42

Muy a menudo quiero convertir una lista en la que cada índice tenga tipos de elementos idénticos a un marco de datos. Por ejemplo, puede tener una lista:¿Cuál es la manera más eficiente de lanzar una lista como un marco de datos?

> my.list 
[[1]] 
[[1]]$global_stdev_ppb 
[1] 24267673 

[[1]]$range 
[1] 0.03114799 

[[1]]$tok 
[1] "hello" 

[[1]]$global_freq_ppb 
[1] 211592.6 


[[2]] 
[[2]]$global_stdev_ppb 
[1] 11561448 

[[2]]$range 
[1] 0.08870838 

[[2]]$tok 
[1] "world" 

[[2]]$global_freq_ppb 
[1] 1002043 

Quiero convertir esta lista para una trama de datos, donde cada elemento de índice es una columna. Lo natural (para mí) para ir es a es utilizar do.call: lo suficientemente

> my.matrix<-do.call("rbind", my.list) 
> my.matrix 
    global_stdev_ppb range  tok  global_freq_ppb 
[1,] 24267673   0.03114799 "hello" 211592.6  
[2,] 11561448   0.08870838 "world" 1002043 

sencillo, pero cuando intento jugar esta matriz como una trama de datos, las columnas siguen siendo elementos de la lista, en lugar de vectores:

> my.df<-as.data.frame(my.matrix, stringsAsFactors=FALSE) 
> my.df[,1] 
[[1]] 
[1] 24267673 

[[2]] 
[1] 11561448 

Actualmente, para obtener el molde trama de datos adecuadamente estoy interactuando sobre cada columna utilizando unlist y as.vector, a continuación, se refunde la trama de datos, tales como:

new.list<-lapply(1:ncol(my.matrix), function(x) as.vector(unlist(my.matrix[,x]))) 
my.df<-as.data.frame(do.call(cbind, new.list), stringsAsFactors=FALSE) 

Esto, sin embargo, parece muy ineficiente. ¿Hay alguna forma mejor de hacer esto?

+1

Véase '? Data.table :: rbindlist' – marbel

+0

A partir de 2017 se debe utilizar' your_list%>% REDUCIR (bind_rows) '' de 'purrr' – Zafar

Respuesta

47

Creo que desee:

> do.call(rbind, lapply(my.list, data.frame, stringsAsFactors=FALSE)) 
    global_stdev_ppb  range tok global_freq_ppb 
1   24267673 0.03114799 hello  211592.6 
2   11561448 0.08870838 world  1002043.0 
> str(do.call(rbind, lapply(my.list, data.frame, stringsAsFactors=FALSE))) 
'data.frame': 2 obs. of 4 variables: 
$ global_stdev_ppb: num 24267673 11561448 
$ range   : num 0.0311 0.0887 
$ tok    : chr "hello" "world" 
$ global_freq_ppb : num 211593 1002043 
+10

plyr :: rbind.fill' es a menudo un poco más rápido que 'rbind.fill', y toda la operación es equivalente a' plyr :: ldply (my.list, data.frame) ' – hadley

17

no se puede decir que esto es el "más eficiente" en términos de memoria o la velocidad, pero es bastante eficiente en términos de codificación:

my.df <- do.call("rbind", lapply(my.list, data.frame)) 

la lapply() paso con data.frame() convierte cada elemento de la lista en una trama de datos de una sola fila que entonces actúa agradable con rbind()

31

Otra opción es:

data.frame(t(sapply(mylist, `[`))) 

pero este simple manipulación de los resultados en una trama de datos de listas:

> str(data.frame(t(sapply(mylist, `[`)))) 
'data.frame': 2 obs. of 3 variables: 
$ a:List of 2 
    ..$ : num 1 
    ..$ : num 2 
$ b:List of 2 
    ..$ : num 2 
    ..$ : num 3 
$ c:List of 2 
    ..$ : chr "a" 
    ..$ : chr "b" 

Una alternativa a esto, a lo largo de las mismas líneas, pero ahora el resultado mismo que las otras soluciones, es:

data.frame(lapply(data.frame(t(sapply(mylist, `[`))), unlist)) 

[Edit: incluido los tiempos de las dos soluciones de @Martin Morgan, que tienen la ventaja sobre la otra solución que devuelve un marco de datos de vectores.] Algunos tiempos representativos en un muy problema simple:

mylist <- list(list(a = 1, b = 2, c = "a"), list(a = 2, b = 3, c = "b")) 

> ## @Joshua Ulrich's solution: 
> system.time(replicate(1000, do.call(rbind, lapply(mylist, data.frame, 
+          stringsAsFactors=FALSE)))) 
    user system elapsed 
    1.740 0.001 1.750 

> ## @JD Long's solution: 
> system.time(replicate(1000, do.call(rbind, lapply(mylist, data.frame)))) 
    user system elapsed 
    2.308 0.002 2.339 

> ## my sapply solution No.1: 
> system.time(replicate(1000, data.frame(t(sapply(mylist, `[`))))) 
    user system elapsed 
    0.296 0.000 0.301 

> ## my sapply solution No.2: 
> system.time(replicate(1000, data.frame(lapply(data.frame(t(sapply(mylist, `[`))), 
+            unlist)))) 
    user system elapsed 
    1.067 0.001 1.091 

> ## @Martin Morgan's Map() sapply() solution: 
> f = function(x) function(i) sapply(x, `[[`, i) 
> system.time(replicate(1000, as.data.frame(Map(f(mylist), names(mylist[[1]]))))) 
    user system elapsed 
    0.775 0.000 0.778 

> ## @Martin Morgan's Map() lapply() unlist() solution: 
> f = function(x) function(i) unlist(lapply(x, `[[`, i), use.names=FALSE) 
> system.time(replicate(1000, as.data.frame(Map(f(mylist), names(mylist[[1]]))))) 
    user system elapsed 
    0.653 0.000 0.658 
+0

Hrm .. el uso de' replicate() 'en esta respuesta es un poco raro. Está probando qué tan eficiente es convertir una pequeña lista en un marco de datos muchas veces. Parece que raramente sería útil. ¿No tendría más sentido probar la eficacia de la conversión de una * gran * lista de listas? – naught101

+0

@ naught101 posiblemente; usted tiene el código, pruébelo ;-) (Informe los resultados --- puede editarlos en mi Respuesta si lo desea) –

+0

@ naught101 Tengo un [means] (http://stackoverflow.com/questions/26534438/intradataframe-analysis-creating-a-derivative-data-frame-from-another-data-fram/26535386) de crear una lista de este tipo, dado que alguien tiene un marco de datos grande para crunching de los números. – jxramos

13

Este

f = function(x) function(i) sapply(x, `[[`, i) 

es una función que devuelve una función que extrae el elemento i-ésimo de x. Entonces

Map(f(mylist), names(mylist[[1]])) 

recibe un nombre (gracias Mapa!) Lista de los vectores que se pueden hacer en una trama de datos

as.data.frame(Map(f(mylist), names(mylist[[1]]))) 

Para la velocidad por lo general es más rápido usar unlist(lapply(...), use.names=FALSE) como

f = function(x) function(i) unlist(lapply(x, `[[`, i), use.names=FALSE) 

Una variante más general es

f = function(X, FUN) function(...) sapply(X, FUN, ...) 

Cuando hacer la estructuras listas de listas vienen? ¿Tal vez hay un paso anterior donde una iteración podría ser reemplazada por algo más vectorizado?

+2

+1 para la ilustración de 'Mapa'. Necesito incorporar 'Map',' Reduce' et. al en mis rutinas diarias ... –

+0

¿Cómo se usan estas cosas? La versión 'as.data.frame (Map (f (mylist), names (mylist)))' no me funciona para el tipo de datos que utilicé @DrewConway y utilicé porque las listas no tienen nombres; Obtengo esto devuelto en su lugar 'marco de datos con 0 columnas y 0 filas'. Incluso con nombres, no puedo hacer que esto funcione para 'mylist' en mi respuesta. Estoy realmente curioso ya que no he usado 'Map' et al en absoluto, así que estoy interesado en cómo funcionan, qué hacen, cuándo implementar mejor, etc. –

+0

Vaya, deberían haber sido nombres (mi lista [[1] ]), es decir, obtener los nombres de los subelementos del primer elemento. –

14

Aunque esta pregunta ya ha sido contestada, vale la pena señalar el paquete data.table tiene rbindlist que lleva a cabo esta tarea muy rápidamente:

library(microbenchmark) 
library(data.table) 
l <- replicate(1E4, list(a=runif(1), b=runif(1), c=runif(1)), simplify=FALSE) 

microbenchmark(times=5, 
    R=as.data.frame(Map(f(l), names(l[[1]]))), 
    dt=data.frame(rbindlist(l)) 
) 

me

Unit: milliseconds 
expr  min  lq median  uq  max neval 
    R 31.060119 31.403943 32.278537 32.370004 33.932700  5 
    dt 2.271059 2.273157 2.600976 2.635001 2.729421  5 
2

da el paquete dplyr de bind_rows es eficiente.

one <- mtcars[1:4, ] 
two <- mtcars[11:14, ] 
system.time(dplyr::bind_rows(one, two)) 
    user system elapsed 
    0.001 0.000 0.001 
Cuestiones relacionadas