Konwertuj listę ramek danych na jedną ramkę danych


336

Mam kod, który w jednym miejscu kończy się listą ramek danych, które naprawdę chcę przekonwertować na pojedynczą ramkę dużych danych.

Dostałem kilka wskazówek z wcześniejszego pytania, które próbowało zrobić coś podobnego, ale bardziej złożonego.

Oto przykład tego, od czego zaczynam (jest to rażąco uproszczone dla ilustracji):

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

Obecnie używam tego:

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

Zobacz także to pytanie: stackoverflow.com/questions/2209258/...
Shane

27
do.call("rbind", list)Idiom co Użyłem przed, jak również. Dlaczego potrzebujesz inicjału unlist?
Dirk Eddelbuettel

5
czy ktoś może mi wyjaśnić różnicę między do.call („rbind”, lista) i rbind (lista) - dlaczego wyniki nie są takie same?
user6571411,

1
@ user6571411 Ponieważ do.call () nie zwraca argumentów jeden po drugim, ale używa listy do przechowywania argumentów funkcji. Zobacz https://www.stat.berkeley.edu/~s133/Docall.html
Marjolein Fokkema

Odpowiedzi:


130

Użyj bind_rows () z pakietu dplyr:

bind_rows(list_of_dataframes, .id = "column_label")

5
Niezłe rozwiązanie. .id = "column_label"dodaje unikalne nazwy wierszy na podstawie nazw elementów listy.
Sibo Jiang

10
ponieważ jest rok 2018 i dplyrjest zarówno szybkie, jak i solidne narzędzie do użycia, zmieniłem to na zaakceptowaną odpowiedź. Lata mijają!
JD Long,

186

Inną opcją jest użycie funkcji plyr:

df <- ldply(listOfDataFrames, data.frame)

Jest to trochę wolniejsze niż oryginał:

> 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

Domyślam się, że użycie do.call("rbind", ...)będzie najszybszym podejściem, jakie znajdziesz, chyba że możesz zrobić coś takiego jak (a) użyć matryc zamiast data.frames i (b) wstępnie przydzielić ostateczną matrycę i przypisać do niej zamiast ją powiększać .

Edytuj 1 :

Na podstawie komentarza Hadleya, oto najnowsza wersja rbind.fillCRAN:

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

Jest to łatwiejsze niż rbind i nieznacznie szybsze (te czasy utrzymują się przez wiele przebiegów). I o ile rozumiem, wersja plyrna github jest jeszcze szybsza.


28
rbind.fill w najnowszej wersji plyr jest znacznie szybszy niż do.call i rbind
hadley

1
ciekawy. dla mnie rbind.fill był najszybszy. Co dziwne, do.call / rbind nie zwrócił identycznej PRAWDY, nawet jeśli nie mogłem znaleźć różnicy. Pozostałe dwie były równe, ale plyr był wolniejszy.
Matt Bannert,

I()może zastąpić data.framew twoim ldplywezwaniu
baptiste

4
jest też melt.listw reshape (2)
baptiste

do.call(function(...) rbind(..., make.row.names=F), df)jest przydatny, jeśli nie chcesz automatycznie generowanych unikalnych nazw plików.
smci

111

Dla celów kompletności pomyślałem, że odpowiedzi na to pytanie wymagają aktualizacji. „Domyślam się, że użycie do.call("rbind", ...)będzie najszybszym podejściem, jakie można znaleźć ...” Prawdopodobnie było to prawdą w maju 2010 r. I jakiś czas później, ale około września 2011 r. rbindlistWprowadzono nową funkcję w data.tablewersji pakietu 1.8.2 , z uwagą „To robi to samo co do.call("rbind",l), ale znacznie szybciej”. O ile szybciej?

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
Dziękuję bardzo za to - wyciągałam włosy, ponieważ moje zestawy danych stawały się zbyt duże, aby ldplypomieścić wiązkę długich, stopionych ramek danych. W każdym razie mam niesamowite przyspieszenie dzięki twojej rbindlistsugestii.
KarateSnowMachine

11
I jeszcze jedno dla kompletności: dplyr::rbind_all(listOfDataFrames)zrobi to samo.
andyteucher

2
czy istnieje odpowiednik, rbindlistale który dołącza ramki danych według kolumny? coś jak cbindlist?
rafa.pereira

2
@ rafa.pereira Pojawiło się ostatnie żądanie funkcji: dodaj funkcję cbindlist
Henrik

Wyciągałam też włosy, ponieważ do.call()przez 18 godzin biegałam na liście ramek danych i nadal nie skończyłam, dziękuję !!!
Graeme Frost

74

fabuła

Kod:

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)

Sesja:

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

AKTUALIZACJA : Ponownie uruchom 31 stycznia 2018 r. Działa na tym samym komputerze. Nowe wersje pakietów. Dodano ziarno dla miłośników nasion.

wprowadź opis zdjęcia tutaj

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

AKTUALIZACJA : Rerun 06-sie-2019.

wprowadź opis zdjęcia tutaj

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
To świetna odpowiedź. Uruchomiłem to samo (ten sam system operacyjny, te same pakiety, inną randomizację, ponieważ nie masz set.seed), ale zauważyłem pewne różnice w wydajności w najgorszym przypadku. rbindlistfaktycznie miałem najlepszy najgorszy przypadek, a także najlepszy typowy przypadek w moich wynikach
C8H10N4O2

48

Jest też bind_rows(x, ...)w 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

technicznie rzecz biorąc, nie potrzebujesz as.data.frame - wszystko to sprawia, że ​​jest to wyłącznie data.frame, w przeciwieństwie do table_df (z deplyr)
user1617979

14

Oto inny sposób, w jaki można to zrobić (po prostu dodając go do odpowiedzi, ponieważ reduce jest to bardzo skuteczne narzędzie funkcjonalne, które często jest pomijane jako zamiennik pętli. W tym konkretnym przypadku żadne z nich nie jest znacznie szybsze niż polecenie.)

przy użyciu podstawy R:

df <- Reduce(rbind, listOfDataFrames)

lub za pomocą tidyverse:

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

11

Jak należy to zrobić w tidyverse:

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

3
Dlaczego miałbyś użyć, mapjeśli bind_rowsmożesz wziąć listę ramek danych?
patrz

9

Zaktualizowany wygląd dla tych, którzy chcą porównać niektóre z ostatnich odpowiedzi (chciałem porównać rozwiązanie mruczenia do rozwiązania dplyr). Zasadniczo połączyłem odpowiedzi z @TheVTM i @rmf.

wprowadź opis zdjęcia tutaj

Kod:

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)

Informacje o sesji:

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

Wersje pakietu:

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

7

Jedyne, czego data.tablebrakuje w rozwiązaniach, to kolumna identyfikacyjna, aby dowiedzieć się, z której ramki danych na liście pochodzą dane.

Coś takiego:

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

Ten idcolparametr dodaje kolumnę ( .id) identyfikującą początek ramki danych zawartej na liście. Wynik wyglądałby mniej więcej tak:

.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  
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.