Cách nối (hợp nhất) khung dữ liệu (bên trong, bên ngoài, bên trái, bên phải)


1233

Cho hai khung dữ liệu:

df1 = data.frame(CustomerId = c(1:6), Product = c(rep("Toaster", 3), rep("Radio", 3)))
df2 = data.frame(CustomerId = c(2, 4, 6), State = c(rep("Alabama", 2), rep("Ohio", 1)))

df1
#  CustomerId Product
#           1 Toaster
#           2 Toaster
#           3 Toaster
#           4   Radio
#           5   Radio
#           6   Radio

df2
#  CustomerId   State
#           2 Alabama
#           4 Alabama
#           6    Ohio

Làm thế nào tôi có thể làm kiểu cơ sở dữ liệu, tức là kiểu sql, tham gia ? Đó là, làm thế nào để tôi có được:


Tín dụng thêm:

Làm thế nào tôi có thể thực hiện một câu lệnh chọn kiểu SQL?


4
stat545-ubc.github.io/bit001_dplyr-chcoateet.html ← câu trả lời yêu thích của tôi cho câu hỏi này
isomorphismes

Chuyển đổi dữ liệu với bảng cheatr do RStudio tạo và duy trì cũng có các infographics đẹp về cách thức hoạt động của các công cụ trong dplyr rstudio.com/resource/cheatsheets
Arthur Yip

2
Nếu bạn đến đây thay vì muốn biết về việc hợp nhất các tệp dữ liệu gấu trúc , tài nguyên đó có thể được tìm thấy ở đây .
cs95

Câu trả lời:


1350

Bằng cách sử dụng mergechức năng và các tham số tùy chọn của nó:

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

Tham gia ngoài: merge(x = df1, y = df2, by = "CustomerId", all = TRUE)

Bên trái bên ngoài: merge(x = df1, y = df2, by = "CustomerId", all.x = TRUE)

Bên ngoài bên phải: merge(x = df1, y = df2, by = "CustomerId", all.y = TRUE)

Tham gia chéo: merge(x = df1, y = df2, by = NULL)

Cũng giống như với phép nối bên trong, có lẽ bạn sẽ muốn chuyển rõ ràng "CustomerId" cho R làm biến phù hợp. Tôi nghĩ rằng hầu như luôn luôn tốt nhất để nêu rõ các định danh mà bạn muốn hợp nhất; Sẽ an toàn hơn nếu data.frames đầu vào thay đổi bất ngờ và dễ đọc hơn sau này.

Bạn có thể hợp nhất trên nhiều cột bằng cách đưa ra bymột vectơ, ví dụ : by = c("CustomerId", "OrderId").

Nếu tên cột để hợp nhất trên không giống nhau, bạn có thể xác định, ví dụ như, by.x = "CustomerId_in_df1", by.y = "CustomerId_in_df2"nơi CustomerId_in_df1là tên của cột trong khung dữ liệu đầu tiên và CustomerId_in_df2là tên của cột trong khung dữ liệu thứ hai. (Đây cũng có thể là vectơ nếu bạn cần hợp nhất trên nhiều cột.)


2
@MattParker Tôi đã sử dụng gói sqldf cho toàn bộ các truy vấn phức tạp đối với dataframes, thực sự cần nó để tự tham gia chéo (ví dụ: data.frame tự tham gia chéo) Tôi tự hỏi làm thế nào nó so sánh từ góc độ hiệu suất ... . ???
Nicholas Hamilton

9
@ADP Tôi chưa bao giờ thực sự sử dụng sqldf, vì vậy tôi không chắc về tốc độ. Nếu hiệu suất là một vấn đề lớn đối với bạn, bạn cũng nên xem xét data.tablegói - đó là một bộ cú pháp tham gia hoàn toàn mới, nhưng nó hoàn toàn nhanh hơn bất cứ điều gì chúng ta đang nói ở đây.
Matt Parker

5
Với sự rõ ràng và giải thích rõ hơn ..... mkmanu.wordpress.com/2016/04/08/ Khăn
Manoj Kumar

42
Một bổ sung nhỏ hữu ích cho tôi - Khi bạn muốn hợp nhất bằng nhiều cột:merge(x=df1,y=df2, by.x=c("x_col1","x_col2"), by.y=c("y_col1","y_col2"))
Dileep Kumar Patchigolla

8
Điều này hoạt động data.tablengay bây giờ, cùng chức năng chỉ nhanh hơn.
marbel

222

Tôi khuyên bạn nên kiểm tra gói sqldf của Gabor Grothendieck , cho phép bạn thể hiện các hoạt động này bằng SQL.

library(sqldf)

