Apa cara tercepat untuk menggabungkan / menggabungkan data.frames di R?


97

Misalnya (tidak yakin apakah contoh yang paling representatif):

N <- 1e6
d1 <- data.frame(x=sample(N,N), y1=rnorm(N))
d2 <- data.frame(x=sample(N,N), y2=rnorm(N))

Inilah yang saya dapatkan sejauh ini:

d <- merge(d1,d2)
# 7.6 sec

library(plyr)
d <- join(d1,d2)
# 2.9 sec

library(data.table)
dt1 <- data.table(d1, key="x")
dt2 <- data.table(d2, key="x")
d <- data.frame( dt1[dt2,list(x,y1,y2=dt2$y2)] )
# 4.9 sec

library(sqldf)
sqldf()
sqldf("create index ix1 on d1(x)")
sqldf("create index ix2 on d2(x)")
d <- sqldf("select * from d1 inner join d2 on d1.x=d2.x")
sqldf()
# 17.4 sec

Cara yang tepat untuk melakukan cara sqldf ditunjukkan di bawah ini oleh Gabor: buat hanya satu indeks (katakanlah di d1) dan gunakan d1.main daripada d1 dalam pernyataan pilih (jika tidak, ia tidak akan menggunakan indeks). Waktu dalam hal ini adalah 13,6 detik. Membangun indeks pada kedua tabel sebenarnya juga tidak diperlukan dalam kasus data.table, cukup lakukan "dt2 <- data.table (d2)" dan waktunya akan 3,9 detik.
datamurf

Kedua jawaban tersebut memberikan informasi yang berharga, layak untuk dibaca (meskipun hanya satu yang dapat "diterima").
datamurf

Anda membandingkan left join dengan inner join dalam pertanyaan Anda
jangorecki

Jawaban:


46

Pendekatan kecocokan berfungsi jika ada kunci unik dalam bingkai data kedua untuk setiap nilai kunci di yang pertama. Jika ada duplikat pada data frame kedua maka pendekatan match and merge tidak sama. Pertandingan, tentu saja, lebih cepat karena tidak terlalu banyak. Secara khusus tidak pernah mencari kunci duplikat. (dilanjutkan setelah kode)

DF1 = data.frame(a = c(1, 1, 2, 2), b = 1:4)
DF2 = data.frame(b = c(1, 2, 3, 3, 4), c = letters[1:5])
merge(DF1, DF2)
    b a c
  1 1 1 a
  2 2 1 b
  3 3 2 c
  4 3 2 d
  5 4 2 e
DF1$c = DF2$c[match(DF1$b, DF2$b)]
DF1$c
[1] a b c e
Levels: a b c d e

> DF1
  a b c
1 1 1 a
2 1 2 b
3 2 3 c
4 2 4 e

Dalam kode sqldf yang diposting di pertanyaan, mungkin tampak bahwa indeks digunakan pada dua tabel tetapi, pada kenyataannya, mereka ditempatkan pada tabel yang ditimpa sebelum pemilihan sql berjalan dan itu, sebagian, menjelaskan mengapa ini sangat lambat. Ide dari sqldf adalah bahwa bingkai data di sesi R Anda merupakan basis data, bukan tabel di sqlite. Jadi, setiap kali kode merujuk ke nama tabel yang tidak memenuhi syarat, kode itu akan mencari di ruang kerja R Anda untuk itu - bukan di database utama sqlite. Jadi pernyataan pemilihan yang ditampilkan membaca d1 dan d2 dari ruang kerja ke dalam database utama sqlite mengalahkan database yang ada dengan indeks. Akibatnya, ia bergabung tanpa indeks. Jika Anda ingin menggunakan versi d1 dan d2 yang ada di database utama sqlite, Anda harus merujuknya sebagai main.d1 dan main. d2 dan bukan sebagai d1 dan d2. Selain itu, jika Anda mencoba membuatnya berjalan secepat mungkin, perhatikan bahwa gabungan sederhana tidak dapat menggunakan indeks pada kedua tabel sehingga Anda dapat menghemat waktu untuk membuat salah satu indeks. Pada kode di bawah ini kami menggambarkan poin-poin ini.

