2010-05-17 5 views
208

J'ai un code qui, à un endroit, se termine par une liste de trames de données que je veux vraiment convertir en une seule grande trame de données.Convertir une liste de trames de données en une trame de données

J'ai obtenu quelques pointeurs d'un earlier question qui essayait de faire quelque chose de similaire mais plus complexe.

Voici un exemple de ce que je commence avec (ce qui est grossièrement simplifiée d'illustration):

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

J'utilise actuellement ceci:

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

Voir aussi cette question: http://stackoverflow.com/questions/2209258/merge-several-data-frames-into-one-data-frame-with-a-loop/2209371 – Shane

+12

Le 'faire .call ("rbind", list) 'idiome est ce que j'ai déjà utilisé. Pourquoi avez-vous besoin de l'initiale 'unlist'? –

+1

Shane, je venais de faire exactement le même test et j'ai attrapé ma foutaise. Vous êtes rapide;) –

Répondre

157

Une autre option est d'utiliser un plyr fonction:

df <- ldply(listOfDataFrames, data.frame) 

Ceci est un peu plus lent que l'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 

Je pense que l'utilisation do.call("rbind", ...) va être l'approche la plus rapide que vous trouverez à moins que vous pouvez faire quelque chose comme (a) utiliser une matrice au lieu d'un data.frames et (b) Préallouer la matrice finale et assignez-le plutôt que de le cultiver.

Edit 1:

Basé sur le commentaire de Hadley, voici la dernière version de rbind.fill de Cran:

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

Cela est plus facile rbind, et un peu plus rapidement (ces timings tiennent au-dessus de plusieurs fonctionne). Et autant que je le comprends, the version of plyr on github est encore plus rapide que cela.

+21

rbind.fill dans la dernière version de plyr est considérablement plus rapide que do.call et rbind – hadley

+1

intéressant. pour moi, rbind.fill était le plus rapide. Assez bizarre, do.call/rbind n'a pas retourné TRUE identique, même si je ne pouvais pas trouver une différence. Les deux autres étaient égaux mais plyr était plus lent. –

+0

'I()' pourrait remplacer 'data.frame' dans votre appel' ldply' – baptiste

80

Par souci d'exhaustivité, j'ai pensé que les réponses à cette question nécessitaient une mise à jour. "Je suppose que l'utilisation de do.call("rbind", ...) sera l'approche la plus rapide que vous trouverez ..." C'était probablement vrai pour mai 2010 et quelque temps après, mais vers septembre 2011 une nouvelle fonction rbindlist a été introduite dans la version du paquet data.table 1.8.2, avec une remarque que "Cela fait la même chose que do.call("rbind",l), mais beaucoup plus rapide". Combien plus vite?

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

Merci beaucoup pour ça - je me suis arraché les cheveux parce que mes données les ensembles devenaient trop grands pour 'ldply'ing un tas de cadres de données longs et fondus.Quoi qu'il en soit, j'ai obtenu une accélération incroyable en utilisant votre suggestion 'rbindlist'. – KarateSnowMachine

+7

Et un de plus pour l'exhaustivité: 'dplyr :: rbind_all (listOfDataFrames)' fera aussi l'affaire. – andyteucher

+1

y at-il un équivalent à 'rbindlist' mais qui ajoute les trames de données par colonne? quelque chose comme une cbindlist? –

35

Il est également bind_rows(x, ...) dans 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

techniquement parlant, vous n'avez pas besoin du fichier as.data.frame - tout ce qu'il fait le rend exclusivement un data.frame, par opposition à aussi un table_df (from deplyr) – user1617979

30

bind-plot

code:

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) 

Session:

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’ 

MISE À JOUR: Relancez 31-Jan-2018. Ran sur le même ordinateur. Nouvelles versions de paquets Graine ajoutée pour les amateurs de graines.

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

C'est une bonne réponse. J'ai couru la même chose (même système d'exploitation, mêmes paquets, randomisation différente parce que vous n'avez pas 'set.seed') mais j'ai vu des différences dans les pires performances. 'rbindlist' avait en fait le meilleur cas-pire ainsi que le meilleur cas typique dans mes résultats – C8H10N4O2

6

Comment cela doit être fait dans le tidyverse:

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

' df_dplyr_purrr' si vous voulez être un puriste tidyverse ... – yeedle

+0

@yeedle Merci - presque que celui-ci glisser;) – Nick

5

Voici une autre façon dont cela peut être fait (juste ajouter aux réponses parce que reduce est un outil fonctionnel très efficace cela est souvent négligé comme un remplacement pour les boucles.) Dans ce cas particulier, aucun d'entre eux n'est significativement plus rapide que le faire.call)

en utilisant la base R:

df <- Reduce(rbind, listOfDataFrames) 

ou, en utilisant le tidyverse:

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

La seule chose que les solutions avec data.table manquent est la colonne d'identifiant de savoir à partir de laquelle dataframe dans la liste des données est provenir de.

Quelque chose comme ceci:

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

Le paramètre idcol ajoute une colonne (.id) d'identifier l'origine de la trame de données figurant dans la liste. Le résultat ressemblerait à quelque chose comme ceci:

.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 visuel mis à jour pour ceux qui veulent comparer certaines des réponses récentes (je voulais comparer la purrr à dplyr solution). Fondamentalement, j'ai combiné les réponses de @TheVTM et @rmf.

enter image description here

Code:

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) 

Session Info:

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 

Versions de l'emballage:

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

Utilisez bind_rows() du paquet dplyr:

bind_rows(list_of_dataframes, .id = "column_label") 
Questions connexes