Convertir une liste de trames de données en une seule trame de données

336

J'ai du code qui à un endroit se retrouve avec une liste de trames de données que je veux vraiment convertir en une seule trame de Big Data.

J'ai reçu quelques conseils d'une question précédente qui essayait de faire quelque chose de similaire mais de plus complexe.

Voici un exemple de ce que je commence (c'est très simplifié à titre 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)
JD Long
la source
Voir aussi cette question: stackoverflow.com/questions/2209258/…
Shane
27
L' do.call("rbind", list)idiome est ce que j'ai également utilisé auparavant. Pourquoi avez-vous besoin de l'initiale unlist?
Dirk Eddelbuettel
5
quelqu'un peut-il m'expliquer la différence entre do.call ("rbind", list) et rbind (list) - pourquoi les sorties ne sont-elles pas les mêmes?
user6571411
1
@ user6571411 Parce que do.call () ne renvoie pas les arguments un par un, mais utilise une liste pour contenir les arguments de la fonction. Voir https://www.stat.berkeley.edu/~s133/Docall.html
Marjolein Fokkema

Réponses:

131

Utilisez bind_rows () du package dplyr:

bind_rows(list_of_dataframes, .id = "column_label")
joeklieg
la source
5
Belle solution. .id = "column_label"ajoute les noms de lignes uniques en fonction des noms des éléments de la liste.
Sibo Jiang
10
depuis 2018 et dplyrest à la fois rapide et un outil solide à utiliser, j'ai changé cela en réponse acceptée. Les années passent!
JD Long
186

Une autre option consiste à utiliser une fonction plyr:

df <- ldply(listOfDataFrames, data.frame)

C'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 suppose que l'utilisation do.call("rbind", ...)sera l'approche la plus rapide que vous trouverez, à moins que vous ne puissiez faire quelque chose comme (a) utiliser des matrices au lieu d'un data.frames et (b) préallouer la matrice finale et l'affecter plutôt que de la développer .

Modifier 1 :

D'après le commentaire de Hadley, voici la dernière version de rbind.fillCRAN:

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

C'est plus facile que rbind, et légèrement plus rapide (ces timings tiennent le coup sur plusieurs exécutions). Et pour autant que je le comprends, la version de plyron github est encore plus rapide que cela.

Shane
la source
28
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 a été le plus rapide. Assez étrange, do.call / rbind n'a pas retourné VRAI identique, même si je ne pouvais pas trouver de différence. Les deux autres étaient égaux mais le plyr était plus lent.
Matt Bannert
I()pourrait remplacer data.framedans votre ldplyappel
baptiste
4
il y a aussi une melt.listrefonte (2)
baptiste
do.call(function(...) rbind(..., make.row.names=F), df)est utile si vous ne voulez pas les noms de domaine uniques générés automatiquement.
smci
111

Dans un souci d'exhaustivité, j'ai pensé que les réponses à cette question nécessitaient une mise à jour. "Je suppose que l'utilisation do.call("rbind", ...)va être 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 a rbindlistété introduite dans la data.tableversion 1.8.2 du paquet. , avec une remarque que "Cela fait la même chose que do.call("rbind",l), mais beaucoup plus rapidement". Combien plus rapide?

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
la source
3
Merci beaucoup pour cela - je me tirais les cheveux parce que mes ensembles de données devenaient trop gros pour ldplycontenir un tas de trames de données longues et fondues. Quoi qu'il en soit, j'ai obtenu une accélération incroyable en utilisant votre rbindlistsuggestion.
KarateSnowMachine
11
Et un de plus pour être complet: dplyr::rbind_all(listOfDataFrames)fera aussi l'affaire.
andyteucher
2
y a-t-il un équivalent rbindlistmais qui ajoute les trames de données par colonne? quelque chose comme une cbindlist?
rafa.pereira
2
@ rafa.pereira Il y a une demande de fonctionnalité récente: ajouter une fonction cbindlist
Henrik
Je tirais aussi mes cheveux parce que je do.call()courais sur une liste de trames de données depuis 18 heures, et que je n'avais toujours pas fini, merci !!!
Graeme Frost
74

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 le 31 janvier 2018. A couru sur le même ordinateur. Nouvelles versions de packages. Ajout de graines pour les amateurs de graines.

entrez la description de l'image ici

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

MISE À JOUR : Relancez le 6 août 2019.

entrez la description de l'image ici

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
la source
2
C'est une excellente réponse. J'ai exécuté la même chose (même système d'exploitation, mêmes packages, randomisation différente parce que vous ne le faites pas set.seed), mais j'ai vu des différences dans les performances les plus défavorables. rbindlistavait en fait le meilleur cas le plus défavorable ainsi que le meilleur cas typique dans mes résultats
C8H10N4O2
48

Il y a aussi 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
TheVTM
la source
techniquement parlant, vous n'avez pas besoin du as.data.frame - tout ce qu'il fait en fait exclusivement un data.frame, par opposition également à un table_df (de deplyr)
user1617979
14

Voici une autre façon de procéder (ajoutez-le simplement aux réponses, car reduce c'est un outil fonctionnel très efficace qui est souvent négligé en remplacement des boucles. Dans ce cas particulier, aucun de ces deux n'est beaucoup plus rapide que do.call)

en utilisant la base R:

df <- Reduce(rbind, listOfDataFrames)

ou, en utilisant l'inverse:

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

Comment cela devrait être fait dans le sens inverse:

df.dplyr.purrr <- listOfDataFrames %>% map_df(bind_rows)
pseudo
la source
3
Pourquoi utiliseriez-vous mapsi vous bind_rowspouvez prendre une liste de cadres de données?
see24
9

Un visuel mis à jour pour ceux qui souhaitent comparer certaines des réponses récentes (je voulais comparer la solution purrr à dplyr). Fondamentalement, j'ai combiné les réponses de @TheVTM et @rmf.

entrez la description de l'image ici

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)

Informations sur la session:

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 du package:

> packageVersion("tidyverse")
[1]1.1.1> packageVersion("data.table")
[1]1.10.0
Nova
la source
7

La seule chose avec laquelle les solutions data.tablemanquent est la colonne identifiant pour savoir de quelle trame de données dans la liste les données proviennent.

Quelque chose comme ça:

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

Le idcolparamètre ajoute une colonne ( .id) identifiant l'origine de la trame de données contenue 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  
f0nzie
la source