2010-05-17 28 views
208

Tengo un código que en un lugar termina con una lista de marcos de datos que realmente quiero convertir a un solo marco de datos grande.Convierta una lista de marcos de datos en un marco de datos

Recibí algunos consejos de un earlier question que intentaba hacer algo similar pero era más complejo.

He aquí un ejemplo de lo que estoy empezando con (esto se simplifica enormemente para la ilustración):

listOfDataFrames <- vector(mode = "list", length = 100) 

for (i in 1:100) { 
    listOfDataFrames[[i]] <- data.frame(a=sample(letters, 500, rep=T), 
          b=rnorm(500), c=rnorm(500)) 
} 

Actualmente estoy usando esto:

df <- do.call("rbind", listOfDataFrames) 
+0

Véase también esta pregunta: http://stackoverflow.com/questions/2209258/merge-several-data-frames-into-one-data-frame-with-a-loop/2209371 – Shane

+12

El 'hacer .call ("rbind", list) 'idioma es lo que he usado antes también. ¿Por qué necesitas la 'lista' inicial? –

+1

Shane, yo acababa de hacer exactamente la misma prueba y entendí mi error. Eres rápido;) –

Respuesta

157

Otra opción es utilizar un plyr función:

df <- ldply(listOfDataFrames, data.frame) 

Esto es un poco más lento que el original:

> system.time({ df <- do.call("rbind", listOfDataFrames) }) 
    user system elapsed 
    0.25 0.00 0.25 
> system.time({ df2 <- ldply(listOfDataFrames, data.frame) }) 
    user system elapsed 
    0.30 0.00 0.29 
> identical(df, df2) 
[1] TRUE 

Mi conjetura es que el uso de do.call("rbind", ...) va a ser el enfoque más rápido que va a encontrar a menos que puede hacer algo como (a) utilizar un matrices en lugar de un data.frames y (b) asignar previamente la matriz final y asignarle en lugar de hacerlo crecer.

Editar 1:

Basado en el comentario de Hadley, aquí está la versión más reciente de rbind.fill desde CRAN:

> system.time({ df3 <- rbind.fill(listOfDataFrames) }) 
    user system elapsed 
    0.24 0.00 0.23 
> identical(df, df3) 
[1] TRUE 

Esto es más fácil que rbind, y marginalmente más rápido (estos tiempos se sostienen a través de múltiples carreras). Y hasta donde yo lo entiendo, the version of plyr on github es incluso más rápido que esto.

+21

rbind.fill en la última versión de plyr es considerablemente más rápido que do.call y rbind – hadley

+1

interesante. para mí rbind.fill fue el más rápido. Lo suficientemente extraño, do.call/rbind no devolvió idéntico verdadero, incluso si no podría encontrar la diferencia. Los otros dos eran iguales pero plyr era más lento. –

+0

'I()' podría reemplazar 'data.frame' en su llamada' ldply' – baptiste

80

Para completar, creo que las respuestas a esta pregunta requieren una actualización. "Creo que usar do.call("rbind", ...) va a ser el enfoque más rápido que encontrarás ..." Probablemente fue cierto para mayo de 2010 y algún tiempo después, pero en septiembre de 2011 se introdujo una nueva función rbindlist en la versión del paquete data.table 1.8.2, con una observación que "Esto hace lo mismo que do.call("rbind",l), pero mucho más rápido". ¿Cuanto más rápido?

library(rbenchmark) 
benchmark(
    do.call = do.call("rbind", listOfDataFrames), 
    plyr_rbind.fill = plyr::rbind.fill(listOfDataFrames), 
    plyr_ldply = plyr::ldply(listOfDataFrames, data.frame), 
    data.table_rbindlist = as.data.frame(data.table::rbindlist(listOfDataFrames)), 
    replications = 100, order = "relative", 
    columns=c('test','replications', 'elapsed','relative') 
) 

    test replications elapsed relative 
4 data.table_rbindlist   100 0.11 1.000 
1    do.call   100 9.39 85.364 
2  plyr_rbind.fill   100 12.08 109.818 
3   plyr_ldply   100 15.14 137.636 
+1

Muchas gracias por esto - me estaba tirando de los pelos porque mis datos los conjuntos se estaban volviendo demasiado grandes para 'completar' un montón de marcos de datos largos y fundidos.De todos modos, obtuve una aceleración increíble usando su sugerencia 'rbindlist'. – KarateSnowMachine

+7

Y uno más para completar: 'dplyr :: rbind_all (listOfDataFrames)' también hará el truco. – andyteucher

+1

¿existe un equivalente a 'rbindlist' pero que añada los marcos de datos por columna? algo así como una lista cbindlist? –

35

Existe también bind_rows(x, ...) en dplyr.

> system.time({ df.Base <- do.call("rbind", listOfDataFrames) }) 
    user system elapsed 
    0.08 0.00 0.07 
> 
> system.time({ df.dplyr <- as.data.frame(bind_rows(listOfDataFrames)) }) 
    user system elapsed 
    0.01 0.00 0.02 
> 
> identical(df.Base, df.dplyr) 
[1] TRUE 
+0

técnicamente hablando, no necesita el archivo .data - todo lo que hace es exclusivamente un data.frame, a diferencia de también un table_df (from deplyr) – user1617979

30

bind-plot

Código:

library(microbenchmark) 

