Hai bảng, một khóa
Dữ liệu thực tế hiếm khi nằm gọn trong một bảng. Khách hàng ở một data frame, đơn hàng của họ ở một data frame khác, và câu hỏi "mỗi khách hàng đã chi bao nhiêu?" cần cả hai. Thứ nối chúng lại là khóa (key): một cột có mặt ở cả hai bảng và cùng chỉ một đối tượng. Gộp bảng theo khóa gọi là join (cách gọi của SQL) hoặc merge (cách gọi của base R); đó là cùng một phép toán.
Đây là cặp bảng được dùng trong suốt trang này. Để ý rằng khách hàng 5 có đặt hàng nhưng không có trong bảng khách hàng, còn khách hàng 2 và 4 chưa từng đặt hàng:
Những chỗ không khớp này là cố ý: cách một phép join xử lý các hàng không có cặp chính là điều phân biệt các loại join.
merge(): mặc định là inner join
merge(x, y, by = "key") ghép các hàng có khóa bằng nhau và nối cột của chúng lại. Mặc định hàm này chỉ giữ những khóa có mặt ở cả hai bảng, tức là inner join:
Kết quả có ba hàng. Ana xuất hiện hai lần: cô ấy có hai đơn hàng, và phép join tạo ra một hàng kết quả cho mỗi cặp khớp nhau. Ben và Dana biến mất (không có đơn hàng), đơn hàng bí ẩn của khách hàng 5 cũng vậy (không có khách hàng). Inner join âm thầm loại bỏ các hàng không khớp ở cả hai bảng: rất đúng khi bạn chỉ quan tâm đến các cặp đầy đủ, nhưng thành bug mất dữ liệu không ai hay khi bạn không muốn vậy.
Left, right và full join: all.x, all.y, all
Để giữ các hàng không khớp, hãy chỉ ra bảng nào không được mất hàng. all.x = TRUE giữ mọi hàng của bảng thứ nhất, đó là left join, loại join được dùng nhiều nhất trong thực tế:
Giờ Ben và Dana vẫn còn, với NA ở cột amount, nghĩa là "khách hàng này có tồn tại; không có đơn hàng nào khớp." Những NA đó mang thông tin chứ không phải rác: is.na(result$amount) chính là danh sách khách hàng chưa từng đặt hàng (tài liệu về giá trị thiếu hướng dẫn cách làm việc với chúng). Các biến thể còn lại là cùng ý tưởng nhưng theo hướng khác: all.y = TRUE giữ mọi hàng của bảng thứ hai (right join; đơn hàng của khách hàng 5 không xác định sẽ được giữ với NA ở name), và all = TRUE giữ tất cả từ cả hai phía (full join).
Hãy chọn bằng cách tự hỏi: hàng của bảng nào mình không được phép làm mất? Bổ sung dữ liệu tra cứu cho một bảng chính: left join, bảng chính đặt trước. Kiểm tra cả hai chiều: full join.
Khóa khác tên: by.x và by.y
Các bảng hiếm khi thống nhất cách đặt tên: id ở bảng này, customer_id ở bảng kia. Đừng đổi tên; hãy cho merge() biết cả hai tên:
Kết quả giữ tên khóa của bảng thứ nhất. Một điểm liên quan: nếu hai bảng có chung tên cột không phải khóa (ví dụ cả hai đều có date), merge sẽ thêm hậu tố thành date.x và date.y. Hãy đổi tên chúng ngay, vì đọc rất khó chịu.
Các hàm join của dplyr
dplyr cho mỗi loại join một động từ riêng, nên ý định nằm ngay trong tên hàm thay vì trong các đối số cờ (khối tĩnh; sandbox chỉ chạy base R):
library(dplyr)
left_join(customers, orders, by = "id")
inner_join(customers, orders, by = "id")
full_join(customers, orders, by = "id")
left_join(customers, orders, by = c("id" = "customer_id")) # different names
Ngoài chuyện dễ đọc, left_join() giữ nguyên thứ tự hàng của bảng thứ nhất (merge() sắp xếp lại theo khóa) và cảnh báo rõ ràng khi gặp ghép nhiều-nhiều; xem phần giới thiệu dplyr để biết cả nhóm động từ.
Thành viên bị đánh giá thấp là anti_join(): hàm này trả về các hàng của bảng thứ nhất không có cặp trong bảng thứ hai, không thêm cột nào, chỉ là phần còn sót lại:
anti_join(customers, orders, by = "id") # customers who never ordered
Câu hỏi "những hàng nào không khớp được?" xuất hiện liên tục khi làm sạch dữ liệu (ID không khớp, bản ghi mồ côi, tra cứu thất bại), và anti_join() trả lời nó trong một lần gọi, trong khi base R cần customers[!(customers$id %in% orders$id), ].
Nối hàng: rbind()
Join gộp cột của hai bảng. Còn khi bạn có hai lô hàng thuộc cùng một loại (đơn hàng tháng 1 và tháng 2), bạn xếp chồng chúng bằng rbind():
Yêu cầu rất chặt: cả hai data frame phải có cùng tên cột (thứ tự tùy ý, vì rbind ghép theo tên). Thiếu hoặc thừa một cột sẽ báo lỗi chứ không điền NA. Khi các lô đã lệch cột nhau, bind_rows() của dplyr dễ tính hơn: nó căn theo tên và điền NA vào chỗ trống.
Bùng nổ khóa trùng lặp
Tai nạn join kinh điển: những khóa bạn tưởng là duy nhất nhưng hóa ra không phải, ở cả hai bảng. Mỗi lần khớp lại ghép cặp với mọi lần khớp khác, khiến số hàng nhân lên:
Hai hàng join với hai hàng cho ra bốn: mỗi x ghép với mỗi y. Với dữ liệu thực, đây là cách một bảng 10.000 hàng biến thành 3 triệu hàng và tổng doanh thu bị nhân đôi. merge() làm việc này mà không báo gì. Cách phòng tránh là một thói quen: trước khi join, kiểm tra khóa bạn cho là duy nhất (anyDuplicated(customers$id) phải bằng 0), và sau khi join, đối chiếu nrow() với con số bạn mong đợi.
Những điều cần nhớ
merge(x, y, by = "key")là inner join: các hàng không khớp âm thầm biến mất.all.x = TRUE(left),all.y = TRUE(right),all = TRUE(full) giữ lại các hàng không khớp và điềnNA.- Khóa khác tên:
by.x/by.ytrong base R,by = c("a" = "b")trong dplyr. - dplyr đặt tên riêng cho từng loại join;
anti_join(), các hàng không khớp, là hàm bị đánh giá thấp. rbind()xếp chồng các bảng cùng cấu trúc; tên cột phải khớp nhau.- Khóa trùng lặp âm thầm nhân số hàng: kiểm tra
anyDuplicated()trước,nrow()sau.
Tiếp theo: chuyển đổi dữ liệu giữa định dạng rộng và dài với pivot_longer() và pivot_wider().
Câu hỏi thường gặp
Làm sao để merge hai data frame trong R?
Dùng merge(x, y, by = "key") với cột khóa chung. Mặc định hàm này thực hiện inner join: chỉ những hàng có khóa xuất hiện ở cả hai data frame mới được giữ lại. Thêm all.x = TRUE để có left join, all.y = TRUE để có right join, hoặc all = TRUE để có full join.
Làm sao để left join trong R?
Với base R: merge(x, y, by = "key", all.x = TRUE) giữ mọi hàng của x và điền NA vào các cột của y ở những chỗ không khớp. Với dplyr: left_join(x, y, by = "key") cho kết quả tương tự, đồng thời giữ nguyên thứ tự hàng ban đầu của x, điều mà merge() không làm.
Làm sao để merge data frame khi cột khóa có tên khác nhau?
Cho merge() biết cả hai tên: merge(x, y, by.x = "id", by.y = "customer_id"). Trong dplyr, cách viết tương đương là left_join(x, y, by = c("id" = "customer_id")), hoặc với hàm hỗ trợ hiện đại, by = join_by(id == customer_id).
merge và rbind trong R khác nhau thế nào?
Hai hàm gộp theo hai chiều khác nhau. merge() ghép các hàng của hai bảng theo khóa rồi gộp cột của chúng, tức là một phép join. rbind() xếp các hàng của bảng này xuống dưới bảng kia, và hai bảng phải có cùng tên cột. Các tệp theo tháng có cột giống hệt nhau thì dùng rbind(); khách hàng cộng với đơn hàng thì dùng merge().