Mengapa rbindlist lebih baik daripada rbind?

135

Saya akan melalui dokumentasi data.tabledan juga memperhatikan dari beberapa percakapan di sini pada SO yang rbindlistseharusnya lebih baik daripada rbind.

Saya ingin tahu mengapa rbindlistlebih baik daripada rbinddan di mana skenario yang rbindlistbenar-benar unggul rbind?

Apakah ada keuntungan dalam hal pemanfaatan memori?

Chinmay Patil
sumber

Jawaban:

155

rbindlistadalah versi yang dioptimalkan do.call(rbind, list(...)), yang dikenal lambat saat digunakanrbind.data.frame


Di mana itu benar-benar unggul

Beberapa pertanyaan yang menunjukkan di mana rbindlistbersinar

Penggabungan cepat daftar vektor data.frame demi baris

Kesulitan mengonversi daftar panjang data.frame (~ 1 juta) menjadi data.frame tunggal menggunakan do.call dan ldply

Ini memiliki tolok ukur yang menunjukkan seberapa cepat dapat.


rbind.data.frame lambat, karena suatu alasan

rbind.data.frametidak banyak memeriksa, dan akan cocok dengan nama. (yaitu rbind.data.frame akan menjelaskan fakta bahwa kolom mungkin dalam urutan berbeda, dan cocok dengan nama), rbindlisttidak melakukan pemeriksaan semacam ini, dan akan bergabung dengan posisi

misalnya

do.call(rbind, list(data.frame(a = 1:2, b = 2:3), data.frame(b = 1:2, a = 2:3)))
##    a b
## 1  1 2
## 2  2 3
## 3  2 1
## 4  3 2

rbindlist(list(data.frame(a = 1:5, b = 2:6), data.frame(b = 1:5, a = 2:6)))
##     a b
##  1: 1 2
##  2: 2 3
##  3: 1 2
##  4: 2 3

Beberapa batasan rbindlist lainnya

Ini digunakan untuk berjuang untuk menangani factors, karena bug yang sejak telah diperbaiki:

rbindlist dua data.tabel di mana satu memiliki faktor dan lainnya memiliki tipe karakter untuk kolom ( Bug # 2650 )

Ini memiliki masalah dengan nama kolom duplikat

lihat Pesan peringatan: di rbindlist (allargs): NAs yang diperkenalkan oleh paksaan: kemungkinan bug di data.table? ( Bug # 2384 )


rbames rbind.data.frame bisa membuat frustasi

rbindlistdapat menangani lists data.framesdan data.tables, dan akan mengembalikan data.tabel tanpa nama pengguna

Anda dapat masuk dalam kekacauan nama pengguna menggunakan do.call(rbind, list(...)) see

Bagaimana menghindari pengubahan nama baris saat menggunakan rbind di dalam do.call?


Efisiensi memori

Dalam hal memori rbindlistditerapkan C, demikian juga memori efisien, digunakan setattruntuk mengatur atribut dengan referensi

rbind.data.framediimplementasikan dalam R, itu banyak penugasan, dan menggunakan attr<-(dan class<-dan rownames<-semuanya akan (secara internal) membuat salinan dari data.frame yang dibuat.

mnel
sumber
1
FYI attr<-, class<-dan (saya pikir) rownames<-semuanya memodifikasi di tempat.
Hadley
5
@adley Anda yakin? Coba DF = data.frame(a=1:3); .Internal(inspect(DF)); tracemem(DF); attr(DF,"test") <- "hello"; .Internal(inspect(DF)).
Matt Dowle
4
rbind.data.framememiliki logika "pembajakan" khusus - ketika argumen pertama adalah data.table, ia memanggil .rbind.data.table, yang melakukan sedikit pemeriksaan & kemudian memanggil secara rbindlistinternal. Jadi, jika Anda sudah memiliki data.tableobjek untuk diikat, mungkin ada sedikit perbedaan kinerja antara rbinddan rbindlist.
Ken Williams
6
mnel, posting ini mungkin perlu diedit, sekarang rbindlistmampu mencocokkan dengan nama ( use.names=TRUE) dan juga mengisi kolom yang hilang ( fill=TRUE). Saya sudah memperbarui ini , ini dan posting ini . Apakah Anda keberatan mengedit yang ini atau tidak apa-apa jika saya melakukannya? Apa pun cara saya baik-baik saja.
Arun
1
dplyr::rbind_listjuga sangat mirip
Hadley
48

Oleh v1.9.2, rbindlisttelah berkembang sedikit, menerapkan banyak fitur termasuk:

  • Memilih kolom tertinggi SEXPTYPEsambil mengikat - diimplementasikan pada v1.9.2penutupan FR # 2456 dan Bug # 4981 .
  • Menangani factorkolom dengan benar - pertama kali diimplementasikan dalam v1.8.10penutupan Bug # 2650 dan diperluas untuk mengikat faktor yang diperintahkanv1.9.2 juga, menutup FR # 4856 dan Bug # 5019 .

Selain itu, di v1.9.2, rbind.data.tablejuga memperoleh fillargumen, yang memungkinkan untuk mengikat dengan mengisi kolom yang hilang, diimplementasikan dalam R.

Sekarang v1.9.3, ada lebih banyak lagi perbaikan pada fitur-fitur yang ada:

  • rbindlistmendapatkan argumen use.names, yang secara default adalah FALSEuntuk kompatibilitas mundur.
  • rbindlistjuga mendapatkan argumen fill, yang secara default juga FALSEuntuk kompatibilitas mundur.
  • Semua fitur ini diimplementasikan dalam C, dan ditulis dengan hati-hati agar tidak berkompromi dalam kecepatan sambil menambahkan fungsionalitas.
  • Karena rbindlistsekarang dapat mencocokkan dengan nama dan mengisi kolom yang hilang, rbind.data.tablepanggil saja rbindlistsekarang. Satu-satunya perbedaan adalah bahwa use.names=TRUEsecara default untuk rbind.data.table, untuk kompatibilitas mundur.

rbind.data.framemelambat sedikit karena salinan (yang menunjukkan @mnel juga) yang dapat dihindari (dengan pindah ke C). Saya pikir itu bukan satu-satunya alasan. Implementasi untuk memeriksa / mencocokkan nama kolom rbind.data.framejuga bisa menjadi lebih lambat ketika ada banyak kolom per data.frame dan ada banyak data.frame yang akan diikat (seperti yang ditunjukkan dalam patokan di bawah).

Namun, rbindlistkekurangan (ed) fitur tertentu (seperti tingkat faktor pemeriksaan atau nama yang cocok) memiliki bobot yang sangat kecil (atau tidak sama sekali) lebih cepat daripada rbind.data.frame. Itu karena mereka dengan hati-hati diimplementasikan dalam C, dioptimalkan untuk kecepatan dan memori.

Berikut adalah patokan yang menyoroti efisien mengikat sembari menyamakan dengan nama kolom serta menggunakan rbindlist's use.namesfitur dari v1.9.3. Set data terdiri dari 10.000 data. Bingkai masing-masing ukuran 10 * 500.

NB: patokan ini telah diperbarui untuk menyertakan dibandingkan dengan dplyr'sbind_rows

library(data.table) # 1.11.5, 2018-06-02 00:09:06 UTC
library(dplyr) # 0.7.5.9000, 2018-06-12 01:41:40 UTC
set.seed(1L)
names = paste0("V", 1:500)
cols = 500L
foo <- function() {
    data = as.data.frame(setDT(lapply(1:cols, function(x) sample(10))))
    setnames(data, sample(names))
}
n = 10e3L
ll = vector("list", n)
for (i in 1:n) {
    .Call("Csetlistelt", ll, i, foo())
}

system.time(ans1 <- rbindlist(ll))
#  user  system elapsed 
# 1.226   0.070   1.296 

system.time(ans2 <- rbindlist(ll, use.names=TRUE))
#  user  system elapsed 
# 2.635   0.129   2.772 

system.time(ans3 <- do.call("rbind", ll))
#   user  system elapsed 
# 36.932   1.628  38.594 

system.time(ans4 <- bind_rows(ll))
#   user  system elapsed 
# 48.754   0.384  49.224 

identical(ans2, setDT(ans3)) 
# [1] TRUE
identical(ans2, setDT(ans4))
# [1] TRUE

Mengikat kolom seperti itu tanpa memeriksa nama hanya membutuhkan 1,3, sedangkan memeriksa nama kolom dan mengikat dengan tepat hanya membutuhkan waktu 1,5 detik. Dibandingkan dengan solusi dasar, ini adalah 14x lebih cepat, dan 18x lebih cepat dari dplyrversi.

Arun
sumber