Cách nhanh nhất để hợp nhất / nối data.frame trong R là gì?


97

Ví dụ (không chắc liệu có phải ví dụ tiêu biểu nhất không):

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

Đây là những gì tôi có cho đến nay:

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

Cách thích hợp để thực hiện cách sqldf được Gabor chỉ ra dưới đây: chỉ tạo một chỉ mục (giả sử trên d1) và sử dụng d1.main thay vì d1 trong câu lệnh select (nếu không nó sẽ không sử dụng chỉ mục). Thời gian trong trường hợp này là 13,6 giây. Việc xây dựng chỉ mục trên cả hai bảng thực sự cũng không cần thiết trong trường hợp data.table, chỉ cần thực hiện "dt2 <- data.table (d2)" và thời gian sẽ là 3,9 giây.
datasmurf

Cả hai câu trả lời đều cung cấp thông tin có giá trị, đáng để đọc cả hai (mặc dù chỉ có một câu có thể được "chấp nhận").
datasmurf

bạn đang so sánh tham gia bên trái với tham gia bên trong trong câu hỏi của bạn
jangorecki

Câu trả lời:


46

Phương pháp so khớp hoạt động khi có một khóa duy nhất trong khung dữ liệu thứ hai cho mỗi giá trị khóa trong khung đầu tiên. Nếu có bản sao trong khung dữ liệu thứ hai thì cách tiếp cận đối sánh và hợp nhất không giống nhau. Tất nhiên, trận đấu sẽ nhanh hơn vì nó không hoạt động nhiều. Đặc biệt nó không bao giờ tìm kiếm các khóa trùng lặp. (tiếp theo sau mã)

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

Trong mã sqldf đã được đăng trong câu hỏi, có vẻ như các chỉ mục đã được sử dụng trên hai bảng nhưng trên thực tế, chúng được đặt trên các bảng đã được ghi đè trước khi lựa chọn sql từng chạy và điều đó giải thích tại sao nó quá chậm. Ý tưởng của sqldf là các khung dữ liệu trong phiên R của bạn tạo thành cơ sở dữ liệu, không phải các bảng trong sqlite. Vì vậy, mỗi khi mã đề cập đến tên bảng không đủ tiêu chuẩn, nó sẽ tìm nó trong không gian làm việc R của bạn - không phải trong cơ sở dữ liệu chính của sqlite. Do đó, câu lệnh select đã được hiển thị đọc d1 và d2 từ không gian làm việc vào cơ sở dữ liệu chính của sqlite để chặn những cái ở đó với các chỉ mục. Kết quả là nó thực hiện một phép nối không có chỉ mục. Nếu bạn muốn sử dụng các phiên bản d1 và d2 có trong cơ sở dữ liệu chính của sqlite, bạn sẽ phải gọi chúng là main.d1 và main. d2 và không như d1 và d2. Ngoài ra, nếu bạn đang cố gắng làm cho nó chạy nhanh nhất có thể thì hãy lưu ý rằng một phép nối đơn giản không thể sử dụng các chỉ mục trên cả hai bảng, do đó bạn có thể tiết kiệm thời gian tạo một trong các chỉ mục. Trong đoạn mã dưới đây, chúng tôi minh họa những điểm này.

Điều đáng chú ý là việc tính toán chính xác có thể tạo ra sự khác biệt lớn về gói nào là nhanh nhất. Ví dụ: chúng tôi thực hiện hợp nhất và tổng hợp bên dưới. Chúng tôi thấy rằng kết quả gần như đảo ngược cho cả hai. Trong ví dụ đầu tiên từ nhanh nhất đến chậm nhất, chúng ta nhận được: data.table, plyr, merge và sqldf trong khi trong ví dụ thứ hai sqldf, tổng hợp, data.table và plyr - gần như ngược lại với cái đầu tiên. Trong ví dụ đầu tiên, sqldf chậm hơn 3 lần so với data.table và trong ví dụ thứ hai, nó nhanh hơn 200 lần so với plyr và nhanh hơn 100 lần so với data.table. Dưới đây chúng tôi hiển thị mã đầu vào, thời gian đầu ra cho hợp nhất và thời gian đầu ra cho tổng hợp. Cũng cần lưu ý rằng sqldf dựa trên cơ sở dữ liệu và do đó có thể xử lý các đối tượng lớn hơn R có thể xử lý (nếu bạn sử dụng đối số dbname của sqldf) trong khi các cách tiếp cận khác bị giới hạn xử lý trong bộ nhớ chính. Ngoài ra, chúng tôi đã minh họa sqldf bằng sqlite nhưng nó cũng hỗ trợ cơ sở dữ liệu H2 và PostgreSQL.

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

