Convierta una lista de marcos de datos en un marco de datos

336

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

Recibí algunos consejos de una pregunta anterior que intentaba hacer algo similar pero más complejo.

Aquí hay un ejemplo de lo que estoy comenzando (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)
JD Long
fuente
También vea esta pregunta: stackoverflow.com/questions/2209258/…
Shane
27
El do.call("rbind", list)idioma es lo que he usado antes también. ¿Por qué necesitas la inicial unlist?
Dirk Eddelbuettel
55
¿Alguien puede explicarme la diferencia entre do.call ("rbind", list) y rbind (list)? ¿Por qué las salidas no son las mismas?
usuario6571411
1
@ user6571411 Porque do.call () no devuelve los argumentos uno por uno, sino que usa una lista para contener los argumentos de la función. Ver https://www.stat.berkeley.edu/~s133/Docall.html
Marjolein Fokkema

Respuestas:

131

Use bind_rows () del paquete dplyr:

bind_rows(list_of_dataframes, .id = "column_label")
joeklieg
fuente
55
Buena solución .id = "column_label"agrega los nombres de fila únicos basados ​​en los nombres de elementos de la lista.
Sibo Jiang
10
dplyrComo es 2018 y es una herramienta rápida y sólida de usar, he cambiado esto a la respuesta aceptada. ¡Los años pasan volando!
JD Long
186

Otra opción es usar una función plyr:

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

Supongo que usar do.call("rbind", ...)será el enfoque más rápido que encontrará a menos que pueda hacer algo como (a) usar matrices en lugar de data.frames y (b) preasignar la matriz final y asignarla en lugar de hacerla crecer .

Editar 1 :

Basado en el comentario de Hadley, aquí está la última versión de rbind.fillCRAN:

> 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 mantienen en varias ejecuciones). Y hasta donde yo entiendo, la versión de plyron github es aún más rápida que esto.

Shane
fuente
28
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. Por extraño que parezca, do.call / rbind no devolvió VERDADERO idéntico, incluso si no pudiera encontrar una diferencia. Los otros dos eran iguales pero el pliegue era más lento.
Matt Bannert
I()podría reemplazar data.frameen su ldplyllamada
baptiste
44
También hay melt.listen remodelación (2)
Baptiste
do.call(function(...) rbind(..., make.row.names=F), df)es útil si no desea los nombres de fila únicos generados automáticamente.
smci
111

Con el fin de completar, pensé que las respuestas a esta pregunta requerían una actualización. "Supongo que usar do.call("rbind", ...)será el enfoque más rápido que encontrará ..." Probablemente fue cierto para mayo de 2010 y algún tiempo después, pero aproximadamente en septiembre de 2011 rbindlistse introdujo una nueva función en la data.tableversión de paquete 1.8.2 , con un comentario que dice "Esto hace lo mismo 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
andrekos
fuente
3
Muchas gracias por esto: me estaba arrancando el pelo porque mis conjuntos de datos se estaban volviendo demasiado grandes para ldplyun montón de marcos de datos largos y fundidos. De todos modos, obtuve una aceleración increíble al usar su rbindlistsugerencia.
KarateSnowMachine
11
Y uno más para completar: dplyr::rbind_all(listOfDataFrames)también hará el truco.
andyteucher
2
¿hay un equivalente a rbindlistpero que agregue los marcos de datos por columna? algo así como una lista de cbindlist?
rafa.pereira
2
@ rafa.pereira Hay una solicitud de función reciente: agregar función cbindlist
Henrik
También me estaba arrancando el cabello porque do.call()había estado corriendo en una lista de marcos de datos durante 18 horas, y aún no había terminado, ¡¡¡gracias !!!
Graeme Frost
74

parcela

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 : Vuelve a ejecutar el 31 de enero de 2018. Corrió en la misma computadora. Nuevas versiones de paquetes. Semillas añadidas para los amantes de las semillas.

ingrese la descripción de la imagen aquí

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

ACTUALIZACIÓN : Vuelva a ejecutar 06-ago-2019.

ingrese la descripción de la imagen aquí

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),
  purrr::map_df(dflist,dplyr::bind_rows),
  times=1000)

ggplot2::autoplot(mb)+theme_bw()

R version 3.6.0 (2019-04-26)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 18.04.2 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/openblas/libblas.so.3
LAPACK: /usr/lib/x86_64-linux-gnu/libopenblasp-r0.2.20.so

packageVersion("plyr")
packageVersion("dplyr")
packageVersion("data.table")
packageVersion("purrr")

>> packageVersion("plyr")
[1]1.8.4
>> packageVersion("dplyr")
[1]0.8.3
>> packageVersion("data.table")
[1]1.12.2
>> packageVersion("purrr")
[1]0.3.2
rmf
fuente
2
Esta es una respuesta genial. Ejecuté lo mismo (mismo sistema operativo, mismos paquetes, diferente aleatorización porque no set.seed) pero vi algunas diferencias en el peor de los casos. rbindlisten realidad
obtuve
48

También hay 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
TheVTM
fuente
técnicamente hablando, no necesita el as.data.frame; todo lo que hace lo convierte exclusivamente en data.frame, a diferencia de también table_df (de deplyr)
usuario1617979
14

Aquí hay otra forma de hacerlo (simplemente agregándolo a las respuestas porque reducees una herramienta funcional muy efectiva que a menudo se pasa por alto como un reemplazo para los bucles. En este caso particular, ninguno de estos es significativamente más rápido que do.call)

utilizando la base R:

df <- Reduce(rbind, listOfDataFrames)

o, usando el tidyverse:

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

Cómo se debe hacer en el tidyverse:

df.dplyr.purrr <- listOfDataFrames %>% map_df(bind_rows)
Mella
fuente
3
¿Por qué usaría mapsi bind_rowspuede tomar una lista de marcos de datos?
see24
9

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

ingrese la descripción de la imagen aquí

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 la 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 de paquete:

> packageVersion("tidyverse")
[1]1.1.1> packageVersion("data.table")
[1]1.10.0
Estrella nueva
fuente
7

Lo único que data.tablefaltan las soluciones es la columna de identificador para saber de qué marco de datos en la lista provienen los datos.

Algo como esto:

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

El idcolparámetro agrega una columna ( .id) que identifica el origen del marco de datos contenido 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  
f0nzie
fuente