Perlu diperhatikan bahwa komputasi yang tepat dapat membuat perbedaan besar pada paket mana yang tercepat. Misalnya, kami melakukan penggabungan dan agregat di bawah ini. Kami melihat bahwa hasilnya hampir terbalik untuk keduanya. Dalam contoh pertama dari tercepat hingga paling lambat kita mendapatkan: data.table, plyr, merge dan sqldf sedangkan pada contoh kedua sqldf, aggregate, data.table dan plyr - hampir kebalikan dari yang pertama. Pada contoh pertama, sqldf 3x lebih lambat dari data.table dan yang kedua 200x lebih cepat dari plyr dan 100 kali lebih cepat dari data.table. Di bawah ini kami menunjukkan kode input, waktu keluaran untuk penggabungan dan waktu keluaran untuk agregat. Perlu juga dicatat bahwa sqldf didasarkan pada database dan karena itu dapat menangani objek yang lebih besar dari yang dapat ditangani R (jika Anda menggunakan argumen dbname dari sqldf) sementara pendekatan lain terbatas pada pemrosesan di memori utama. Kami juga telah mengilustrasikan sqldf dengan sqlite tetapi juga mendukung database H2 dan PostgreSQL juga.

library(plyr)
library(data.table)
library(sqldf)

set.seed(123)
N <- 1e5
d1 <- data.frame(x=sample(N,N), y1=rnorm(N))
d2 <- data.frame(x=sample(N,N), y2=rnorm(N))

g1 <- sample(1:1000, N, replace = TRUE)
g2<- sample(1:1000, N, replace = TRUE)
d <- data.frame(d1, g1, g2)

library(rbenchmark)

benchmark(replications = 1, order = "elapsed",
   merge = merge(d1, d2),
   plyr = join(d1, d2),
   data.table = { 
      dt1 <- data.table(d1, key = "x")
      dt2 <- data.table(d2, key = "x")
      data.frame( dt1[dt2,list(x,y1,y2=dt2$y2)] )
      },
   sqldf = sqldf(c("create index ix1 on d1(x)",
      "select * from main.d1 join d2 using(x)"))
)

set.seed(123)
N <- 1e5
g1 <- sample(1:1000, N, replace = TRUE)
g2<- sample(1:1000, N, replace = TRUE)
d <- data.frame(x=sample(N,N), y=rnorm(N), g1, g2)

benchmark(replications = 1, order = "elapsed",
   aggregate = aggregate(d[c("x", "y")], d[c("g1", "g2")], mean), 
   data.table = {
      dt <- data.table(d, key = "g1,g2")
      dt[, colMeans(cbind(x, y)), by = "g1,g2"]
   },
   plyr = ddply(d, .(g1, g2), summarise, avx = mean(x), avy=mean(y)),
   sqldf = sqldf(c("create index ix on d(g1, g2)",
      "select g1, g2, avg(x), avg(y) from main.d group by g1, g2"))
)

Keluaran dari dua panggilan benchmark yang membandingkan kalkulasi penggabungan adalah:

Joining by: x
        test replications elapsed relative user.self sys.self user.child sys.child
3 data.table            1    0.34 1.000000      0.31     0.01         NA        NA
2       plyr            1    0.44 1.294118      0.39     0.02         NA        NA
1      merge            1    1.17 3.441176      1.10     0.04         NA        NA
4      sqldf            1    3.34 9.823529      3.24     0.04         NA        NA

Keluaran dari panggilan tolok ukur yang membandingkan penghitungan agregat adalah:

        test replications elapsed  relative user.self sys.self user.child sys.child
4      sqldf            1    2.81  1.000000      2.73     0.02         NA        NA
1  aggregate            1   14.89  5.298932     14.89     0.00         NA        NA
2 data.table            1  132.46 47.138790    131.70     0.08         NA        NA
3       plyr            1  212.69 75.690391    211.57     0.56         NA        NA

Terima kasih, Gabor. Poin luar biasa, saya membuat beberapa penyesuaian melalui komentar pada pertanyaan asli. Sebenarnya saya kira urutannya mungkin berubah bahkan dalam kasus "merge" tergantung pada ukuran relatif tabel, banyaknya kunci dll (itulah mengapa saya katakan saya tidak yakin apakah contoh saya representatif). Meskipun demikian, senang melihat semua solusi berbeda untuk masalah tersebut.
datamurf

