dplyr join 函數完整介紹:資料合併時一定要懂的 8 個函數

在資料分析工作中,我們很常會遇到「資料分散在不同表格」的情況。

例如:

一張表是學生基本資料,另一張表是考試成績; 一張表是病人基本資料,另一張表是檢驗數據; 一張表是建築物資訊,另一張表是逐時用電量。

這時候,我們就需要把不同資料表依照某些共同欄位合併起來。在 R 的 dplyr 套件中,最常用來處理資料表合併的函數,就是一系列以 _join() 結尾的函數。

本文會介紹 dplyr 中常見的 join 函數,包括:

inner_join()
left_join()
right_join()
full_join()
semi_join()
anti_join()
nest_join()
cross_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 = 1id = 2 同時出現在 studentscore 兩張表中,所以結果只保留這些資料。

需要注意的是,id = 3id = 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 = 3id = 4score 中沒有成績資料,因此 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 = 5score 中有成績,但在 student 中找不到學生基本資料,因此 nameclass 會是 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 = 1id = 2 兩邊都有,所以完整合併。 id = 3id = 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 = 3id = 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"))

多個欄位合併

假設我們要同時依照 iddate 合併:

left_join(data1, data2, by = join_by(id, date))

等同於:

left_join(data1, data2, by = c("id", "date"))

十二、合併資料時常見問題

1. 為什麼合併後資料列變多?

如果右表中同一個 key 有多筆資料,合併後左表資料會被重複展開。

例如 scoreid = 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 = 3id = 4 沒有成績,所以 mathNA

你可以用 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")

這樣可以大幅降低合併錯誤,也能讓你的資料分析流程更加穩健。