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)

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

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.


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

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

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

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)

11

Cómo se debe hacer en el tidyverse:

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

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

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  
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.