Saya juga menghargai komentar tentang kasus "agregasi". Meskipun ini berbeda dengan penyiapan "gabung" dalam pertanyaan, ini sangat relevan. Saya sebenarnya akan menanyakannya dalam pertanyaan terpisah, tetapi sudah ada satu di sini stackoverflow.com/questions/3685492/… . Anda mungkin ingin berkontribusi untuk itu juga, karena berdasarkan hasil di atas, solusi sqldf mungkin mengalahkan semua jawaban yang ada di sana;)
datasetmurf

40

132 detik yang dilaporkan dalam hasil Gabor data.tablesebenarnya adalah fungsi basis waktu colMeansdan cbind(alokasi memori dan penyalinan diinduksi dengan menggunakan fungsi tersebut). Ada cara penggunaan yang baik dan buruk data.tablejuga.

benchmark(replications = 1, order = "elapsed", 
  aggregate = aggregate(d[c("x", "y")], d[c("g1", "g2")], mean),
  data.tableBad = {
     dt <- data.table(d, key = "g1,g2") 
     dt[, colMeans(cbind(x, y)), by = "g1,g2"]
  }, 
  data.tableGood = {
     dt <- data.table(d, key = "g1,g2") 
     dt[, list(mean(x),mean(y)), by = "g1,g2"]
  }, 
  plyr = ddply(d, .(g1, g2), summarise, avx = mean(x), avy=mean(y)),
  sqldf = sqldf(c("create index ix on d(g1, g2)",
      "select g1, g2, avg(x), avg(y) from main.d group by g1, g2"))
  ) 

            test replications elapsed relative user.self sys.self
3 data.tableGood            1    0.15    1.000      0.16     0.00
5          sqldf            1    1.01    6.733      1.01     0.00
2  data.tableBad            1    1.63   10.867      1.61     0.01
1      aggregate            1    6.40   42.667      6.38     0.00
4           plyr            1  317.97 2119.800    265.12    51.05

packageVersion("data.table")
# [1] ‘1.8.2’
packageVersion("plyr")
# [1] ‘1.7.1’
packageVersion("sqldf")
# [1] ‘0.4.6.4’
R.version.string
# R version 2.15.1 (2012-06-22)

Harap dicatat bahwa saya tidak tahu plyr dengan baik jadi harap periksa dengan Hadley sebelum mengandalkan plyrwaktu di sini. Perhatikan juga bahwa data.tabledo termasuk waktu untuk mengkonversi data.tabledan mengatur kunci, untuk jarak jauh.


Jawaban ini telah diperbarui sejak jawaban asli pada bulan Desember 2010. Hasil benchmark sebelumnya ada di bawah. Silakan lihat riwayat revisi jawaban ini untuk melihat apa yang berubah.

              test replications elapsed   relative user.self sys.self
4   data.tableBest            1   0.532   1.000000     0.488    0.020
7            sqldf            1   2.059   3.870301     2.041    0.008
3 data.tableBetter            1   9.580  18.007519     9.213    0.220
1        aggregate            1  14.864  27.939850    13.937    0.316
2  data.tableWorst            1 152.046 285.800752   150.173    0.556
6 plyrwithInternal            1 198.283 372.712406   189.391    7.665
5             plyr            1 225.726 424.296992   208.013    8.004

Karena ddply hanya berfungsi dengan bingkai data, ini adalah contoh yang menghasilkan kinerja kasus terburuk. Saya berharap memiliki antarmuka yang lebih baik untuk jenis operasi umum ini di versi mendatang.
hadley

1
FYI: Anda tidak dapat menggunakan .Internalpanggilan dalam paket CRAN, lihat Kebijakan Repositori CRAN .
Joshua Ulrich

@JoshuaUlrich Anda bisa ketika jawabannya ditulis hampir 2 tahun yang lalu, iirc. Saya akan memperbarui jawaban ini karena data.tablesecara otomatis dioptimalkan meansekarang (tanpa menelepon secara .Internalinternal).
Matt Dowle

