inner_join()
left_join()
right_join()
full_join()
semi_join()
anti_join()
nest_join()
cross_join()dplyr join 函數完整介紹:資料合併時一定要懂的 8 個函數
在資料分析工作中,我們很常會遇到「資料分散在不同表格」的情況。
例如:
一張表是學生基本資料,另一張表是考試成績; 一張表是病人基本資料,另一張表是檢驗數據; 一張表是建築物資訊,另一張表是逐時用電量。
這時候,我們就需要把不同資料表依照某些共同欄位合併起來。在 R 的 dplyr 套件中,最常用來處理資料表合併的函數,就是一系列以 _join() 結尾的函數。
本文會介紹 dplyr 中常見的 join 函數,包括:
另外,也會介紹新版 dplyr 常用的 join_by(),用來指定更清楚的合併條件。
一、什麼是 join?
join 的意思是「依照共同欄位,把兩張資料表合併」。
假設我們有兩張資料表:
第一張是學生基本資料:
library(dplyr)
student <- tibble(
id = c(1, 2, 3, 4),
name = c("Amy", "Ben", "Cathy", "David"),
class = c("A", "A", "B", "B")
)
student| id | name | class |
|---|---|---|
| 1 | Amy | A |
| 2 | Ben | A |
| 3 | Cathy | B |
| 4 | David | B |
第二張是數學成績:
score <- tibble(
id = c(1, 2, 2, 5),
math = c(90, 80, 85, 70)
)
score| id | math |
|---|---|
| 1 | 90 |
| 2 | 80 |
| 2 | 85 |
| 5 | 70 |
兩張表都有共同欄位 id,所以我們可以用 id 作為合併依據。
二、dplyr join 函數總覽
| 函數 | 會保留哪些資料? | 會不會新增右表欄位? | 常見用途 |
|---|---|---|---|
inner_join() |
只保留左右兩表都有配對的資料 | 會 | 取交集 |
left_join() |
保留左表全部資料 | 會 | 最常用,主表補欄位 |
right_join() |
保留右表全部資料 | 會 | 以右表為主 |
full_join() |
保留左右兩表全部資料 | 會 | 合併完整資料 |
semi_join() |
保留左表中,在右表有配對的列 | 不會 | 篩出有對應資料者 |
anti_join() |
保留左表中,在右表沒有配對的列 | 不會 | 找缺漏、未配對資料 |
nest_join() |
保留左表,右表配對結果放入巢狀欄位 | 會 | 一對多資料整理 |
cross_join() |
左表每列與右表每列全部組合 | 會 | 產生所有組合 |
## 三、inner_join():只保留兩邊都有的資料
inner_join() 會保留兩張表都有配對成功的資料。
inner_join(student, score, by = "id")結果:
| id | name | class | math |
|---|---|---|---|
| 1 | Amy | A | 90 |
| 2 | Ben | A | 80 |
| 2 | Ben | A | 85 |
這裡只有 id = 1 和 id = 2 同時出現在 student 與 score 兩張表中,所以結果只保留這些資料。
需要注意的是,id = 3 和 id = 4 雖然在學生表中存在,但在成績表中沒有對應資料,所以被排除。 而 id = 5 雖然在成績表中存在,但在學生表中沒有對應資料,也不會出現在結果中。
適合情境
inner_join() 適合用在你只想分析「兩張表都有資料」的對象。
例如:
student %>%
inner_join(score, by = "id")可以用來找出「有學生基本資料,也有成績資料」的人。
四、left_join():保留左表全部資料
left_join() 是資料分析中最常用的 join 函數。
它會保留左邊資料表的所有資料,再把右邊資料表中能配對的欄位補進來。
left_join(student, score, by = "id")結果:
| id | name | class | math |
|---|---|---|---|
| 1 | Amy | A | 90 |
| 2 | Ben | A | 80 |
| 2 | Ben | A | 85 |
| 3 | Cathy | B | NA |
| 4 | David | B | NA |
這裡 student 是左表,所以所有學生都會被保留。
其中 id = 3 和 id = 4 在 score 中沒有成績資料,因此 math 會顯示為 NA。
適合情境
left_join() 特別適合用在「以主資料表為主,補上其他資料」的情況。
例如:
student %>%
left_join(score, by = "id")意思是:我想保留完整學生名單,並且把有成績的人補上成績。
這在實務資料分析中非常常見。
例如公共衛生資料中,可能會有:
patient_basic %>%
left_join(lab_data, by = "patient_id")意思是以病人基本資料為主,補上檢驗資料。
五、right_join():保留右表全部資料
right_join() 剛好和 left_join() 相反。
它會保留右邊資料表的所有資料,再把左邊資料表中能配對的欄位補進來。
right_join(student, score, by = "id")結果:
| id | name | class | math |
|---|---|---|---|
| 1 | Amy | A | 90 |
| 2 | Ben | A | 80 |
| 2 | Ben | A | 85 |
| 5 | NA | NA | 70 |
這裡 score 是右表,所以所有成績資料都會被保留。
其中 id = 5 在 score 中有成績,但在 student 中找不到學生基本資料,因此 name 和 class 會是 NA。
適合情境
right_join() 的使用頻率通常比 left_join() 低,因為多數人習慣把主要資料表放在左邊,然後使用 left_join()。
例如:
right_join(student, score, by = "id")其實也可以改寫成:
left_join(score, student, by = "id")所以在實務上,我通常會建議優先使用 left_join(),可讀性比較好。
六、full_join():保留左右兩表全部資料
full_join() 會保留左右兩張表的所有資料。
full_join(student, score, by = "id")結果:
| id | name | class | math |
|---|---|---|---|
| 1 | Amy | A | 90 |
| 2 | Ben | A | 80 |
| 2 | Ben | A | 85 |
| 3 | Cathy | B | NA |
| 4 | David | B | NA |
| 5 | NA | NA | 70 |
可以看到:
id = 1 和 id = 2 兩邊都有,所以完整合併。 id = 3 和 id = 4 只有學生資料,沒有成績。 id = 5 只有成績資料,沒有學生基本資料。
適合情境
full_join() 適合用在你想保留兩邊所有資料的時候。
例如你有兩份不同來源的名單:
full_join(list_a, list_b, by = "id")可以用來整理出完整名單,並檢查哪些資料只存在於其中一邊。
七、semi_join():只保留左表中有配對的資料
semi_join() 和前面幾個 join 有一個很大的差異:
它不會把右表的欄位加進來,只會用右表來篩選左表。
semi_join(student, score, by = "id")結果:
| id | name | class |
|---|---|---|
| 1 | Amy | A |
| 2 | Ben | A |
這個結果的意思是:
從
student中,找出那些在score裡面也有出現的學生。
注意,結果中沒有 math 欄位,因為 semi_join() 只負責篩選,不負責新增欄位。
適合情境
semi_join() 適合用來找出「有對應資料」的資料列。
例如:
student %>%
semi_join(score, by = "id")意思是找出有成績紀錄的學生。
在實務上,也可以用來找出有檢驗紀錄的病人:
patient_basic %>%
semi_join(lab_data, by = "patient_id")八、anti_join():找出左表中沒有配對的資料
anti_join() 是資料清理時非常實用的函數。
它會保留左表中「在右表找不到配對」的資料。
anti_join(student, score, by = "id")結果:
| id | name | class |
|---|---|---|
| 3 | Cathy | B |
| 4 | David | B |
這個結果代表:
id = 3和id = 4這兩位學生在成績表中沒有資料。
適合情境
anti_join() 很適合用來檢查資料缺漏。
例如:
student %>%
anti_join(score, by = "id")可以找出沒有成績資料的學生。
在資料分析工作中,我很推薦在合併資料前後使用 anti_join() 檢查資料是否有漏配。
例如:
patient_basic %>%
anti_join(lab_data, by = "patient_id")可以找出沒有檢驗資料的病人。
或是:
building_info %>%
anti_join(electricity_data, by = "building_id")可以找出沒有用電資料的建築物。
九、nest_join():把配對結果收成巢狀資料
nest_join() 比較進階。
它會保留左表資料,並且把右表中配對到的資料收進一個 list-column。
nest_join(student, score, by = "id", name = "score_data")結果概念如下:
| id | name | class | score_data |
|---|---|---|---|
| 1 | Amy | A | tibble(math = 90) |
| 2 | Ben | A | tibble(math = 80, 85) |
| 3 | Cathy | B | 空 tibble |
| 4 | David | B | 空 tibble |
這裡每一位學生都保留一列,而配對到的成績資料被放在 score_data 這個欄位裡。
適合情境
nest_join() 適合用在一對多資料,而且你不想馬上把資料展開的時候。
例如一位病人可能有多筆檢驗紀錄:
patient_basic %>%
nest_join(lab_data, by = "patient_id", name = "labs")這樣每位病人仍然是一列,但每個人的所有檢驗資料會被收在 labs 裡面。
如果之後想展開,可以搭配 tidyr::unnest():
library(tidyr)
patient_nested %>%
unnest(labs)十、cross_join():產生所有組合
cross_join() 會把左表的每一列和右表的每一列全部配對。
例如:
term <- tibble(
term = c("期中", "期末")
)
cross_join(student, term)結果:
| id | name | class | term |
|---|---|---|---|
| 1 | Amy | A | 期中 |
| 1 | Amy | A | 期末 |
| 2 | Ben | A | 期中 |
| 2 | Ben | A | 期末 |
| 3 | Cathy | B | 期中 |
| 3 | Cathy | B | 期末 |
| 4 | David | B | 期中 |
| 4 | David | B | 期末 |
如果左表有 4 列,右表有 2 列,結果就會有:
4 * 2 = 8列。
適合情境
cross_join() 適合用來產生所有可能組合。
例如:
student %>%
cross_join(term)可以產生每位學生在每個考試階段的紀錄框架。
也可以用在實驗設計,例如建立所有參數組合:
model_type <- tibble(model = c("linear", "random_forest"))
feature_set <- tibble(feature = c("basic", "advanced"))
cross_join(model_type, feature_set)十一、join_by():更清楚地指定合併條件
在 dplyr 中,傳統寫法是:
left_join(student, score, by = "id")新版 dplyr 可以使用 join_by():
left_join(student, score, by = join_by(id))兩者在這個例子中效果相同。
兩邊欄位名稱不同時
假設學生資料中的欄位叫做 student_id,成績資料中的欄位叫做 id:
student2 <- tibble(
student_id = c(1, 2, 3),
name = c("Amy", "Ben", "Cathy")
)
score2 <- tibble(
id = c(1, 2, 4),
math = c(90, 80, 70)
)可以這樣寫:
left_join(student2, score2, by = join_by(student_id == id))這樣的寫法比傳統寫法更直覺:
left_join(student2, score2, by = c("student_id" = "id"))多個欄位合併
假設我們要同時依照 id 和 date 合併:
left_join(data1, data2, by = join_by(id, date))等同於:
left_join(data1, data2, by = c("id", "date"))十二、合併資料時常見問題
1. 為什麼合併後資料列變多?
如果右表中同一個 key 有多筆資料,合併後左表資料會被重複展開。
例如 score 中 id = 2 有兩筆成績:
| id | math |
|---|---|
| 2 | 80 |
| 2 | 85 |
所以:
left_join(student, score, by = "id")會讓 Ben 出現兩列。
這不是錯誤,而是一對多合併的正常結果。
如果你原本期待每個 id 只出現一次,合併前可以先檢查:
score %>%
count(id) %>%
filter(n > 1)2. 合併後出現很多 NA 是什麼意思?
NA 通常代表某一邊沒有配對到資料。
例如:
left_join(student, score, by = "id")中,id = 3 和 id = 4 沒有成績,所以 math 是 NA。
你可以用 anti_join() 檢查哪些資料沒有配對:
student %>%
anti_join(score, by = "id")3. 合併前應該先檢查什麼?
建議至少檢查三件事:
| 檢查項目 | 範例程式 |
|---|---|
| key 是否有重複 | count(data, id) %>% filter(n > 1) |
| key 是否有缺失值 | filter(data, is.na(id)) |
| 有哪些資料配不到 | anti_join(x, y, by = "id") |
例如:
student %>%
count(id) %>%
filter(n > 1)score %>%
count(id) %>%
filter(n > 1)student %>%
anti_join(score, by = "id")## 十三、實務上怎麼選 join 函數?
| 你的需求 | 建議使用 |
|---|---|
| 我要保留主資料表,再補上其他欄位 | left_join() |
| 我只想保留兩邊都有的資料 | inner_join() |
| 我要保留兩邊所有資料 | full_join() |
| 我要找出有對應資料的人 | semi_join() |
| 我要找出沒有對應資料的人 | anti_join() |
| 我要產生所有排列組合 | cross_join() |
| 我要保留一對多資料,但先不要展開 | nest_join() |
十四、最推薦新手先熟悉哪幾個?
如果你剛開始學 dplyr join,不需要一次把所有函數都背起來。
我會建議先熟悉這四個:
left_join()
inner_join()
anti_join()
full_join()其中最重要的是:
left_join()因為它最符合日常資料分析的需求: 以一張主資料表為核心,補上其他資料表的欄位。
第二個很重要的是:
anti_join()因為它可以幫助你檢查資料是否有漏配,是資料清理和除錯時非常實用的工具。
十五、結語
dplyr 的 join 函數是資料分析中非常核心的工具。只要你的資料來自不同來源,就很可能會用到 join。
簡單整理:
inner_join() 用來保留兩邊都有的資料。 left_join() 用來保留左表全部資料,是最常用的合併方式。 right_join() 用來保留右表全部資料,但實務上較少使用。 full_join() 用來保留左右兩邊所有資料。 semi_join() 用來篩出左表中有配對的資料。 anti_join() 用來找出左表中沒有配對的資料。 nest_join() 用來把右表配對結果收成巢狀欄位。 cross_join() 用來產生所有組合。 join_by() 則是用來更清楚地指定合併條件。
在實務分析中,我會建議養成一個習慣:
# 合併前先檢查配不到的資料
anti_join(x, y, by = "id")
# 確認後再正式合併
left_join(x, y, by = "id")這樣可以大幅降低合併錯誤,也能讓你的資料分析流程更加穩健。