Learning Objectives
- 用 merge 依共同的鍵把兩張資料表接起來,說出 inner、left、right、outer 各留下哪些列
- 用 indicator 找出對不到的鍵,並說明合併後為什麼會出現缺值
- 預估合併後的列數,認出鍵重複造成的列數暴增,並用 validate 提早發現
- 用 concat 把欄位相同的資料表上下接起來
資料分散在好幾張表
上一課最後留了一個問題:成績表 grades.csv 只有學號、課程與分數,沒有系所;系所記在學生表 students.csv。
想算「各系的期末平均」,就要先把兩張表依學號接起來。
資料分成好幾張表是常態,原因很實際:學生的系所只要記一次,不必在他修的每一門課都抄一遍(抄很多次就有抄錯的機會)。 分析時再依共同的欄位把需要的表接起來,這個動作叫合併(merge),共同的欄位叫鍵(key)。
這一課用到四份檔案:grades.csv(600 筆修課成績)、students.csv(200 位學生)、memberships.csv(258 筆社團參加紀錄)、clubs.csv(10 個社團)。
它們都是程式產生的模擬資料,不是任何學校的真實資料。
逐步看:四種合併方式
左表有 S1~S4 四位學生的系所,右表有 S1、S3、S5 的期末成績。兩張表都有的學號只有 S1 與 S3。每一步看右邊的結果表多了或少了哪幾列。
四種方式怎麼選
| how | 留下哪些列 | 對不到的格子 |
|---|---|---|
inner(預設) |
兩邊都有的鍵 | 沒有,對不到的列直接消失 |
left |
左表的每一列 | 右表的欄補 NaN |
right |
右表的每一列 | 左表的欄補 NaN |
outer |
兩邊所有的鍵 | 兩邊都可能補 NaN |
實務上最常用的是 left:「以成績表為主,把系所補上去」。選 inner 的時候要特別小心,因為對不到的列會靜悄悄地消失,
程式不會警告你。合併完先比一比列數,再用 indicator=True 看有沒有 left_only 或 right_only,是發現問題最快的方法。
兩張表的鍵欄名稱不一樣時(例如左表叫 student_id、右表叫 學號),用 left_on="student_id", right_on="學號" 分別指定。
逐步看:把系所接到成績表上
回到上一課的問題。注意合併前後的 shape:以成績表為主做 left 合併,列數應該不變。
逐步看:對不到的鍵,以及沒參加社團的人
社員紀錄裡有兩個社團代碼(C11、C12)不在社團表裡,可能是新成立還沒登記的社團。看 indicator 怎麼把它們找出來。
鍵重複時,列數會相乘
合併時,左表的一列會和右表每一個鍵相同的列配成一對。所以鍵在兩邊都重複時,列數是相乘的: 左表有 2 列 S1、右表有 3 列 S1,合併後就有 2 × 3 = 6 列 S1。這叫多對多(many-to-many)合併, 多半是忘了先彙總或選錯了鍵,結果算平均、總和時同一筆資料被重複計算。
合併前先想清楚兩張表的關係:
| 關係 | 例子 | validate= |
|---|---|---|
| 一對一 | 學生基本資料 × 學生聯絡資料 | "one_to_one" |
| 多對一 | 成績(一人多筆)× 學生(一人一筆) | "many_to_one" |
| 多對多 | 社團紀錄(一人多筆)× 成績(一人多筆) | 通常是錯誤 |
把預期的關係寫進 validate,不符合時 pandas 會丟出 MergeError,比事後發現數字不對容易得多。
逐步看:多對多合併讓列數暴增
先看一個 3 列和 4 列的小例子,再看社團紀錄直接和成績表合併會發生什麼事。
concat:欄位相同的表上下接起來
merge 是左右接(依鍵配對,欄變多);pd.concat 最常見的用法是上下接(列變多),例如把上學期和下學期的成績表接成一張。
fall = pd.DataFrame({"學號": ["S1", "S2"], "期末": [82, 75]})
spring = pd.DataFrame({"學號": ["S1", "S3"], "期末": [88, 91]})
both = pd.concat([fall, spring], ignore_index=True) # 4 列,索引重新編為 0~3
兩件事要注意:
- 不加 ignore_index=True 時,原本的索引會照抄,結果的索引是 0、1、0、1,有重複。
- 欄位名稱要一致:一張叫「期末」、另一張叫「final」,concat 會當成兩個不同的欄,各自補上 NaN。
實作:各類社團的參加人次
讀入 memberships.csv 與 clubs.csv,依 club_id 合併(用預設的 inner),再依社團類別(category)分組,
用 size() 算出每一類的參加人次,由多到少排序後印出。(對不到的 C11、C12 會被 inner 排除,這一題就是要這樣算。)
小測驗:合併方式
成績表有 600 列(一人可能有好幾列),學生表有 200 列(每位學生剛好一列,而且成績表的學號都在學生表裡)。grades.merge(students, on="student_id", how="left") 的結果有幾列?
想保留兩張表所有的鍵,對不到的地方補 NaN,how 要設成什麼?
indicator=True 產生的 _merge 欄裡,某一列是 left_only,代表什麼?
小測驗:列數與 concat
下面程式會印出什麼?
import pandas as pd
x = pd.DataFrame({"k": ["a", "a", "b"], "v": [1, 2, 3]})
y = pd.DataFrame({"k": ["a", "a", "c"], "w": [7, 8, 9]})
print(len(x.merge(y, on="k")), len(x.merge(y, on="k", how="outer")))
grades.merge(students, on="student_id", validate="many_to_one") 在什麼情況下會丟出 MergeError?
下面程式會印出什麼?
import pandas as pd
a = pd.DataFrame({"id": [1, 2]})
b = pd.DataFrame({"id": [3]})
print(pd.concat([a, b]).index.tolist())
重點整理
pd.merge(左, 右, on="鍵", how=...):依共同的鍵把兩張表左右接起來;鍵名不同時用left_on、right_on。inner只留兩邊都有的鍵(預設),left、right保留其中一邊的每一列,outer全部保留;對不到的格子是 NaN,整數欄會因此變成浮點數。- 合併後先比列數,再用
indicator=True看有沒有left_only、right_only;inner 會讓對不到的列靜悄悄消失。 - 鍵在兩邊都重複時列數相乘;用
validate="many_to_one"等寫下預期的關係,讓錯誤提早出現。 pd.concat把欄位相同的表上下接起來,記得ignore_index=True。- 下一課處理資料表的形狀:同一份成績可以是「一列一筆修課」的長表,也可以是「一列一位學生、一欄一門課」的寬表,兩種形狀怎麼互轉、各適合做什麼。