Konvertieren Sie eine Liste von Datenrahmen in einen Datenrahmen


336

Ich habe Code, der an einer Stelle mit einer Liste von Datenrahmen endet, die ich wirklich in einen einzelnen großen Datenrahmen konvertieren möchte.

Ich habe einige Hinweise von einer früheren Frage erhalten, die versucht hat, etwas Ähnliches, aber Komplexeres zu tun.

Hier ist ein Beispiel dafür, womit ich beginne (dies ist zur Veranschaulichung stark vereinfacht):

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

Ich benutze derzeit Folgendes:

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


27
Die do.call("rbind", list)Redewendung habe ich auch schon früher verwendet. Warum brauchst du die Initiale unlist?
Dirk Eddelbuettel

5
Kann mir jemand den Unterschied zwischen do.call ("rbind", Liste) und rbind (Liste) erklären - warum sind die Ausgaben nicht gleich?
user6571411

1
@ user6571411 Da do.call () die Argumente nicht einzeln zurückgibt, sondern eine Liste verwendet, um die Argumente der Funktion zu speichern. Siehe https://www.stat.berkeley.edu/~s133/Docall.html
Marjolein Fokkema

Antworten:


130

Verwenden Sie bind_rows () aus dem dplyr-Paket:

bind_rows(list_of_dataframes, .id = "column_label")

5
Schöne Lösung. .id = "column_label"Fügt die eindeutigen Zeilennamen basierend auf den Namen der Listenelemente hinzu.
Sibo Jiang

10
Da es 2018 ist und dplyrsowohl schnell als auch ein solides Werkzeug ist, habe ich dies in die akzeptierte Antwort geändert. Die Jahre fliegen sie vorbei!
JD Long

186

Eine andere Möglichkeit ist die Verwendung einer Plyr-Funktion:

df <- ldply(listOfDataFrames, data.frame)

Dies ist etwas langsamer als das 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

Ich vermute, dass mit do.call("rbind", ...) der schnellste Ansatz sein wird, den Sie finden werden, es sei denn, Sie können so etwas wie (a) eine Matrize anstelle eines data.frames verwenden und (b) die endgültige Matrix vorab zuweisen und ihr zuweisen, anstatt sie zu vergrößern .

Bearbeiten 1 :

Basierend auf Hadleys Kommentar ist hier die neueste Version rbind.fillvon CRAN:

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

Dies ist einfacher als rbind und geringfügig schneller (diese Timings halten über mehrere Läufe hinweg). Und soweit ich das verstehe, ist die Version von plyron github noch schneller.


28
rbind.fill in der neuesten Version von plyr ist erheblich schneller als do.call und rbind
hadley

1
interessant. Für mich war rbind.fill das schnellste. Seltsamerweise hat do.call / rbind nicht identische TRUE zurückgegeben, auch wenn ich keinen Unterschied finden konnte. Die anderen beiden waren gleich, aber Plyr war langsamer.
Matt Bannert

I()könnte data.framein Ihrem ldplyAnruf ersetzen
Baptiste

4
Es gibt auch melt.listin Umform (2)
Baptiste

do.call(function(...) rbind(..., make.row.names=F), df)ist nützlich, wenn Sie die automatisch generierten eindeutigen Rownamen nicht möchten.
smci

111

Der Vollständigkeit halber dachte ich, dass die Antworten auf diese Frage ein Update erfordern. "Ich vermute, dass die Verwendung do.call("rbind", ...)der schnellste Ansatz sein wird, den Sie finden werden ..." Es war wahrscheinlich für Mai 2010 und einige Zeit danach zutreffend, aber im September 2011 wurde eine neue Funktion rbindlistin der data.tablePaketversion 1.8.2 eingeführt , mit der Bemerkung, dass "Dies das gleiche tut do.call("rbind",l), aber viel schneller". Wie viel schneller?

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
Vielen Dank dafür - ich habe mir die Haare ausgezogen, weil meine Datensätze zu groß wurden, um ldplyein paar lange, geschmolzene Datenrahmen zu erstellen. Wie auch immer, ich habe durch Ihren rbindlistVorschlag eine unglaubliche Beschleunigung erzielt .
KarateSnowMachine

11
Und noch eine der Vollständigkeit halber: dplyr::rbind_all(listOfDataFrames)wird auch den Trick machen.
andyteucher

2
rbindlistGibt es ein Äquivalent zu, aber das Anhängen der Datenrahmen nach Spalte? so etwas wie eine cbindlist?
rafa.pereira

2
@ rafa.pereira Es gibt eine aktuelle Feature-Anfrage: Funktion hinzufügen cbindlist
Henrik

Ich zog mir auch die Haare aus, weil ich do.call()18 Stunden lang auf einer Liste von Datenrahmen lief und immer noch nicht fertig war, danke !!!
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

UPDATE : Wiederholen Sie den 31. Januar 2018. Lief auf demselben Computer. Neue Versionen von Paketen. Samen für Samenliebhaber hinzugefügt.

Geben Sie hier die Bildbeschreibung ein

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

UPDATE : Wiederholung 06-Aug-2019.

Geben Sie hier die Bildbeschreibung ein

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
Dies ist eine großartige Antwort. Ich habe dasselbe ausgeführt (dasselbe Betriebssystem, dieselben Pakete, unterschiedliche Randomisierung, weil Sie dies nicht tun set.seed), aber einige Unterschiede in der Worst-Case-Leistung festgestellt. rbindlisthatte tatsächlich den besten Worst-Case sowie den besten typischen Fall in meinen Ergebnissen
C8H10N4O2

48

Es gibt auch 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

Technisch gesehen benötigen Sie den as.data.frame nicht - alles, was er macht, macht ihn ausschließlich zu einem data.frame, im Gegensatz zu einem table_df (von deplyr)
user1617979

14

Hier ist eine andere Möglichkeit, dies zu tun (einfach zu den Antworten hinzufügen, weil reduce ein sehr effektives Funktionswerkzeug ist, das häufig als Ersatz für Schleifen übersehen wird. In diesem speziellen Fall ist keines dieser Tools wesentlich schneller als do.call).

unter Verwendung der Basis R:

df <- Reduce(rbind, listOfDataFrames)

oder mit dem tidyverse:

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

11

Wie es im Tidyverse gemacht werden soll:

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

3
Warum würden Sie verwenden, mapwenn bind_rowsSie eine Liste von Datenrahmen aufnehmen können?
siehe

9

Ein aktualisiertes Bild für diejenigen, die einige der jüngsten Antworten vergleichen möchten (ich wollte die Lösung purrr mit dplyr vergleichen). Grundsätzlich habe ich Antworten von @TheVTM und @rmf kombiniert.

Geben Sie hier die Bildbeschreibung ein

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)

Sitzungsinfo:

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

Paketversionen:

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

7

Das einzige, was den Lösungen data.tablefehlt, ist die Identifizierungsspalte, um zu wissen, von welchem ​​Datenrahmen in der Liste die Daten stammen.

Etwas wie das:

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

Der idcolParameter fügt eine Spalte ( .id) hinzu, die den Ursprung des in der Liste enthaltenen Datenrahmens angibt. Das Ergebnis würde ungefähr so ​​aussehen:

.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  
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.