dflist <- vector(length=10,mode="list") 
for(i in 1:100) 
{ 
    dflist[[i]] <- data.frame(a=runif(n=260),b=runif(n=260), 
          c=rep(LETTERS,10),d=rep(LETTERS,10)) 
} 


mb <- microbenchmark(
plyr::rbind.fill(dflist), 
dplyr::bind_rows(dflist), 
data.table::rbindlist(dflist), 
plyr::ldply(dflist,data.frame), 
do.call("rbind",dflist), 
times=1000) 

ggplot2::autoplot(mb) 

Sesión:

R version 3.3.0 (2016-05-03) 
Platform: x86_64-w64-mingw32/x64 (64-bit) 
Running under: Windows 7 x64 (build 7601) Service Pack 1 

> packageVersion("plyr") 
[1] ‘1.8.4’ 
> packageVersion("dplyr") 
[1] ‘0.5.0’ 
> packageVersion("data.table") 
[1] ‘1.9.6’ 

ACTUALIZACIÓN: Vuelva a ejecutar el 31-Ene-2018. Corrió en la misma computadora. Nuevas versiones de paquetes. Se agregó semilla para los amantes de las semillas.

enter image description here

set.seed(21) 
library(microbenchmark) 

dflist <- vector(length=10,mode="list") 
for(i in 1:100) 
{ 
    dflist[[i]] <- data.frame(a=runif(n=260),b=runif(n=260), 
          c=rep(LETTERS,10),d=rep(LETTERS,10)) 
} 


mb <- microbenchmark(
    plyr::rbind.fill(dflist), 
    dplyr::bind_rows(dflist), 
    data.table::rbindlist(dflist), 
    plyr::ldply(dflist,data.frame), 
    do.call("rbind",dflist), 
    times=1000) 

ggplot2::autoplot(mb)+theme_bw() 


R version 3.4.0 (2017-04-21) 
Platform: x86_64-w64-mingw32/x64 (64-bit) 
Running under: Windows 7 x64 (build 7601) Service Pack 1 

> packageVersion("plyr") 
[1] ‘1.8.4’ 
> packageVersion("dplyr") 
[1] ‘0.7.2’ 
> packageVersion("data.table") 
[1] ‘1.10.4’ 
+1

Esta es una gran respuesta. Ejecuté lo mismo (el mismo SO, los mismos paquetes, diferentes aleatorizaciones porque no 'set.seed') pero vi algunas diferencias en el peor de los casos. 'rbindlist' en realidad tenía el mejor peor caso y el mejor caso típico en mis resultados – C8H10N4O2

6

¿Cómo se debe hacer en el tidyverse:

df.dplyr.purrr <- listOfDataFrames %>% map_df(bind_rows) 
+1

' df_dplyr_purrr' si quieres ser un purista tidyverse ... – yeedle

+0

@yeedle Gracias - casi dejo que se deslice;) – Nick

5

Ésta es otra manera esto se puede hacer (sólo añadir a las respuestas porque reduce es una herramienta funcional muy eficaz que a menudo se pasa por alto como reemplazo de los bucles. En este caso particular, ninguno de estos es significativamente más rápido que do.call)

usando la base R:

df <- Reduce(rbind, listOfDataFrames) 

o, utilizando la tidyverse:

library(tidyverse) # or, library(dplyr); library(purrr) 
df <- listOfDataFrames %>% reduce(bind_rows) 
4

La única cosa que las soluciones con data.table faltan es la columna identificador para saber de qué trama de datos en la lista de los datos es procedente de.

Algo como esto:

df_id <- data.table::rbindlist(listOfDataFrames, idcol = TRUE) 

El parámetro idcol agrega una columna (.id) identificar el origen de la trama de datos contenida en la lista. El resultado sería algo como esto:

.id a   b   c 
1 u -0.05315128 -1.31975849 
1 b -1.00404849 1.15257952 
1 y 1.17478229 -0.91043925 
1 q -1.65488899 0.05846295 
1 c -1.43730524 0.95245909 
1 b 0.56434313 0.93813197 
2

Una actualización visual para aquellos que quieran comparar algunas de las recientes respuestas (quería comparar la purrr a dplyr solución). Básicamente, combiné las respuestas de @TheVTM y @rmf.

enter image description here

Código:

library(microbenchmark) 
library(data.table) 
library(tidyverse) 

dflist <- vector(length=10,mode="list") 
for(i in 1:100) 
{ 
    dflist[[i]] <- data.frame(a=runif(n=260),b=runif(n=260), 
          c=rep(LETTERS,10),d=rep(LETTERS,10)) 
} 


mb <- microbenchmark(
    dplyr::bind_rows(dflist), 
    data.table::rbindlist(dflist), 
    purrr::map_df(dflist, bind_rows), 
    do.call("rbind",dflist), 
    times=500) 

ggplot2::autoplot(mb) 

información de sesión:

sessionInfo() 
R version 3.4.1 (2017-06-30) 
Platform: x86_64-w64-mingw32/x64 (64-bit) 
Running under: Windows 7 x64 (build 7601) Service Pack 1 

versiones del paquete:

> packageVersion("tidyverse") 
[1] ‘1.1.1’ 
> packageVersion("data.table") 
[1] ‘1.10.0’ 
1

Use bind_rows() desde el paquete dplyr:

bind_rows(list_of_dataframes, .id = "column_label") 
Cuestiones relacionadas