## inner join
df3 <- sqldf("SELECT CustomerId, Product, State 
              FROM df1
              JOIN df2 USING(CustomerID)")

## left join (substitute 'right' for right join)
df4 <- sqldf("SELECT CustomerId, Product, State 
              FROM df1
              LEFT JOIN df2 USING(CustomerID)")

Tôi thấy cú pháp SQL đơn giản và tự nhiên hơn so với tương đương R của nó (nhưng điều này có thể chỉ phản ánh sự thiên vị RDBMS của tôi).

Xem GitHub sqldf của Gabor để biết thêm thông tin về việc tham gia.


198

Có một cách tiếp cận data.table cho một phép nối bên trong, rất hiệu quả về thời gian và bộ nhớ (và cần thiết cho một số data.frames lớn hơn):

library(data.table)

dt1 <- data.table(df1, key = "CustomerId") 
dt2 <- data.table(df2, key = "CustomerId")

joined.dt1.dt.2 <- dt1[dt2]

mergecũng hoạt động trên data.tables (vì nó là chung và gọi merge.data.table)

merge(dt1, dt2)

data.table được ghi lại trên stackoverflow:
Cách thực hiện thao tác hợp nhất data.table
Dịch SQL kết hợp với khóa ngoài sang R data.table Cú pháp
thay thế hiệu quả để hợp nhất cho dữ liệu lớn hơn.frames R
Cách thực hiện nối ngoài cơ bản bên trái với data.table trong R?

Một tùy chọn khác là joinchức năng được tìm thấy trong gói plyr

library(plyr)

join(df1, df2,
     type = "inner")

#   CustomerId Product   State
# 1          2 Toaster Alabama
# 2          4   Radio Alabama
# 3          6   Radio    Ohio

Tùy chọn cho type: inner, left, right,full .

Từ ?join: Không giống như merge, [ join] duy trì thứ tự của x cho dù loại tham gia nào được sử dụng.


8
+1 để đề cập plyr::join. Microbenchmarking chỉ ra rằng nó thực hiện nhanh hơn khoảng 3 lần so với merge.
Beasterfield

20
Tuy nhiên, data.tablenhanh hơn nhiều so với cả hai. Ngoài ra còn có sự hỗ trợ tuyệt vời trong SO, tôi không thấy nhiều nhà văn gói trả lời các câu hỏi ở đây thường xuyên như data.tablenhà văn hoặc người đóng góp.
marbel

1
Là gì data.tablecú pháp cho việc sáp nhập một danh sách các khung dữ liệu ?
Alexanderr Blekh

5
Xin lưu ý: dt1 [dt2] là một kết nối bên ngoài bên phải (không phải là tham gia bên trong "thuần túy") để TẤT CẢ các hàng từ dt2 sẽ là một phần của kết quả ngay cả khi không có hàng phù hợp trong dt1. Tác động: Kết quả của bạn có các hàng không mong muốn tiềm tàng nếu bạn có các giá trị chính trong dt2 không khớp với các giá trị chính của dt1.
R Yoda

8
@RYoda bạn chỉ có thể chỉ định nomatch = 0Ltrong trường hợp đó.
David Arenburg

181

Bạn cũng có thể tham gia bằng cách sử dụng gói dplyr tuyệt vời của Hadley Wickham .

library(dplyr)

#make sure that CustomerId cols are both type numeric
#they ARE not using the provided code in question and dplyr will complain
df1$CustomerId <- as.numeric(df1$CustomerId)
df2$CustomerId <- as.numeric(df2$CustomerId)

Tham gia đột biến: thêm các cột vào df1 bằng cách sử dụng các kết quả khớp trong df2

#inner
inner_join(df1, df2)

#left outer
left_join(df1, df2)

#right outer
right_join(df1, df2)

#alternate right outer
left_join(df2, df1)

#full join
full_join(df1, df2)

Lọc tham gia: lọc ra các hàng trong df1, không sửa đổi các cột

semi_join(df1, df2) #keep only observations in df1 that match in df2.
anti_join(df1, df2) #drops all observations in df1 that match in df2.

16
Tại sao bạn cần chuyển đổi CustomerIdsang số? Tôi không thấy bất kỳ đề cập nào trong tài liệu (cho cả hai plyrdplyr) về loại hạn chế này. Mã của bạn sẽ hoạt động không chính xác, nếu cột hợp nhất sẽ là characterloại (đặc biệt quan tâm đến plyr)? Tui bỏ lỡ điều gì vậy?
Alexanderr Blekh

Người ta có thể sử dụng semi_join (df1, df2, df3, df4) để chỉ giữ các quan sát trong df1 khớp với các cột còn lại không?
Ghose Bishwajit

@GhoseBishwajit Giả sử bạn còn lại trung bình của dataframes thay vì cột, bạn có thể sử dụng rbind trên df2, df3 và df4 nếu họ có cùng một cấu trúc ví dụ semi_join (df1, rbind (df2, df3, df4))
abhy3

Vâng, tôi có nghĩa là khung dữ liệu. Nhưng chúng không có cấu trúc giống như một số bị thiếu trên một số hàng nhất định. Đối với bốn datafram, tôi có dữ liệu về bốn chỉ số khác nhau (GDP, GNP GINI, MMR) cho số lượng quốc gia khác nhau. Tôi muốn tham gia các datafram theo cách chỉ giữ các quốc gia đó hiện diện cho cả bốn chỉ số.
Ghose Bishwajit

86

Có một số ví dụ tốt về việc này tại R Wiki . Tôi sẽ đánh cắp một cặp vợ chồng ở đây:

Phương pháp hợp nhất

Vì các khóa của bạn được đặt tên giống nhau nên cách ngắn để thực hiện phép nối bên trong là merge ():

merge(df1,df2)

một kết nối bên trong đầy đủ (tất cả các bản ghi từ cả hai bảng) có thể được tạo bằng từ khóa "tất cả":

merge(df1,df2, all=TRUE)

nối ngoài trái của df1 và df2:

merge(df1,df2, all.x=TRUE)

một kết nối bên ngoài bên phải của df1 và df2:

merge(df1,df2, all.y=TRUE)

bạn có thể lật chúng, tát chúng và chà chúng xuống để có được hai kết nối bên ngoài khác mà bạn đã hỏi về :)

Phương pháp đăng ký

Một kết nối bên ngoài bên trái với df1 ở bên trái bằng phương thức đăng ký sẽ là:

df1[,"State"]<-df2[df1[ ,"Product"], "State"]

Sự kết hợp khác của các phép nối ngoài có thể được tạo bằng cách trộn ví dụ đăng ký tham gia bên ngoài bên trái. (vâng, tôi biết đó là tương đương với câu nói "Tôi sẽ để nó như một bài tập cho người đọc ...")


4
Liên kết "R Wiki" bị hỏng.
zx8754

79

Mới năm 2014:

Đặc biệt nếu bạn cũng quan tâm đến thao tác dữ liệu nói chung (bao gồm sắp xếp, lọc, đặt lại, tóm tắt, v.v.), bạn chắc chắn nên xem qua dplyr , đi kèm với một loạt các chức năng được thiết kế để hỗ trợ công việc của bạn cụ thể với khung dữ liệu và một số loại cơ sở dữ liệu khác. Nó thậm chí còn cung cấp một giao diện SQL khá phức tạp và thậm chí là một chức năng để chuyển đổi (hầu hết) mã SQL trực tiếp thành R.

Bốn chức năng liên quan đến nối trong gói dplyr là (để trích dẫn):

  • inner_join(x, y, by = NULL, copy = FALSE, ...): trả về tất cả các hàng từ x trong đó có các giá trị khớp trong y và tất cả các cột từ x và y
  • left_join(x, y, by = NULL, copy = FALSE, ...): trả về tất cả các hàng từ x và tất cả các cột từ x và y
  • semi_join(x, y, by = NULL, copy = FALSE, ...): trả về tất cả các hàng từ x trong đó có các giá trị khớp trong y, chỉ giữ các cột từ x.
  • anti_join(x, y, by = NULL, copy = FALSE, ...): trả về tất cả các hàng từ x trong đó không có giá trị khớp trong y, chỉ giữ các cột từ x

Tất cả đều ở đây rất chi tiết.

Chọn cột có thể được thực hiện bởi select(df,"column"). Nếu đó không phải là SQL-ish đủ cho bạn, thì có sql()chức năng, trong đó bạn có thể nhập mã SQL như hiện tại và nó sẽ thực hiện thao tác bạn đã chỉ định giống như bạn đã viết bằng R (để biết thêm thông tin, vui lòng tham khảo đến họa tiết dplyr / cơ sở dữ liệu ). Ví dụ: nếu được áp dụng đúng cách, sql("SELECT * FROM hflights")sẽ chọn tất cả các cột từ bảng dplyr "hflights" ("tbl").


Giải pháp chắc chắn tốt nhất cho tầm quan trọng mà gói dplyr đã đạt được trong hai năm qua.
Marco Fumagalli

72

Cập nhật các phương thức data.table để tham gia bộ dữ liệu. Xem các ví dụ dưới đây cho từng loại tham gia. Có hai phương thức, một từ [.data.tablekhi truyền data.table thứ hai làm đối số thứ nhất cho tập hợp con, một cách khác là sử dụng mergehàm gửi đến phương thức data.table nhanh.

df1 = data.frame(CustomerId = c(1:6), Product = c(rep("Toaster", 3), rep("Radio", 3)))
df2 = data.frame(CustomerId = c(2L, 4L, 7L), State = c(rep("Alabama", 2), rep("Ohio", 1))) # one value changed to show full outer join

library(data.table)

dt1 = as.data.table(df1)
dt2 = as.data.table(df2)
setkey(dt1, CustomerId)
setkey(dt2, CustomerId)
# right outer join keyed data.tables
dt1[dt2]

setkey(dt1, NULL)
setkey(dt2, NULL)
# right outer join unkeyed data.tables - use `on` argument
dt1[dt2, on = "CustomerId"]

# left outer join - swap dt1 with dt2
dt2[dt1, on = "CustomerId"]

# inner join - use `nomatch` argument
dt1[dt2, nomatch=NULL, on = "CustomerId"]

# anti join - use `!` operator
dt1[!dt2, on = "CustomerId"]

# inner join - using merge method
merge(dt1, dt2, by = "CustomerId")

# full outer join
merge(dt1, dt2, by = "CustomerId", all = TRUE)

# see ?merge.data.table arguments for other cases

Dưới đây kiểm tra điểm chuẩn cơ sở R, sqldf, dplyr và data.table.
Kiểm tra điểm chuẩn các bộ dữ liệu không khóa / không lập chỉ mục. Điểm chuẩn được thực hiện trên bộ dữ liệu hàng 50M-1, có 50M-2 giá trị chung trên cột tham gia để mỗi kịch bản (bên trong, bên trái, bên phải, đầy đủ) có thể được kiểm tra và tham gia vẫn không tầm thường để thực hiện. Đây là loại tham gia mà cũng nhấn mạnh các thuật toán tham gia. Thời gian được tính sqldf:0.4.11, dplyr:0.7.8, data.table:1.12.0.

# inner
Unit: seconds
   expr       min        lq      mean    median        uq       max neval
   base 111.66266 111.66266 111.66266 111.66266 111.66266 111.66266     1
  sqldf 624.88388 624.88388 624.88388 624.88388 624.88388 624.88388     1
  dplyr  51.91233  51.91233  51.91233  51.91233  51.91233  51.91233     1
     DT  10.40552  10.40552  10.40552  10.40552  10.40552  10.40552     1
# left
Unit: seconds
   expr        min         lq       mean     median         uq        max 
   base 142.782030 142.782030 142.782030 142.782030 142.782030 142.782030     
  sqldf 613.917109 613.917109 613.917109 613.917109 613.917109 613.917109     
  dplyr  49.711912  49.711912  49.711912  49.711912  49.711912  49.711912     
     DT   9.674348   9.674348   9.674348   9.674348   9.674348   9.674348       
# right
Unit: seconds
   expr        min         lq       mean     median         uq        max
   base 122.366301 122.366301 122.366301 122.366301 122.366301 122.366301     
  sqldf 611.119157 611.119157 611.119157 611.119157 611.119157 611.119157     
  dplyr  50.384841  50.384841  50.384841  50.384841  50.384841  50.384841     
     DT   9.899145   9.899145   9.899145   9.899145   9.899145   9.899145     
# full
Unit: seconds
  expr       min        lq      mean    median        uq       max neval
  base 141.79464 141.79464 141.79464 141.79464 141.79464 141.79464     1
 dplyr  94.66436  94.66436  94.66436  94.66436  94.66436  94.66436     1
    DT  21.62573  21.62573  21.62573  21.62573  21.62573  21.62573     1

Lưu ý rằng có các loại liên kết khác mà bạn có thể thực hiện bằng cách sử dụng data.table:
- cập nhật khi tham gia - nếu bạn muốn tra cứu các giá trị từ một bảng khác vào bảng chính của mình
- tổng hợp khi tham gia - nếu bạn muốn tổng hợp trên khóa bạn đang tham gia, bạn không có hiện thực hóa tất cả tham gia kết quả
- chồng chéo tham gia - nếu bạn muốn kết hợp bởi các dãy
- cán tham gia - nếu bạn muốn hợp nhất để có thể phù hợp với các giá trị từ preceeding / hàng sau bằng cách cuộn chúng về phía trước hoặc phía sau
- phi đẳng tham gia - nếu bạn điều kiện tham gia là không bằng nhau

Mã để sao chép:

library(microbenchmark)
library(sqldf)
library(dplyr)
library(data.table)
sapply(c("sqldf","dplyr","data.table"), packageVersion, simplify=FALSE)

n = 5e7
set.seed(108)
df1 = data.frame(x=sample(n,n-1L), y1=rnorm(n-1L))
df2 = data.frame(x=sample(n,n-1L), y2=rnorm(n-1L))
dt1 = as.data.table(df1)
dt2 = as.data.table(df2)

mb = list()
# inner join
microbenchmark(times = 1L,
               base = merge(df1, df2, by = "x"),
               sqldf = sqldf("SELECT * FROM df1 INNER JOIN df2 ON df1.x = df2.x"),
               dplyr = inner_join(df1, df2, by = "x"),
               DT = dt1[dt2, nomatch=NULL, on = "x"]) -> mb$inner

# left outer join
microbenchmark(times = 1L,
               base = merge(df1, df2, by = "x", all.x = TRUE),
               sqldf = sqldf("SELECT * FROM df1 LEFT OUTER JOIN df2 ON df1.x = df2.x"),
               dplyr = left_join(df1, df2, by = c("x"="x")),
               DT = dt2[dt1, on = "x"]) -> mb$left

# right outer join
microbenchmark(times = 1L,
               base = merge(df1, df2, by = "x", all.y = TRUE),
               sqldf = sqldf("SELECT * FROM df2 LEFT OUTER JOIN df1 ON df2.x = df1.x"),
               dplyr = right_join(df1, df2, by = "x"),
               DT = dt1[dt2, on = "x"]) -> mb$right

# full outer join
microbenchmark(times = 1L,
               base = merge(df1, df2, by = "x", all = TRUE),
               dplyr = full_join(df1, df2, by = "x"),
               DT = merge(dt1, dt2, by = "x", all = TRUE)) -> mb$full

lapply(mb, print) -> nul

Có đáng để thêm một ví dụ cho thấy cách sử dụng các tên cột khác nhau on = không?
SymbolixAU

1
@Symbolix chúng tôi có thể đợi bản phát hành 1.9.8 vì nó sẽ thêm các toán tử tham gia không đẳng thức vào onarg
jangorecki

Một suy nghĩ khác; có đáng để thêm một lưu ý rằng với đối số merge.data.tablemặc định sort = TRUE, sẽ thêm một khóa trong quá trình hợp nhất và để nó ở đó trong kết quả. Đây là điều cần chú ý, đặc biệt nếu bạn đang cố gắng tránh cài đặt phím.
SymbolixAU

1
Tôi ngạc nhiên khi không có ai đề cập rằng hầu hết những người này không làm việc nếu có dups ...
statquant 7/11/2016

@statquant Bạn có thể tham gia Cartesian với data.table, ý bạn là gì? Bạn có thể cho biết cụ thể hơn được không.
David Arenburg

32

dplyr kể từ 0,4 đã triển khai tất cả các tham gia bao gồm outer_join, nhưng điều đáng chú ý là trong một vài bản phát hành đầu tiên trước 0,4, nó đã không được cung cấp outer_join, và kết quả là có rất nhiều mã người dùng bị hack thực sự xấu đã xuất hiện khá lâu sau đó (bạn vẫn có thể tìm thấy mã như vậy trong SO, câu trả lời Kaggle, github từ thời kỳ đó. Do đó câu trả lời này vẫn phục vụ mục đích hữu ích.)

Điểm nổi bật liên quan đến tham gia :

v0.5 (6/2016)

  • Xử lý cho loại POSIXct, múi giờ, trùng lặp, các cấp yếu tố khác nhau. Lỗi và cảnh báo tốt hơn.
  • Đối số hậu tố mới để kiểm soát những tên hậu tố trùng lặp nhận được (# 1296)

v0.4.0 (1/2015)

  • Thực hiện tham gia ngay và tham gia bên ngoài (# 96)
  • Các phép nối đột biến, thêm các biến mới vào một bảng từ các hàng khớp nhau. Lọc tham gia, lọc các quan sát từ một bảng dựa trên việc chúng có khớp với một quan sát trong bảng khác hay không.

v0.3 (10/2014)

  • Bây giờ có thể left_join bởi các biến khác nhau trong mỗi bảng: df1%>% left_join (df2, c ("var1" = "var2"))

v0.2 (5/2014)

  • * _join () không còn sắp xếp lại tên cột (# 324)

v0.1.3 (4/2014)

Cách giải quyết theo ý kiến ​​của hadley trong vấn đề đó:

  • right_join (x, y) giống với left_join (y, x) về các hàng, chỉ các cột sẽ là các thứ tự khác nhau. Dễ dàng làm việc xung quanh với select (new_column_order)
  • outs_join về cơ bản là union (left_join (x, y), right_join (x, y)) - tức là bảo toàn tất cả các hàng trong cả hai khung dữ liệu.

1
@Gregor: không nên xóa. Điều quan trọng là người dùng R phải biết rằng các khả năng tham gia đã bị thiếu trong nhiều năm, vì hầu hết các mã ngoài đó đều có cách giải quyết hoặc triển khai thủ công ad-hoc, hoặc quảng cáo với các vectơ chỉ mục, hoặc tệ hơn là vẫn tránh sử dụng các gói này hoặc hoạt động ở tất cả. Mỗi tuần tôi thấy những câu hỏi như vậy về SO. Chúng tôi sẽ hoàn tác sự nhầm lẫn trong nhiều năm tới.
smci

@Gregor và những người khác đã hỏi: đã cập nhật, tóm tắt những thay đổi lịch sử và những gì còn thiếu trong vài năm khi câu hỏi này được hỏi. Điều này minh họa tại sao mã từ thời kỳ đó chủ yếu là hack, hoặc tránh sử dụng dplyr tham gia và quay trở lại hợp nhất. Nếu bạn kiểm tra các cơ sở mã hóa lịch sử trên SO và Kaggle, bạn vẫn có thể thấy sự chậm trễ chấp nhận và mã người dùng bị nhầm lẫn nghiêm trọng dẫn đến điều này. Hãy cho tôi biết nếu bạn vẫn thấy câu trả lời này còn thiếu.
smci

@Gregor: Những người trong chúng ta đã nhận nuôi nó vào giữa năm 2014 đã không chọn thời điểm tốt nhất. (Tôi nghĩ rằng đã có các bản phát hành trước đó (0.0.x) vào khoảng năm 2013, nhưng không, lỗi của tôi.) Bất kể, vẫn còn rất nhiều mã tào lao vào năm 2015, đó là điều thúc đẩy tôi đăng bài này, tôi đã cố gắng làm sáng tỏ crud tôi tìm thấy trên Kaggle, github, SO.
smci

2
Vâng, tôi hiểu, và tôi nghĩ bạn làm tốt công việc đó. (Tôi là một adopter đầu quá, và trong khi tôi vẫn như dplyrcú pháp, sự thay đổi từ lazyevalđể rlangbackends đã phá vỡ một loạt các mã cho tôi, mà chở tôi để tìm hiểu thêm data.table, và bây giờ tôi chủ yếu sử dụng data.table.)
Gregor Thomas

@Gregor: thật thú vị, bạn có thể chỉ cho tôi bất kỳ câu hỏi và trả lời nào (của bạn hoặc của bất kỳ ai khác) không? Có vẻ như mỗi lần áp dụng plyr/ dplyr/ data.table/ tidyverse của chúng tôi phụ thuộc rất nhiều vào năm chúng tôi bắt đầu và những gì (phôi thai) nói rằng các gói đã trở lại sau đó, trái ngược với bây giờ ...
smci

25

Khi tham gia hai khung dữ liệu với ~ 1 triệu hàng mỗi hàng, một có 2 cột và cột còn lại có ~ 20, tôi đã ngạc nhiên thấy merge(..., all.x = TRUE, all.y = TRUE)nhanh hơn sau đó dplyr::full_join(). Đây là với dplyr v0.4

Hợp nhất mất ~ 17 giây, full_join mất ~ 65 giây.

Mặc dù vậy, một số thực phẩm, vì tôi thường mặc định là dplyr cho các tác vụ thao tác.


24

Đối với trường hợp nối trái với 0..*:0..1cardinality hoặc nối phải với 0..1:0..*cardinality, có thể gán các cột đơn phương từ liên kết ( 0..1bảng) trực tiếp vào joinee ( 0..*bảng), và do đó tránh tạo ra một bảng dữ liệu hoàn toàn mới. Điều này đòi hỏi phải khớp các cột chính từ liên kết vào trình nối và lập chỉ mục + sắp xếp các hàng của người tham gia tương ứng cho bài tập.

Nếu khóa là một cột đơn, thì chúng ta có thể sử dụng một cuộc gọi đến match() để thực hiện khớp. Đây là trường hợp tôi sẽ trình bày trong câu trả lời này.

Đây là một ví dụ dựa trên OP, ngoại trừ tôi đã thêm một hàng bổ sung df2với id là 7 để kiểm tra trường hợp khóa không khớp trong trình nối. Điều này có hiệu quả df1còn lại tham gia df2:

df1 <- data.frame(CustomerId=1:6,Product=c(rep('Toaster',3L),rep('Radio',3L)));
df2 <- data.frame(CustomerId=c(2L,4L,6L,7L),State=c(rep('Alabama',2L),'Ohio','Texas'));
df1[names(df2)[-1L]] <- df2[match(df1[,1L],df2[,1L]),-1L];
df1;
##   CustomerId Product   State
## 1          1 Toaster    <NA>
## 2          2 Toaster Alabama
## 3          3 Toaster    <NA>
## 4          4   Radio Alabama
## 5          5   Radio    <NA>
## 6          6   Radio    Ohio

Trong phần trên tôi đã mã hóa một giả định rằng cột khóa là cột đầu tiên của cả hai bảng đầu vào. Tôi sẽ lập luận rằng, nói chung, đây không phải là một giả định vô lý, vì, nếu bạn có data.frame với một cột chính, sẽ thật lạ nếu nó không được thiết lập làm cột đầu tiên của data.frame từ Ban đầu. Và bạn luôn có thể sắp xếp lại các cột để làm cho nó như vậy. Một kết quả thuận lợi của giả định này là tên của cột khóa không phải được mã hóa cứng, mặc dù tôi cho rằng nó chỉ thay thế một giả định này bằng một giả định khác. Sự can thiệp là một lợi thế khác của việc lập chỉ mục số nguyên, cũng như tốc độ. Trong các điểm chuẩn bên dưới, tôi sẽ thay đổi cách triển khai để sử dụng lập chỉ mục tên chuỗi để phù hợp với việc triển khai cạnh tranh.

Tôi nghĩ rằng đây là một giải pháp đặc biệt thích hợp nếu bạn có một vài bảng mà bạn muốn rời khỏi tham gia so với một bảng lớn duy nhất. Việc xây dựng lại toàn bộ bảng cho mỗi lần hợp nhất sẽ không cần thiết và không hiệu quả.

Mặt khác, nếu bạn cần joinee vẫn không thay đổi thông qua thao tác này vì bất kỳ lý do gì, thì giải pháp này không thể được sử dụng, vì nó sửa đổi trực tiếp joinee. Mặc dù trong trường hợp đó, bạn chỉ cần tạo một bản sao và thực hiện (các) bài tập tại chỗ trên bản sao.


Là một lưu ý phụ, tôi đã xem xét ngắn gọn các giải pháp phù hợp có thể cho các khóa nhiều màu. Thật không may, các giải pháp phù hợp duy nhất tôi tìm thấy là:

  • kết nối không hiệu quả. ví dụ match(interaction(df1$a,df1$b),interaction(df2$a,df2$b)), hoặc cùng một ý tưởng với paste().
  • liên kết cartesian không hiệu quả, ví dụ outer(df1$a,df2$a,`==`) & outer(df1$b,df2$b,`==`).
  • cơ sở R merge()và các hàm hợp nhất dựa trên gói tương đương, luôn luôn phân bổ một bảng mới để trả về kết quả đã hợp nhất và do đó không phù hợp với giải pháp dựa trên phân công tại chỗ.

Ví dụ: xem Kết hợp nhiều cột trên các khung dữ liệu khác nhau và lấy cột khác làm kết quả , khớp hai cột với hai cột khác , Ghép trên nhiều cột và bản sao của câu hỏi này ban đầu tôi đưa ra giải pháp tại chỗ, Kết hợp hai khung dữ liệu với số lượng khác nhau của các hàng trong R .


Điểm chuẩn

Tôi quyết định tự làm điểm chuẩn để xem cách tiếp cận bài tập tại chỗ so với các giải pháp khác đã được đưa ra trong câu hỏi này.

Mã kiểm tra:

library(microbenchmark);
library(data.table);
library(sqldf);
library(plyr);
library(dplyr);

solSpecs <- list(
    merge=list(testFuncs=list(
        inner=function(df1,df2,key) merge(df1,df2,key),
        left =function(df1,df2,key) merge(df1,df2,key,all.x=T),
        right=function(df1,df2,key) merge(df1,df2,key,all.y=T),
        full =function(df1,df2,key) merge(df1,df2,key,all=T)
    )),
    data.table.unkeyed=list(argSpec='data.table.unkeyed',testFuncs=list(
        inner=function(dt1,dt2,key) dt1[dt2,on=key,nomatch=0L,allow.cartesian=T],
        left =function(dt1,dt2,key) dt2[dt1,on=key,allow.cartesian=T],
        right=function(dt1,dt2,key) dt1[dt2,on=key,allow.cartesian=T],
        full =function(dt1,dt2,key) merge(dt1,dt2,key,all=T,allow.cartesian=T) ## calls merge.data.table()
    )),
    data.table.keyed=list(argSpec='data.table.keyed',testFuncs=list(
        inner=function(dt1,dt2) dt1[dt2,nomatch=0L,allow.cartesian=T],
        left =function(dt1,dt2) dt2[dt1,allow.cartesian=T],
        right=function(dt1,dt2) dt1[dt2,allow.cartesian=T],
        full =function(dt1,dt2) merge(dt1,dt2,all=T,allow.cartesian=T) ## calls merge.data.table()
    )),
    sqldf.unindexed=list(testFuncs=list( ## note: must pass connection=NULL to avoid running against the live DB connection, which would result in collisions with the residual tables from the last query upload
        inner=function(df1,df2,key) sqldf(paste0('select * from df1 inner join df2 using(',paste(collapse=',',key),')'),connection=NULL),
        left =function(df1,df2,key) sqldf(paste0('select * from df1 left join df2 using(',paste(collapse=',',key),')'),connection=NULL),
        right=function(df1,df2,key) sqldf(paste0('select * from df2 left join df1 using(',paste(collapse=',',key),')'),connection=NULL) ## can't do right join proper, not yet supported; inverted left join is equivalent
        ##full =function(df1,df2,key) sqldf(paste0('select * from df1 full join df2 using(',paste(collapse=',',key),')'),connection=NULL) ## can't do full join proper, not yet supported; possible to hack it with a union of left joins, but too unreasonable to include in testing
    )),
    sqldf.indexed=list(testFuncs=list( ## important: requires an active DB connection with preindexed main.df1 and main.df2 ready to go; arguments are actually ignored
        inner=function(df1,df2,key) sqldf(paste0('select * from main.df1 inner join main.df2 using(',paste(collapse=',',key),')')),
        left =function(df1,df2,key) sqldf(paste0('select * from main.df1 left join main.df2 using(',paste(collapse=',',key),')')),
        right=function(df1,df2,key) sqldf(paste0('select * from main.df2 left join main.df1 using(',paste(collapse=',',key),')')) ## can't do right join proper, not yet supported; inverted left join is equivalent
        ##full =function(df1,df2,key) sqldf(paste0('select * from main.df1 full join main.df2 using(',paste(collapse=',',key),')')) ## can't do full join proper, not yet supported; possible to hack it with a union of left joins, but too unreasonable to include in testing
    )),
    plyr=list(testFuncs=list(
        inner=function(df1,df2,key) join(df1,df2,key,'inner'),
        left =function(df1,df2,key) join(df1,df2,key,'left'),
        right=function(df1,df2,key) join(df1,df2,key,'right'),
        full =function(df1,df2,key) join(df1,df2,key,'full')
    )),
    dplyr=list(testFuncs=list(
        inner=function(df1,df2,key) inner_join(df1,df2,key),
        left =function(df1,df2,key) left_join(df1,df2,key),
        right=function(df1,df2,key) right_join(df1,df2,key),
        full =function(df1,df2,key) full_join(df1,df2,key)
    )),
    in.place=list(testFuncs=list(
        left =function(df1,df2,key) { cns <- setdiff(names(df2),key); df1[cns] <- df2[match(df1[,key],df2[,key]),cns]; df1; },
        right=function(df1,df2,key) { cns <- setdiff(names(df1),key); df2[cns] <- df1[match(df2[,key],df1[,key]),cns]; df2; }
    ))
);

getSolTypes <- function() names(solSpecs);
getJoinTypes <- function() unique(unlist(lapply(solSpecs,function(x) names(x$testFuncs))));
getArgSpec <- function(argSpecs,key=NULL) if (is.null(key)) argSpecs$default else argSpecs[[key]];

initSqldf <- function() {
    sqldf(); ## creates sqlite connection on first run, cleans up and closes existing connection otherwise
    if (exists('sqldfInitFlag',envir=globalenv(),inherits=F) && sqldfInitFlag) { ## false only on first run
        sqldf(); ## creates a new connection
    } else {
        assign('sqldfInitFlag',T,envir=globalenv()); ## set to true for the one and only time
    }; ## end if
    invisible();
}; ## end initSqldf()

setUpBenchmarkCall <- function(argSpecs,joinType,solTypes=getSolTypes(),env=parent.frame()) {
    ## builds and returns a list of expressions suitable for passing to the list argument of microbenchmark(), and assigns variables to resolve symbol references in those expressions
    callExpressions <- list();
    nms <- character();
    for (solType in solTypes) {
        testFunc <- solSpecs[[solType]]$testFuncs[[joinType]];
        if (is.null(testFunc)) next; ## this join type is not defined for this solution type
        testFuncName <- paste0('tf.',solType);
        assign(testFuncName,testFunc,envir=env);
        argSpecKey <- solSpecs[[solType]]$argSpec;
        argSpec <- getArgSpec(argSpecs,argSpecKey);
        argList <- setNames(nm=names(argSpec$args),vector('list',length(argSpec$args)));
        for (i in seq_along(argSpec$args)) {
            argName <- paste0('tfa.',argSpecKey,i);
            assign(argName,argSpec$args[[i]],envir=env);
            argList[[i]] <- if (i%in%argSpec$copySpec) call('copy',as.symbol(argName)) else as.symbol(argName);
        }; ## end for
        callExpressions[[length(callExpressions)+1L]] <- do.call(call,c(list(testFuncName),argList),quote=T);
        nms[length(nms)+1L] <- solType;
    }; ## end for
    names(callExpressions) <- nms;
    callExpressions;
}; ## end setUpBenchmarkCall()

harmonize <- function(res) {
    res <- as.data.frame(res); ## coerce to data.frame
    for (ci in which(sapply(res,is.factor))) res[[ci]] <- as.character(res[[ci]]); ## coerce factor columns to character
    for (ci in which(sapply(res,is.logical))) res[[ci]] <- as.integer(res[[ci]]); ## coerce logical columns to integer (works around sqldf quirk of munging logicals to integers)
    ##for (ci in which(sapply(res,inherits,'POSIXct'))) res[[ci]] <- as.double(res[[ci]]); ## coerce POSIXct columns to double (works around sqldf quirk of losing POSIXct class) ----- POSIXct doesn't work at all in sqldf.indexed
    res <- res[order(names(res))]; ## order columns
    res <- res[do.call(order,res),]; ## order rows
    res;
}; ## end harmonize()

checkIdentical <- function(argSpecs,solTypes=getSolTypes()) {
    for (joinType in getJoinTypes()) {
        callExpressions <- setUpBenchmarkCall(argSpecs,joinType,solTypes);
        if (length(callExpressions)<2L) next;
        ex <- harmonize(eval(callExpressions[[1L]]));
        for (i in seq(2L,len=length(callExpressions)-1L)) {
            y <- harmonize(eval(callExpressions[[i]]));
            if (!isTRUE(all.equal(ex,y,check.attributes=F))) {
                ex <<- ex;
                y <<- y;
                solType <- names(callExpressions)[i];
                stop(paste0('non-identical: ',solType,' ',joinType,'.'));
            }; ## end if
        }; ## end for
    }; ## end for
    invisible();
}; ## end checkIdentical()

testJoinType <- function(argSpecs,joinType,solTypes=getSolTypes(),metric=NULL,times=100L) {
    callExpressions <- setUpBenchmarkCall(argSpecs,joinType,solTypes);
    bm <- microbenchmark(list=callExpressions,times=times);
    if (is.null(metric)) return(bm);
    bm <- summary(bm);
    res <- setNames(nm=names(callExpressions),bm[[metric]]);
    attr(res,'unit') <- attr(bm,'unit');
    res;
}; ## end testJoinType()

testAllJoinTypes <- function(argSpecs,solTypes=getSolTypes(),metric=NULL,times=100L) {
    joinTypes <- getJoinTypes();
    resList <- setNames(nm=joinTypes,lapply(joinTypes,function(joinType) testJoinType(argSpecs,joinType,solTypes,metric,times)));
    if (is.null(metric)) return(resList);
    units <- unname(unlist(lapply(resList,attr,'unit')));
    res <- do.call(data.frame,c(list(join=joinTypes),setNames(nm=solTypes,rep(list(rep(NA_real_,length(joinTypes))),length(solTypes))),list(unit=units,stringsAsFactors=F)));
    for (i in seq_along(resList)) res[i,match(names(resList[[i]]),names(res))] <- resList[[i]];
    res;
}; ## end testAllJoinTypes()

testGrid <- function(makeArgSpecsFunc,sizes,overlaps,solTypes=getSolTypes(),joinTypes=getJoinTypes(),metric='median',times=100L) {

    res <- expand.grid(size=sizes,overlap=overlaps,joinType=joinTypes,stringsAsFactors=F);
    res[solTypes] <- NA_real_;
    res$unit <- NA_character_;
    for (ri in seq_len(nrow(res))) {

        size <- res$size[ri];
        overlap <- res$overlap[ri];
        joinType <- res$joinType[ri];

        argSpecs <- makeArgSpecsFunc(size,overlap);

        checkIdentical(argSpecs,solTypes);

        cur <- testJoinType(argSpecs,joinType,solTypes,metric,times);
        res[ri,match(names(cur),names(res))] <- cur;
        res$unit[ri] <- attr(cur,'unit');

    }; ## end for

    res;

}; ## end testGrid()

Đây là điểm chuẩn của ví dụ dựa trên OP mà tôi đã trình bày trước đó:

## OP's example, supplemented with a non-matching row in df2
argSpecs <- list(
    default=list(copySpec=1:2,args=list(
        df1 <- data.frame(CustomerId=1:6,Product=c(rep('Toaster',3L),rep('Radio',3L))),
        df2 <- data.frame(CustomerId=c(2L,4L,6L,7L),State=c(rep('Alabama',2L),'Ohio','Texas')),
        'CustomerId'
    )),
    data.table.unkeyed=list(copySpec=1:2,args=list(
        as.data.table(df1),
        as.data.table(df2),
        'CustomerId'
    )),
    data.table.keyed=list(copySpec=1:2,args=list(
        setkey(as.data.table(df1),CustomerId),
        setkey(as.data.table(df2),CustomerId)
    ))
);
## prepare sqldf
initSqldf();
sqldf('create index df1_key on df1(CustomerId);'); ## upload and create an sqlite index on df1
sqldf('create index df2_key on df2(CustomerId);'); ## upload and create an sqlite index on df2

checkIdentical(argSpecs);

testAllJoinTypes(argSpecs,metric='median');
##    join    merge data.table.unkeyed data.table.keyed sqldf.unindexed sqldf.indexed      plyr    dplyr in.place         unit
## 1 inner  644.259           861.9345          923.516        9157.752      1580.390  959.2250 270.9190       NA microseconds
## 2  left  713.539           888.0205          910.045        8820.334      1529.714  968.4195 270.9185 224.3045 microseconds
## 3 right 1221.804           909.1900          923.944        8930.668      1533.135 1063.7860 269.8495 218.1035 microseconds
## 4  full 1302.203          3107.5380         3184.729              NA            NA 1593.6475 270.7055       NA microseconds

Ở đây tôi điểm chuẩn trên dữ liệu đầu vào ngẫu nhiên, thử các thang đo khác nhau và các mẫu khóa khác nhau chồng chéo giữa hai bảng đầu vào. Điểm chuẩn này vẫn bị hạn chế trong trường hợp khóa số nguyên cột đơn. Đồng thời, để đảm bảo rằng giải pháp tại chỗ sẽ hoạt động cho cả các phép nối trái và phải của cùng một bảng, tất cả dữ liệu thử nghiệm ngẫu nhiên đều sử dụng tính chính xác 0..1:0..1. Điều này được thực hiện bằng cách lấy mẫu mà không thay thế cột khóa của data.frame đầu tiên khi tạo cột khóa của data.frame thứ hai.

makeArgSpecs.singleIntegerKey.optionalOneToOne <- function(size,overlap) {

    com <- as.integer(size*overlap);

    argSpecs <- list(
        default=list(copySpec=1:2,args=list(
            df1 <- data.frame(id=sample(size),y1=rnorm(size),y2=rnorm(size)),
            df2 <- data.frame(id=sample(c(if (com>0L) sample(df1$id,com) else integer(),seq(size+1L,len=size-com))),y3=rnorm(size),y4=rnorm(size)),
            'id'
        )),
        data.table.unkeyed=list(copySpec=1:2,args=list(
            as.data.table(df1),
            as.data.table(df2),
            'id'
        )),
        data.table.keyed=list(copySpec=1:2,args=list(
            setkey(as.data.table(df1),id),
            setkey(as.data.table(df2),id)
        ))
    );
    ## prepare sqldf
    initSqldf();
    sqldf('create index df1_key on df1(id);'); ## upload and create an sqlite index on df1
    sqldf('create index df2_key on df2(id);'); ## upload and create an sqlite index on df2

    argSpecs;

}; ## end makeArgSpecs.singleIntegerKey.optionalOneToOne()

## cross of various input sizes and key overlaps
sizes <- c(1e1L,1e3L,1e6L);
overlaps <- c(0.99,0.5,0.01);
system.time({ res <- testGrid(makeArgSpecs.singleIntegerKey.optionalOneToOne,sizes,overlaps); });
##     user   system  elapsed
## 22024.65 12308.63 34493.19

Tôi đã viết một số mã để tạo ra các lô log-log của các kết quả trên. Tôi đã tạo ra một âm mưu riêng cho từng tỷ lệ trùng lặp. Hơi lộn xộn một chút, nhưng tôi thích có tất cả các loại giải pháp và các loại tham gia được trình bày trong cùng một cốt truyện.

Tôi đã sử dụng phép nội suy spline để hiển thị một đường cong mượt mà cho mỗi kết hợp loại giải pháp / nối, được vẽ bằng các ký hiệu pch riêng. Loại tham gia được ghi lại bằng biểu tượng pch, sử dụng dấu chấm cho dấu ngoặc góc bên trong, bên trái và bên phải cho bên trái và bên phải và một viên kim cương cho đầy đủ. Loại giải pháp được ghi lại bằng màu sắc như trong truyền thuyết.

plotRes <- function(res,titleFunc,useFloor=F) {
    solTypes <- setdiff(names(res),c('size','overlap','joinType','unit')); ## derive from res
    normMult <- c(microseconds=1e-3,milliseconds=1); ## normalize to milliseconds
    joinTypes <- getJoinTypes();
    cols <- c(merge='purple',data.table.unkeyed='blue',data.table.keyed='#00DDDD',sqldf.unindexed='brown',sqldf.indexed='orange',plyr='red',dplyr='#00BB00',in.place='magenta');
    pchs <- list(inner=20L,left='<',right='>',full=23L);
    cexs <- c(inner=0.7,left=1,right=1,full=0.7);
    NP <- 60L;
    ord <- order(decreasing=T,colMeans(res[res$size==max(res$size),solTypes],na.rm=T));
    ymajors <- data.frame(y=c(1,1e3),label=c('1ms','1s'),stringsAsFactors=F);
    for (overlap in unique(res$overlap)) {
        x1 <- res[res$overlap==overlap,];
        x1[solTypes] <- x1[solTypes]*normMult[x1$unit]; x1$unit <- NULL;
        xlim <- c(1e1,max(x1$size));
        xticks <- 10^seq(log10(xlim[1L]),log10(xlim[2L]));
        ylim <- c(1e-1,10^((if (useFloor) floor else ceiling)(log10(max(x1[solTypes],na.rm=T))))); ## use floor() to zoom in a little more, only sqldf.unindexed will break above, but xpd=NA will keep it visible
        yticks <- 10^seq(log10(ylim[1L]),log10(ylim[2L]));
        yticks.minor <- rep(yticks[-length(yticks)],each=9L)*1:9;
        plot(NA,xlim=xlim,ylim=ylim,xaxs='i',yaxs='i',axes=F,xlab='size (rows)',ylab='time (ms)',log='xy');
        abline(v=xticks,col='lightgrey');
        abline(h=yticks.minor,col='lightgrey',lty=3L);
        abline(h=yticks,col='lightgrey');
        axis(1L,xticks,parse(text=sprintf('10^%d',as.integer(log10(xticks)))));
        axis(2L,yticks,parse(text=sprintf('10^%d',as.integer(log10(yticks)))),las=1L);
        axis(4L,ymajors$y,ymajors$label,las=1L,tick=F,cex.axis=0.7,hadj=0.5);
        for (joinType in rev(joinTypes)) { ## reverse to draw full first, since it's larger and would be more obtrusive if drawn last
            x2 <- x1[x1$joinType==joinType,];
            for (solType in solTypes) {
                if (any(!is.na(x2[[solType]]))) {
                    xy <- spline(x2$size,x2[[solType]],xout=10^(seq(log10(x2$size[1L]),log10(x2$size[nrow(x2)]),len=NP)));
                    points(xy$x,xy$y,pch=pchs[[joinType]],col=cols[solType],cex=cexs[joinType],xpd=NA);
                }; ## end if
            }; ## end for
        }; ## end for
        ## custom legend
        ## due to logarithmic skew, must do all distance calcs in inches, and convert to user coords afterward
        ## the bottom-left corner of the legend will be defined in normalized figure coords, although we can convert to inches immediately
        leg.cex <- 0.7;
        leg.x.in <- grconvertX(0.275,'nfc','in');
        leg.y.in <- grconvertY(0.6,'nfc','in');
        leg.x.user <- grconvertX(leg.x.in,'in');
        leg.y.user <- grconvertY(leg.y.in,'in');
        leg.outpad.w.in <- 0.1;
        leg.outpad.h.in <- 0.1;
        leg.midpad.w.in <- 0.1;
        leg.midpad.h.in <- 0.1;
        leg.sol.w.in <- max(strwidth(solTypes,'in',leg.cex));
        leg.sol.h.in <- max(strheight(solTypes,'in',leg.cex))*1.5; ## multiplication factor for greater line height
        leg.join.w.in <- max(strheight(joinTypes,'in',leg.cex))*1.5; ## ditto
        leg.join.h.in <- max(strwidth(joinTypes,'in',leg.cex));
        leg.main.w.in <- leg.join.w.in*length(joinTypes);
        leg.main.h.in <- leg.sol.h.in*length(solTypes);
        leg.x2.user <- grconvertX(leg.x.in+leg.outpad.w.in*2+leg.main.w.in+leg.midpad.w.in+leg.sol.w.in,'in');
        leg.y2.user <- grconvertY(leg.y.in+leg.outpad.h.in*2+leg.main.h.in+leg.midpad.h.in+leg.join.h.in,'in');
        leg.cols.x.user <- grconvertX(leg.x.in+leg.outpad.w.in+leg.join.w.in*(0.5+seq(0L,length(joinTypes)-1L)),'in');
        leg.lines.y.user <- grconvertY(leg.y.in+leg.outpad.h.in+leg.main.h.in-leg.sol.h.in*(0.5+seq(0L,length(solTypes)-1L)),'in');
        leg.sol.x.user <- grconvertX(leg.x.in+leg.outpad.w.in+leg.main.w.in+leg.midpad.w.in,'in');
        leg.join.y.user <- grconvertY(leg.y.in+leg.outpad.h.in+leg.main.h.in+leg.midpad.h.in,'in');
        rect(leg.x.user,leg.y.user,leg.x2.user,leg.y2.user,col='white');
        text(leg.sol.x.user,leg.lines.y.user,solTypes[ord],cex=leg.cex,pos=4L,offset=0);
        text(leg.cols.x.user,leg.join.y.user,joinTypes,cex=leg.cex,pos=4L,offset=0,srt=90); ## srt rotation applies *after* pos/offset positioning
        for (i in seq_along(joinTypes)) {
            joinType <- joinTypes[i];
            points(rep(leg.cols.x.user[i],length(solTypes)),ifelse(colSums(!is.na(x1[x1$joinType==joinType,solTypes[ord]]))==0L,NA,leg.lines.y.user),pch=pchs[[joinType]],col=cols[solTypes[ord]]);
        }; ## end for
        title(titleFunc(overlap));
        readline(sprintf('overlap %.02f',overlap));
    }; ## end for
}; ## end plotRes()

titleFunc <- function(overlap) sprintf('R merge solutions: single-column integer key, 0..1:0..1 cardinality, %d%% overlap',as.integer(overlap*100));
plotRes(res,titleFunc,T);

R-merge-điểm-đơn-cột-số nguyên-khóa-tùy chọn-một-một-99

R-merge-điểm-đơn-cột-số nguyên-khóa-tùy chọn-một-một-một-50

R-merge-điểm-đơn-cột-số nguyên-khóa-tùy chọn-một-một-một


Đây là một điểm chuẩn quy mô lớn thứ hai có nhiệm vụ nặng nề hơn, liên quan đến số lượng và loại cột chính, cũng như tính chính xác. Đối với điểm chuẩn này, tôi sử dụng ba cột chính: một ký tự, một số nguyên và một logic, không có giới hạn về số lượng thẻ (nghĩa là, 0..*:0..*). (Nói chung, không nên xác định các cột chính có giá trị gấp đôi hoặc phức tạp do các biến chứng so sánh dấu phẩy động và về cơ bản không ai sử dụng loại thô, ít hơn nhiều cho các cột chính, vì vậy tôi đã không đưa các loại đó vào khóa Ngoài ra, vì lợi ích của thông tin, ban đầu tôi đã thử sử dụng bốn cột chính bằng cách bao gồm một cột khóa POSIXct, nhưng loại POSIXct không chơi tốt với sqldf.indexedgiải pháp vì một số lý do, có thể do dị thường so sánh điểm nổi, vì vậy tôi loại bỏ nó.)

makeArgSpecs.assortedKey.optionalManyToMany <- function(size,overlap,uniquePct=75) {

    ## number of unique keys in df1
    u1Size <- as.integer(size*uniquePct/100);

    ## (roughly) divide u1Size into bases, so we can use expand.grid() to produce the required number of unique key values with repetitions within individual key columns
    ## use ceiling() to ensure we cover u1Size; will truncate afterward
    u1SizePerKeyColumn <- as.integer(ceiling(u1Size^(1/3)));

    ## generate the unique key values for df1
    keys1 <- expand.grid(stringsAsFactors=F,
        idCharacter=replicate(u1SizePerKeyColumn,paste(collapse='',sample(letters,sample(4:12,1L),T))),
        idInteger=sample(u1SizePerKeyColumn),
        idLogical=sample(c(F,T),u1SizePerKeyColumn,T)
        ##idPOSIXct=as.POSIXct('2016-01-01 00:00:00','UTC')+sample(u1SizePerKeyColumn)
    )[seq_len(u1Size),];

    ## rbind some repetitions of the unique keys; this will prepare one side of the many-to-many relationship
    ## also scramble the order afterward
    keys1 <- rbind(keys1,keys1[sample(nrow(keys1),size-u1Size,T),])[sample(size),];

    ## common and unilateral key counts
    com <- as.integer(size*overlap);
    uni <- size-com;

    ## generate some unilateral keys for df2 by synthesizing outside of the idInteger range of df1
    keys2 <- data.frame(stringsAsFactors=F,
        idCharacter=replicate(uni,paste(collapse='',sample(letters,sample(4:12,1L),T))),
        idInteger=u1SizePerKeyColumn+sample(uni),
        idLogical=sample(c(F,T),uni,T)
        ##idPOSIXct=as.POSIXct('2016-01-01 00:00:00','UTC')+u1SizePerKeyColumn+sample(uni)
    );

    ## rbind random keys from df1; this will complete the many-to-many relationship
    ## also scramble the order afterward
    keys2 <- rbind(keys2,keys1[sample(nrow(keys1),com,T),])[sample(size),];

    ##keyNames <- c('idCharacter','idInteger','idLogical','idPOSIXct');
    keyNames <- c('idCharacter','idInteger','idLogical');
    ## note: was going to use raw and complex type for two of the non-key columns, but data.table doesn't seem to fully support them
    argSpecs <- list(
        default=list(copySpec=1:2,args=list(
            df1 <- cbind(stringsAsFactors=F,keys1,y1=sample(c(F,T),size,T),y2=sample(size),y3=rnorm(size),y4=replicate(size,paste(collapse='',sample(letters,sample(4:12,1L),T)))),
            df2 <- cbind(stringsAsFactors=F,keys2,y5=sample(c(F,T),size,T),y6=sample(size),y7=rnorm(size),y8=replicate(size,paste(collapse='',sample(letters,sample(4:12,1L),T)))),
            keyNames
        )),
        data.table.unkeyed=list(copySpec=1:2,args=list(
            as.data.table(df1),
            as.data.table(df2),
            keyNames
        )),
        data.table.keyed=list(copySpec=1:2,args=list(
            setkeyv(as.data.table(df1),keyNames),
            setkeyv(as.data.table(df2),keyNames)
        ))
    );
    ## prepare sqldf
    initSqldf();
    sqldf(paste0('create index df1_key on df1(',paste(collapse=',',keyNames),');')); ## upload and create an sqlite index on df1
    sqldf(paste0('create index df2_key on df2(',paste(collapse=',',keyNames),');')); ## upload and create an sqlite index on df2

    argSpecs;

}; ## end makeArgSpecs.assortedKey.optionalManyToMany()

sizes <- c(1e1L,1e3L,1e5L); ## 1e5L instead of 1e6L to respect more heavy-duty inputs
overlaps <- c(0.99,0.5,0.01);
solTypes <- setdiff(getSolTypes(),'in.place');
system.time({ res <- testGrid(makeArgSpecs.assortedKey.optionalManyToMany,sizes,overlaps,solTypes); });
##     user   system  elapsed
## 38895.50   784.19 39745.53

Các ô kết quả, sử dụng cùng một mã âm mưu được đưa ra ở trên:

titleFunc <- function(overlap) sprintf('R merge solutions: character/integer/logical key, 0..*:0..* cardinality, %d%% overlap',as.integer(overlap*100));
plotRes(res,titleFunc,F);

R-merge-điểm chuẩn các loại-khóa-tùy chọn-nhiều-nhiều-99

R-merge-điểm chuẩn các loại-khóa-tùy chọn-nhiều-nhiều-50

R-merge-điểm chuẩn các loại-khóa-tùy chọn-nhiều-nhiều-1


phân tích rất hay, nhưng thật đáng tiếc khi bạn đặt tỷ lệ từ 10 ^ 1 đến 10 ^ 6, đó là những bộ nhỏ đến mức chênh lệch tốc độ gần như không liên quan. 10 ^ 6 đến 10 ^ 8 sẽ rất thú vị để xem!
jangorecki

1
Tôi cũng phát hiện ra bạn bao gồm thời gian cưỡng chế lớp trong điểm chuẩn khiến nó không hợp lệ cho hoạt động tham gia.
jangorecki

8
  1. Sử dụng merge hàm, chúng ta có thể chọn biến của bảng bên trái hoặc bảng bên phải, giống như tất cả chúng ta quen thuộc với câu lệnh select trong SQL (EX: Chọn a. * ... hoặc Chọn b. * Từ .....)
  2. Chúng ta phải thêm mã bổ sung sẽ tập hợp con từ bảng mới được nối.

    • SQL: - select a.* from df1 a inner join df2 b on a.CustomerId=b.CustomerId

    • R: - merge(df1, df2, by.x = "CustomerId", by.y = "CustomerId")[,names(df1)]

Cùng một cách

  • SQL: - select b.* from df1 a inner join df2 b on a.CustomerId=b.CustomerId

  • R: - merge(df1, df2, by.x = "CustomerId", by.y = "CustomerId")[,names(df2)]


7

Cho tham gia vào một bên trên tất cả các cột, bạn cũng có thể sử dụng fintersecttừ data.table -package hoặc intersecttừ dplyr -package như một thay thế cho mergemà không chỉ định các by-columns. điều này sẽ cho các hàng bằng nhau giữa hai dataframes:

merge(df1, df2)
#   V1 V2
# 1  B  2
# 2  C  3
dplyr::intersect(df1, df2)
#   V1 V2
# 1  B  2
# 2  C  3
data.table::fintersect(setDT(df1), setDT(df2))
#    V1 V2
# 1:  B  2
# 2:  C  3

Dữ liệu ví dụ:

df1 <- data.frame(V1 = LETTERS[1:4], V2 = 1:4)
df2 <- data.frame(V1 = LETTERS[2:3], V2 = 2:3)

5

Cập nhật tham gia. Một phép nối kiểu SQL quan trọng khác là "phép nối cập nhật " trong đó các cột trong một bảng được cập nhật (hoặc được tạo) bằng cách sử dụng bảng khác.

Sửa đổi bảng ví dụ của OP ...

sales = data.frame(
  CustomerId = c(1, 1, 1, 3, 4, 6), 
  Year = 2000:2005,
  Product = c(rep("Toaster", 3), rep("Radio", 3))
)
cust = data.frame(
  CustomerId = c(1, 1, 4, 6), 
  Year = c(2001L, 2002L, 2002L, 2002L),
  State = state.name[1:4]
)

sales
# CustomerId Year Product
#          1 2000 Toaster
#          1 2001 Toaster
#          1 2002 Toaster
#          3 2003   Radio
#          4 2004   Radio
#          6 2005   Radio

cust
# CustomerId Year    State
#          1 2001  Alabama
#          1 2002   Alaska
#          4 2002  Arizona
#          6 2002 Arkansas

Giả sử chúng tôi muốn thêm trạng thái của khách hàng custvào bảng mua hàng sales, bỏ qua cột năm. Với cơ sở R, chúng ta có thể xác định các hàng khớp và sau đó sao chép các giá trị qua:

sales$State <- cust$State[ match(sales$CustomerId, cust$CustomerId) ]

# CustomerId Year Product    State
#          1 2000 Toaster  Alabama
#          1 2001 Toaster  Alabama
#          1 2002 Toaster  Alabama
#          3 2003   Radio     <NA>
#          4 2004   Radio  Arizona
#          6 2005   Radio Arkansas

# cleanup for the next example
sales$State <- NULL

Như có thể thấy ở đây, matchchọn hàng khớp đầu tiên từ bảng khách hàng.


Cập nhật tham gia với nhiều cột. Cách tiếp cận ở trên hoạt động tốt khi chúng tôi chỉ tham gia vào một cột duy nhất và hài lòng với trận đấu đầu tiên. Giả sử chúng ta muốn năm đo lường trong bảng khách hàng khớp với năm bán hàng.

Như câu trả lời của @ bgoldst, matchvớiinteraction có thể là một lựa chọn cho trường hợp này. Nói thẳng hơn, người ta có thể sử dụng data.table:

library(data.table)
setDT(sales); setDT(cust)

sales[, State := cust[sales, on=.(CustomerId, Year), x.State]]

#    CustomerId Year Product   State
# 1:          1 2000 Toaster    <NA>
# 2:          1 2001 Toaster Alabama
# 3:          1 2002 Toaster  Alaska
# 4:          3 2003   Radio    <NA>
# 5:          4 2004   Radio    <NA>
# 6:          6 2005   Radio    <NA>

# cleanup for next example
sales[, State := NULL]

Tham gia cập nhật.Thay phiên, chúng tôi có thể muốn lấy trạng thái cuối cùng mà khách hàng được tìm thấy trong:

sales[, State := cust[sales, on=.(CustomerId, Year), roll=TRUE, x.State]]

#    CustomerId Year Product    State
# 1:          1 2000 Toaster     <NA>
# 2:          1 2001 Toaster  Alabama
# 3:          1 2002 Toaster   Alaska
# 4:          3 2003   Radio     <NA>
# 5:          4 2004   Radio  Arizona
# 6:          6 2005   Radio Arkansas

Ba ví dụ trên tập trung vào việc tạo / thêm một cột mới. Xem Câu hỏi thường gặp về R có liên quan để biết ví dụ về cập nhật / sửa đổi cột hiện có.

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.