Konversi daftar bingkai data menjadi satu bingkai data

336

Saya memiliki kode yang di satu tempat berakhir dengan daftar bingkai data yang saya benar-benar ingin konversi menjadi satu bingkai data besar.

Saya mendapat beberapa petunjuk dari pertanyaan sebelumnya yang mencoba melakukan sesuatu yang serupa tetapi lebih kompleks.

Berikut adalah contoh dari apa yang saya mulai dengan (ini sangat disederhanakan untuk ilustrasi):

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

Saya sedang menggunakan ini:

  df <- do.call("rbind", listOfDataFrames)
JD Long
sumber
Lihat juga pertanyaan ini: stackoverflow.com/questions/2209258/…
Shane
27
The do.call("rbind", list)idiom adalah apa yang telah saya gunakan sebelumnya juga. Mengapa Anda membutuhkan inisial unlist?
Dirk Eddelbuettel
5
dapatkah seseorang menjelaskan kepada saya perbedaan antara do.call ("rbind", list) dan rbind (list) - mengapa outputnya tidak sama?
user6571411
1
@ user6571411 Karena do.call () tidak mengembalikan argumen satu per satu, tetapi menggunakan daftar untuk menyimpan argumen fungsi. Lihat https://www.stat.berkeley.edu/~s133/Docall.html
Marjolein Fokkema

Jawaban:

131

Gunakan bind_rows () dari paket dplyr:

bind_rows(list_of_dataframes, .id = "column_label")
joeklieg
sumber
5
Solusi bagus .id = "column_label"menambahkan nama baris unik berdasarkan nama elemen daftar.
Sibo Jiang
10
karena ini tahun 2018 dan dplyrmerupakan alat yang cepat dan kuat untuk digunakan, saya telah mengubah ini menjadi jawaban yang diterima. Tahun-tahun berlalu, mereka terbang!
JD Long
186

Satu opsi lain adalah menggunakan fungsi plyr:

df <- ldply(listOfDataFrames, data.frame)

Ini sedikit lebih lambat dari aslinya:

> 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

Dugaan saya adalah bahwa menggunakan do.call("rbind", ...)akan menjadi pendekatan tercepat yang akan Anda temukan kecuali Anda dapat melakukan sesuatu seperti (a) menggunakan matriks, bukan data.frame dan (b) mengalokasikan awal matriks akhir dan menetapkannya daripada menumbuhkannya .

Edit 1 :

Berdasarkan komentar Hadley, inilah versi terbaru rbind.filldari CRAN:

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

Ini lebih mudah daripada rbind, dan sedikit lebih cepat (timing ini bertahan lebih dari beberapa kali). Dan sejauh yang saya mengerti, versi plyron github bahkan lebih cepat dari ini.

Shane
sumber
28
rbind.fill dalam versi terbaru plyr jauh lebih cepat daripada do.call dan rbind
hadley
1
menarik. bagi saya rbind.fill adalah yang tercepat. Cukup aneh, do.call / rbind tidak mengembalikan TRUE yang identik, bahkan jika saya tidak dapat menemukan perbedaan. Dua lainnya sama tetapi plyr lebih lambat.
Matt Bannert
I()dapat menggantikan panggilan data.frameAndaldply
baptiste
4
ada juga yang melt.listmembentuk kembali (2)
baptiste
do.call(function(...) rbind(..., make.row.names=F), df)berguna jika Anda tidak ingin nama unik unik yang dihasilkan secara otomatis.
smci
111

Untuk tujuan kelengkapan, saya pikir jawaban untuk pertanyaan ini memerlukan pembaruan. "Dugaan saya adalah bahwa menggunakan do.call("rbind", ...)akan menjadi pendekatan tercepat yang akan Anda temukan ..." Itu mungkin benar untuk Mei 2010 dan beberapa waktu setelahnya, tetapi pada sekitar Sep 2011 fungsi baru rbindlistdiperkenalkan dalam data.tablepaket versi 1.8.2 , dengan komentar bahwa "Ini melakukan hal yang sama do.call("rbind",l), tetapi jauh lebih cepat". Seberapa cepat?

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
sumber
3
Terima kasih banyak untuk ini - saya mencabut rambut saya karena set data saya menjadi terlalu besar untuk ldplysekelompok frame data yang panjang dan cair. Bagaimanapun, saya mendapat speedup yang luar biasa dengan menggunakan rbindlistsaran Anda .
KarateSnowMachine
11
Dan satu lagi untuk kelengkapan: dplyr::rbind_all(listOfDataFrames)akan melakukan trik juga.
andyteucher
2
apakah ada yang setara dengan rbindlisttetapi yang menambahkan frame data dengan kolom? sesuatu seperti cbindlist?
rafa.pereira
2
@ rafa.pereira Ada permintaan fitur terbaru: add function cbindlist
Henrik
Saya juga mencabut rambut saya karena do.call()sudah berjalan di daftar bingkai data selama 18 jam, dan masih belum selesai, terima kasih !!!
Graeme Frost
74

bind-plot

Kode:

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)

Sidang:

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 : Jalankan kembali 31-Jan-2018. Berlari di komputer yang sama. Versi paket baru. Menambahkan benih untuk pecinta benih.

masukkan deskripsi gambar di sini

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 : Jalankan kembali 06-Agustus-2019.

masukkan deskripsi gambar di sini

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
sumber
2
Ini jawaban yang bagus. Saya menjalankan hal yang sama (OS yang sama, paket yang sama, pengacakan berbeda karena Anda tidak set.seed) tetapi melihat beberapa perbedaan dalam kinerja kasus terburuk. rbindlistbenar-benar memiliki kasus terburuk terbaik serta kasus khas terbaik dalam hasil saya
C8H10N4O2
48

Ada juga bind_rows(x, ...)di 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
sumber
secara teknis Anda tidak perlu as.data.frame - semua yang dilakukan membuatnya menjadi data.frame secara eksklusif, tidak seperti table_df (dari deplyr)
user1617979
14

Inilah cara lain yang dapat dilakukan (hanya menambahkannya ke jawaban karena reducemerupakan alat fungsional yang sangat efektif yang sering diabaikan sebagai pengganti loop. Dalam kasus khusus ini, tak satu pun dari ini secara signifikan lebih cepat daripada do.call)

menggunakan basis R:

df <- Reduce(rbind, listOfDataFrames)

atau, menggunakan tidyverse:

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

Bagaimana seharusnya dilakukan di rapi:

df.dplyr.purrr <- listOfDataFrames %>% map_df(bind_rows)
Nick
sumber
3
Mengapa Anda menggunakan mapjika bind_rowsbisa mengambil daftar dataframe?
lihat24
9

Visual yang diperbarui untuk mereka yang ingin membandingkan beberapa jawaban terakhir (saya ingin membandingkan solusi purrr dengan dplyr). Pada dasarnya saya menggabungkan jawaban dari @TheVTM dan @rmf.

masukkan deskripsi gambar di sini

Kode:

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 Sesi:

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

Versi Paket:

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

Satu-satunya hal yang data.tablehilang dengan solusi adalah kolom pengidentifikasi untuk mengetahui dari mana dataframe dalam daftar data berasal.

Sesuatu seperti ini:

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

The idcolparameter menambahkan kolom ( .id) mengidentifikasi asal dataframe yang terkandung dalam daftar. Hasilnya akan terlihat seperti ini:

.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
sumber