@MatthewDowle: Ya, saya tidak yakin kapan / apakah itu berubah. Saya baru tahu itu masalahnya sekarang. Dan itu baik-baik saja dalam jawaban Anda, hanya tidak akan berfungsi dalam paket.
Joshua Ulrich

1
@AleksandrBlekh Terima kasih. Saya telah menautkan komentar Anda di sini ke permintaan fitur # 599 yang ada . Ayo pindah ke sana. Kode contoh Anda dengan baik menunjukkan forloop, itu bagus. Bisakah Anda menambahkan lebih banyak info tentang "analisis SEM" ke masalah itu? Misalnya saya menebak bahwa SEM = Scanning electron microscope? Mengetahui lebih banyak tentang aplikasi membuatnya lebih menarik bagi kami dan membantu kami memprioritaskan.
Matt Dowle

16

Untuk tugas sederhana (nilai unik di kedua sisi gabungan) saya menggunakan match:

system.time({
    d <- d1
    d$y2 <- d2$y2[match(d1$x,d2$x)]
})

Ini jauh lebih cepat daripada menggabungkan (di mesin saya 0,13 detik hingga 3,37 detik).

Waktu saya:

  • merge: 3.32s
  • plyr: 0.84s
  • match: 0,12 d

4
Terima kasih, Marek. Beberapa penjelasan mengapa ini begitu cepat (membangun tabel indeks / hash) dapat ditemukan di sini: tolstoy.newcastle.edu.au/R/help/01c/2739.html
datasetmurf

11

Saya pikir akan menarik untuk memposting patokan dengan dplyr dalam campuran: (memiliki banyak hal yang berjalan)

            test replications elapsed relative user.self sys.self
5          dplyr            1    0.25     1.00      0.25     0.00
3 data.tableGood            1    0.28     1.12      0.27     0.00
6          sqldf            1    0.58     2.32      0.57     0.00
2  data.tableBad            1    1.10     4.40      1.09     0.01
1      aggregate            1    4.79    19.16      4.73     0.02
4           plyr            1  186.70   746.80    152.11    30.27

packageVersion("data.table")
[1]1.8.10’
packageVersion("plyr")
[1]1.8’
packageVersion("sqldf")
[1]0.4.7’
packageVersion("dplyr")
[1]0.1.2’
R.version.string
[1] "R version 3.0.2 (2013-09-25)"

Baru saja menambahkan:

dplyr = summarise(dt_dt, avx = mean(x), avy = mean(y))

dan atur data untuk dplyr dengan tabel data:

dt <- tbl_dt(d)
dt_dt <- group_by(dt, g1, g2)

Diperbarui: Saya menghapus data.tableBad dan plyr dan hanya RStudio open (i7, 16GB ram).

Dengan data.table 1.9 dan dplyr dengan data frame:

            test replications elapsed relative user.self sys.self
2 data.tableGood            1    0.02      1.0      0.02     0.00
3          dplyr            1    0.04      2.0      0.04     0.00
4          sqldf            1    0.46     23.0      0.46     0.00
1      aggregate            1    6.11    305.5      6.10     0.02

Dengan data.table 1.9 dan dplyr dengan tabel data:

            test replications elapsed relative user.self sys.self
2 data.tableGood            1    0.02        1      0.02     0.00
3          dplyr            1    0.02        1      0.02     0.00
4          sqldf            1    0.44       22      0.43     0.02
1      aggregate            1    6.14      307      6.10     0.01

packageVersion("data.table")
[1] '1.9.0'
packageVersion("dplyr")
[1] '0.1.2'

Untuk konsistensi berikut ini asli dengan all dan data.table 1.9 dan dplyr menggunakan tabel data:

            test replications elapsed relative user.self sys.self
5          dplyr            1    0.01        1      0.02     0.00
3 data.tableGood            1    0.02        2      0.01     0.00
6          sqldf            1    0.47       47      0.46     0.00
1      aggregate            1    6.16      616      6.16     0.00
2  data.tableBad            1   15.45     1545     15.38     0.01
4           plyr            1  110.23    11023     90.46    19.52

Saya pikir data ini terlalu kecil untuk data.table dan dplyr baru :)

Kumpulan data yang lebih besar:

