2010-05-17 23 views
208

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

Ho ricevuto alcuni suggerimenti da uno earlier question che cercava di fare qualcosa di simile ma più complesso.

Ecco un esempio di quello che sto iniziando con (questo è grossolanamente semplificata 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) 
+0

vedere anche questa domanda: http://stackoverflow.com/questions/2209258/merge-several-data-frames-into-one-data-frame-with-a-loop/2209371 – Shane

+12

Il 'fare .call ("rbind", list) 'idiom è quello che ho usato anche in precedenza. Perché hai bisogno della prima 'unlist'? –

+1

Shane, avevo appena fatto lo stesso identico test e mi sono beccato le palle. Sei veloce;) –

risposta

157

Un altra possibilità è quella di utilizzare un plyr funzione:

df <- ldply(listOfDataFrames, data.frame) 

Questo è un po 'più lento rispetto all'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", ...) sta per essere l'approccio più veloce che troverete a meno che non si può fare qualcosa di simile (a) utilizzare un matrici invece di un data.frames e (b) preallocare la matrice finale e assegnarlo piuttosto che coltivarlo.

Edit 1:

Basato sul commento di Hadley, ecco l'ultima versione di rbind.fill da CRAN:

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

Questo è più facile che rbind, e marginalmente più veloce (questi tempi reggere su più corre). E per quanto ho capito, the version of plyr on github è ancora più veloce di questo.

+21

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. Stranamente, do.call/rbind non ha restituito lo stesso VERO, anche se potevo trovare una differenza. Gli altri due erano uguali ma plyr era più lento. –

+0

'I()' potrebbe sostituire 'data.frame' nella tua chiamata' ldply' – baptiste

80

Ai fini della completezza, ho pensato che le risposte a questa domanda richiedessero un aggiornamento. "Immagino che usare l'do.call("rbind", ...) sarà l'approccio più veloce che troverai ..." Probabilmente era vero per maggio 2010 e qualche tempo dopo, ma a circa settembre 2011 è stata introdotta una nuova funzionenella versione del pacchetto data.table 1.8.2, con un'osservazione che "Questo fa lo stesso di 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 
+1

Grazie mille per questo - stavo tirando i capelli perché i miei dati gli insiemi stavano diventando troppo grandi per avere un sacco di frame di dati lunghi e fusi.Ad ogni modo, ho ottenuto un'incredibile accelerazione usando il tuo suggerimento 'rbindlist'. – KarateSnowMachine

+7

E un altro per completezza: 'dplyr :: rbind_all (listOfDataFrames)' farà anche il trucco. – andyteucher

+1

c'è un equivalente a 'rbindlist' ma che aggiunge i frame di dati per colonna? qualcosa come una cbindlist? –

35

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 
+0

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

30

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’ 

UPDATE: Rerun 31-gen-2018. Funzionava sullo stesso computer. Nuove versioni di pacchetti. Aggiunto seme per gli amanti delle sementi.

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

Questa è un'ottima risposta. Ho eseguito la stessa cosa (stesso sistema operativo, stessi pacchetti, randomizzazione diversa perché non si 'set.seed'), ma ho visto alcune differenze nelle prestazioni nel caso peggiore. 'rbindlist' in realtà ha avuto il peggiore dei casi peggiore e il miglior caso tipico nei miei risultati – C8H10N4O2

6

Come dovrebbe essere fatto nel tidyverse:

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

' df_dplyr_purrr' se vuoi essere un purista del tidyverse ... – yeedle

+0

@yeedle Grazie - quasi lascia che uno scivoli;) – Nick

5

Ecco un altro modo in cui questo può essere fatto (solo aggiungendolo alle risposte perché reduce è uno strumento funzionale molto efficace spesso trascurato come sostituzione dei loop. In questo caso particolare, nessuno di questi è significativamente più veloce di do.call)

utilizzando Base R:

df <- Reduce(rbind, listOfDataFrames) 

o, utilizzando il tidyverse:

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

L'unica cosa che le soluzioni con data.table mancano è la colonna identificatore di sapere da cui dataframe nella lista dei dati è proveniente da.

Qualcosa di simile a questo:

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

Il parametro idcol aggiunge una colonna (.id) identificare l'origine del dataframe contenute nell'elenco. Il risultato sarebbe guardare a qualcosa di simile:

.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

Un aggiornamento visivo per chi vuole confrontare alcune delle recenti risposte (volevo confrontare la purrr a dplyr soluzione). Fondamentalmente ho combinato le risposte da @ TheVTM e @rmf.

enter image description here

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) 

Info 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’ 
1

Utilizzare bind_rows() dal pacchetto dplyr:

bind_rows(list_of_dataframes, .id = "column_label") 
Problemi correlati