Kết quả đầu ra từ hai lệnh gọi điểm chuẩn so sánh các phép tính hợp nhất là:

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

Kết quả từ lệnh gọi điểm chuẩn so sánh các tính toán tổng hợp là:

        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

Cảm ơn, Gabor. Điểm tuyệt vời, tôi đã thực hiện một số điều chỉnh thông qua nhận xét cho câu hỏi ban đầu. Trên thực tế, tôi đoán thứ tự có thể thay đổi ngay cả trong trường hợp "hợp nhất" tùy thuộc vào kích thước tương đối của các bảng, nhiều khóa, v.v. (đó là lý do tại sao tôi nói rằng tôi không chắc liệu ví dụ của tôi có phải là đại diện hay không). Tuy nhiên, thật tuyệt khi thấy tất cả các giải pháp khác nhau cho vấn đề.
datasmurf

Tôi cũng đánh giá cao nhận xét về trường hợp "tập hợp". Mặc dù điều này khác với thiết lập "hợp nhất" trong câu hỏi, nhưng nó rất phù hợp. Tôi đã thực sự hỏi về nó trong một câu hỏi riêng, nhưng đã có một câu hỏi ở đây stackoverflow.com/questions/3685492/… . Bạn có thể muốn đóng góp cho một điều đó là tốt, như căn cứ vào kết quả trên, các giải pháp sqldf có thể đánh bại tất cả các câu trả lời hiện có;)
datasmurf

40

132 giây được báo cáo trong kết quả của Gabor data.tablethực sự là các hàm cơ sở định thời gian colMeanscbind(việc cấp phát bộ nhớ và sao chép được tạo ra bằng cách sử dụng các hàm đó). Có những cách sử dụng tốt và xấu data.table.

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)

Xin lưu ý rằng tôi không biết rõ về plyr nên hãy kiểm tra với Hadley trước khi dựa vào plyrthời gian ở đây. Cũng lưu ý rằng những việc data.tablecần làm bao gồm thời gian chuyển đổi sang data.tablevà đặt chìa khóa, để tính giá vé.


Câu trả lời này đã được cập nhật kể từ câu trả lời ban đầu vào tháng 12 năm 2010. Các kết quả điểm chuẩn trước đó ở bên dưới. Vui lòng xem lịch sử sửa đổi của câu trả lời này để xem những gì đã thay đổi.

              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

Vì ddply chỉ hoạt động với các khung dữ liệu, đây là ví dụ mang lại hiệu suất trong trường hợp xấu nhất. Tôi hy vọng sẽ có một giao diện tốt hơn cho loại hoạt động phổ biến này trong một phiên bản trong tương lai.
hadley

1
FYI: bạn không thể sử dụng .Internalcác cuộc gọi trong gói CRAN, hãy xem Chính sách kho lưu trữ CRAN .
Joshua Ulrich

@JoshuaUlrich Bạn có thể khi câu trả lời được viết gần 2 năm trước, iirc. Tôi sẽ cập nhật câu trả lời này như data.tabletự động tối ưu hóa meanngay bây giờ (mà không cần gọi .Internalnội bộ).
Matt Dowle 29/10/12

@MatthewDowle: Vâng, tôi không chắc khi nào / nếu nó thay đổi. Tôi chỉ biết đó là trường hợp bây giờ. Và nó hoàn toàn tốt trong câu trả lời của bạn, chỉ là sẽ không hoạt động trong các gói.
Joshua Ulrich

1
@AleksandrBlekh Cảm ơn. Tôi đã liên kết các nhận xét của bạn ở đây với yêu cầu tính năng hiện có số 599 . Hãy chuyển đến đó. Mã ví dụ của bạn hiển thị forvòng lặp một cách độc đáo , điều đó thật tốt. Bạn có thể thêm thông tin về "phân tích SEM" cho vấn đề đó không? Ví dụ, tôi đoán rằng SEM = Kính hiển vi điện tử quét? Việc biết thêm về ứng dụng khiến ứng dụng trở nên thú vị hơn đối với chúng tôi và giúp chúng tôi ưu tiên.
Matt Dowle

16