N <- 1e8
g1 <- sample(1:50000, N, replace = TRUE)
g2<- sample(1:50000, N, replace = TRUE)
d <- data.frame(x=sample(N,N), y=rnorm(N), g1, g2)

Butuh sekitar 10-13GB ram hanya untuk menyimpan data sebelum menjalankan benchmark.

Hasil:

            test replications elapsed relative user.self sys.self
1          dplyr            1   14.88        1      6.24     7.52
2 data.tableGood            1   28.41        1     18.55      9.4

Mencoba 1 miliar tapi meledakkan ram. 32GB akan menanganinya tidak masalah.


[Edit oleh Arun] (dotcomken, dapatkah Anda menjalankan kode ini dan menempelkan hasil benchmarking Anda? Terima kasih).

require(data.table)
require(dplyr)
require(rbenchmark)

N <- 1e8
g1 <- sample(1:50000, N, replace = TRUE)
g2 <- sample(1:50000, N, replace = TRUE)
d <- data.frame(x=sample(N,N), y=rnorm(N), g1, g2)

benchmark(replications = 5, order = "elapsed", 
  data.table = {
     dt <- as.data.table(d) 
     dt[, lapply(.SD, mean), by = "g1,g2"]
  }, 
  dplyr_DF = d %.% group_by(g1, g2) %.% summarise(avx = mean(x), avy=mean(y))
) 

Sesuai permintaan Arun di sini output dari apa yang Anda berikan kepada saya untuk dijalankan:

        test replications elapsed relative user.self sys.self
1 data.table            5   15.35     1.00     13.77     1.57
2   dplyr_DF            5  137.84     8.98    136.31     1.44

Maaf atas kebingungannya, larut malam menghampiri saya.

Menggunakan dplyr dengan bingkai data tampaknya menjadi cara yang kurang efisien untuk memproses ringkasan. Apakah metode ini untuk membandingkan fungsionalitas yang tepat dari data.table dan dplyr dengan metode struktur datanya disertakan? Saya hampir lebih suka memisahkan itu karena sebagian besar data perlu dibersihkan sebelum kita group_by atau membuat data.table. Ini bisa menjadi masalah selera tetapi saya pikir bagian terpenting adalah seberapa efisien data dapat dimodelkan.


1
Pembaruan yang bagus. Terima kasih. Saya pikir mesin Anda adalah binatang buas dibandingkan dengan kumpulan data ini .. Berapa ukuran cache L2 Anda (dan L3 jika ada)?
Arun

i7 L2 berukuran 2x256 KB 8-arah, L3 adalah 4 MB 16-arah. 128 GB SSD, Menangkan 7 dari Dell inspiron
dotcomken

1
Bisakah Anda memformat ulang contoh Anda. Saya sedikit bingung. Apakah data.table lebih baik (dalam contoh ini) daripada dplyr? Jika ya, dalam keadaan apa.
csgillespie

1

Dengan menggunakan fungsi penggabungan dan parameter opsionalnya:

Inner join: merge (df1, df2) akan bekerja untuk contoh ini karena R secara otomatis menggabungkan frame dengan nama variabel umum, tetapi Anda kemungkinan besar ingin menentukan merge (df1, df2, by = "CustomerId") untuk memastikan bahwa Anda cocok hanya pada bidang yang Anda inginkan. Anda juga dapat menggunakan parameter by.x dan by.y jika variabel yang cocok memiliki nama yang berbeda dalam bingkai data yang berbeda.

Outer join: merge(x = df1, y = df2, by = "CustomerId", all = TRUE)

Left outer: merge(x = df1, y = df2, by = "CustomerId", all.x = TRUE)

Right outer: merge(x = df1, y = df2, by = "CustomerId", all.y = TRUE)

Cross join: merge(x = df1, y = df2, by = NULL)

Pertanyaannya adalah tentang kinerja. Anda hanya memberikan sintaks untuk gabungan. Meskipun membantu, itu tidak menjawab pertanyaan itu. Jawaban ini tidak memiliki data tolok ukur yang menggunakan contoh OP untuk menunjukkan kinerjanya lebih baik, atau setidaknya sangat kompetitif.
Michael Tuchman
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.