Converti un elenco di frame di dati in un frame di dati


336

Ho un codice che in un punto finisce con un elenco di frame di dati che voglio davvero convertire in un singolo frame di dati di grandi dimensioni.

Ho ricevuto alcuni suggerimenti da una domanda precedente che stava cercando di fare qualcosa di simile ma più complesso.

Ecco un esempio di ciò che sto iniziando (questo è grossolanamente semplificato per l'illustrazione):

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

Attualmente sto usando questo:

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

Vedi anche questa domanda: stackoverflow.com/questions/2209258/…
Shane

27
Il do.call("rbind", list)linguaggio è quello che ho usato anche prima. Perché hai bisogno dell'iniziale unlist?
Dirk Eddelbuettel,

5
qualcuno può spiegarmi la differenza tra do.call ("rbind", list) e rbind (list) - perché gli output non sono gli stessi?
user6571411

1
@ user6571411 Perché do.call () non restituisce gli argomenti uno a uno, ma utilizza un elenco per contenere gli argomenti della funzione. Vedi https://www.stat.berkeley.edu/~s133/Docall.html
Marjolein Fokkema

Risposte:


131

Usa bind_rows () dal pacchetto dplyr:

bind_rows(list_of_dataframes, .id = "column_label")

5
Bella soluzione. .id = "column_label"aggiunge i nomi di riga univoci in base ai nomi degli elementi dell'elenco.
Sibo Jiang,

10
da quando è il 2018 ed dplyrè allo stesso tempo veloce e solido strumento da utilizzare, ho cambiato questo nella risposta accettata. Gli anni volano!
JD Long

186

Un'altra opzione è quella di utilizzare una funzione plyr:

df <- ldply(listOfDataFrames, data.frame)

Questo è un po 'più lento dell'originale:

> 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

La mia ipotesi è che l'utilizzo do.call("rbind", ...)sarà l'approccio più veloce che troverai a meno che tu non possa fare qualcosa come (a) usare una matrice al posto di un data.frames e (b) preallocare la matrice finale e assegnarla invece di farla crescere .

Modifica 1 :

Sulla base del commento di Hadley, ecco l'ultima versione di rbind.fillCRAN:

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

Questo è più facile di rbind e leggermente più veloce (questi tempi resistono su più corse). E per quanto ho capito, la versione di plyrsu github è persino più veloce di così.


28
rbind.fill nell'ultima versione di plyr è notevolmente più veloce di do.call e rbind
hadley,

1
interessante. per me rbind.fill è stato il più veloce. Abbastanza strano, do.call / rbind non ha restituito VERO identico, anche se non sono riuscito a trovare una differenza. Gli altri due erano uguali ma Plyr era più lento.
Matt Bannert,

I()potrebbe sostituire data.framenella tua ldplychiamata
battista

4
c'è anche melt.listin reshape (2)
battezzare il

do.call(function(...) rbind(..., make.row.names=F), df)è utile se non desideri i rownames univoci generati automaticamente.
smci,

111

Ai fini della completezza, ho pensato che le risposte a questa domanda richiedessero un aggiornamento. "La mia ipotesi è che l'utilizzo do.call("rbind", ...)sarà l'approccio più veloce che troverete ..." Probabilmente era vero per maggio 2010 e qualche tempo dopo, ma a circa settembre 2011 è rbindliststata introdotta una nuova funzione nella data.tableversione del pacchetto 1.8.2 , con un'osservazione che "Questo fa lo stesso do.call("rbind",l), ma molto più veloce". Quanto più veloce?

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
Grazie mille per questo - mi stavo togliendo i capelli perché i miei set di dati stavano diventando troppo grandi per ldplyinging un gruppo di lunghi e fusi frame di dati. Ad ogni modo, ho ottenuto un incredibile speedup usando il tuo rbindlistsuggerimento.
KarateSnowMachine,

11
E ancora uno per completezza: dplyr::rbind_all(listOfDataFrames)farà anche il trucco.
Andyteucher,

2
esiste un equivalente rbindlistma che aggiunge i frame di dati per colonna? qualcosa come una cbindlist?
rafa.pereira,

2
@ rafa.pereira Esiste una recente richiesta di funzionalità: aggiungi funzione cbindlist
Henrik

Mi stavo anche togliendo i capelli perché do.call()avevo funzionato su un elenco di frame di dati per 18 ore e non avevo ancora finito, grazie !!!
Graeme Frost,

74

bind-plot

Codice:

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)

Sessione:

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

AGGIORNAMENTO : rieseguire il 31-gen-2018. Ho funzionato sullo stesso computer. Nuove versioni di pacchetti. Aggiunto seme per gli amanti dei semi.

inserisci qui la descrizione dell'immagine

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

AGGIORNAMENTO : rieseguire il 06-ago-2019.

inserisci qui la descrizione dell'immagine

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
Questa è un'ottima risposta Ho eseguito la stessa cosa (stesso sistema operativo, stessi pacchetti, randomizzazione diversa perché non lo fai set.seed) ma ho visto alcune differenze nelle prestazioni nel caso peggiore. rbindlistin realtà ho avuto il miglior caso peggiore e il miglior caso tipico nei miei risultati
C8H10N4O2

48

C'è anche bind_rows(x, ...)in 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

tecnicamente parlando non hai bisogno di as.data.frame - tutto ciò che lo rende esclusivamente un data.frame, al contrario di un table_df (da deplyr)
user1617979

14

Ecco un altro modo per farlo (basta aggiungerlo alle risposte perché reduceè uno strumento funzionale molto efficace che viene spesso trascurato in sostituzione dei loop. In questo caso particolare, nessuno dei due è significativamente più veloce di do.call)

usando la base R:

df <- Reduce(rbind, listOfDataFrames)

oppure, usando il tidyverse:

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

11

Come dovrebbe essere fatto in ordine:

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

3
Perché dovresti usare mapse bind_rowspuoi prendere un elenco di frame di dati?
vedi24,

9

Una grafica aggiornata per coloro che vogliono confrontare alcune delle risposte recenti (volevo confrontare la soluzione da purrr a dplyr). Fondamentalmente ho combinato le risposte di @TheVTM e @rmf.

inserisci qui la descrizione dell'immagine

Codice:

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)

Informazioni sulla sessione:

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

Versioni del pacchetto:

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

7

L'unica cosa che data.tablemanca alle soluzioni è la colonna identificatore per sapere da quale frame di dati nell'elenco provengono i dati.

Qualcosa come questo:

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

Il idcolparametro aggiunge una colonna ( .id) che identifica l'origine del frame di dati contenuto nell'elenco. Il risultato sarebbe simile a questo:

.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  
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.