Đối với tác vụ đơn giản (giá trị duy nhất ở cả hai phía của phép nối), tôi sử dụng match:

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

Nó nhanh hơn nhiều so với hợp nhất (trên máy của tôi từ 0,13 giây đến 3,37 giây).

Thời gian của tôi:

  • merge: 3,32 giây
  • plyr: 0,84 giây
  • match: 0,12 giây

4
Cảm ơn bạn, Marek. Một số giải thích về lý do tại sao đây là quá nhanh (xây dựng một chỉ số / bảng băm) có thể được tìm thấy ở đây: tolstoy.newcastle.edu.au/R/help/01c/2739.html
datasmurf

11

Nghĩ rằng sẽ rất thú vị khi đăng một điểm chuẩn với dplyr trong hỗn hợp: (có rất nhiều thứ đang chạy)

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

Vừa thêm:

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

và thiết lập dữ liệu cho dplyr với bảng dữ liệu:

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

Đã cập nhật: Tôi đã xóa data.tableBad và plyr và không có gì ngoài RStudio đang mở (i7, ram 16GB).

Với data.table 1.9 và dplyr với 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

Với data.table 1.9 và dplyr với data table:

            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'

Để nhất quán, đây là bản gốc với tất cả và data. Table 1.9 và dplyr sử dụng bảng dữ liệu:

            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

Tôi nghĩ rằng dữ liệu này quá nhỏ đối với data.table và dplyr mới :)

Tập dữ liệu lớn hơn:

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)

Lấy khoảng 10-13GB ram chỉ để giữ dữ liệu trước khi chạy điểm chuẩn.

Các kết quả:

            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

Đã thử 1 tỷ nhưng nổ ram. 32GB sẽ xử lý nó không có vấn đề.


[Chỉnh sửa bởi Arun] (dotcomken, bạn có thể vui lòng chạy mã này và dán kết quả điểm chuẩn của bạn không? Cảm ơn).

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

Theo yêu cầu của Arun ở đây, đầu ra của những gì bạn đã cung cấp cho tôi để chạy:

        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

Xin lỗi vì sự nhầm lẫn, đêm muộn đã đến với tôi.

Sử dụng dplyr với khung dữ liệu dường như là cách kém hiệu quả hơn để xử lý tóm tắt. Đây có phải là phương pháp để so sánh chức năng chính xác của data.table và dplyr với các phương pháp cấu trúc dữ liệu của chúng không? Tôi gần như muốn tách biệt điều đó vì hầu hết dữ liệu sẽ cần được làm sạch trước khi chúng tôi group_by hoặc tạo data.table. Đó có thể là vấn đề về sở thích nhưng tôi nghĩ phần quan trọng nhất là dữ liệu có thể được mô hình hóa hiệu quả như thế nào.


1
Cập nhật tốt đẹp. Cảm ơn. Tôi nghĩ máy của bạn là một con quái vật so với tập dữ liệu này .. Kích thước bộ nhớ cache L2 của bạn (và L3 nếu tồn tại) là bao nhiêu?
Arun

i7 L2 là 2x256 KB 8 chiều, L3 là 4 MB 16 chiều. 128 GB SSD, Win 7 trên inspiron Dell
dotcomken

1
Bạn có thể định dạng lại ví dụ của mình. Tôi hơi bối rối. Data.table (trong ví dụ này) có tốt hơn dplyr không? Nêu vậy, thi trong hoan cảnh nao.
csgillespie

1

Bằng cách sử dụng hàm hợp nhất và các tham số tùy chọn của nó:

Kết nối bên trong: hợp nhất (df1, df2) sẽ hoạt động đối với các ví dụ này vì R tự động nối các khung bằng các tên biến phổ biến, nhưng bạn rất có thể muốn chỉ định hợp nhất (df1, df2, by = "CustomerId") để đảm bảo rằng bạn chỉ phù hợp với các trường bạn muốn. Bạn cũng có thể sử dụng các tham số by.x và by.y nếu các biến phù hợp có tên khác nhau trong các khung dữ liệu khác nhau.

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)

Câu hỏi là về hiệu suất. Bạn chỉ cung cấp cú pháp cho các phép nối. Mặc dù hữu ích, nó không trả lời câu hỏi. Câu trả lời này thiếu dữ liệu điểm chuẩn bằng cách sử dụng các ví dụ của OP để cho thấy nó hoạt động tốt hơn hoặc ít nhất là có tính cạnh tranh cao.
Michael Tuchman
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.