🗺️ 程式互動式地圖
AI 線・Pandas

pandas merge:四種接法與筆數為什麼會變多

把兩張表依鍵接起來,最怕的不是報錯,而是安靜地算錯:對不到的訂單被丟掉、重複的鍵讓營收翻倍、整數欄突然多了小數點。這一頁用顧客表與訂單表,把 inner、left、right、outer 的差別、筆數的乘法規則,以及 merge、join、concat 該在什麼時候用,一次看清楚。

how 切換:連線動畫與 _merge 欄 筆數預測器:先猜再揭曉 merge/join/concat:12 張情境卡

💡 先搞懂問題

晴空咖啡的會員系統有兩張表。顧客表記錄每位會員的編號 cid、姓名與所在城市;訂單表每一列是一筆消費,只記了 cid 和金額。行銷部想知道「台北的會員一共消費多少」,城市在顧客表、金額在訂單表,所以得先把兩張表依 cid 對起來。在試算表裡這是 VLOOKUP,在 SQL 裡是 JOIN,在 pandas 裡就是 merge。

merge 一行就寫完,麻煩在它幾乎不會報錯,算錯了也不會提醒你。訂單表裡有一筆 C09,顧客表找不到這個人(可能是測試資料或已刪除的帳號),用預設的接法,這筆 150 元的訂單就安靜地消失了。反過來,如果顧客表裡 C01 因為改地址被建檔了兩次,他的每一筆訂單都會被複製成兩列,營收跟著灌水。有時候金額欄還會從 120 變成 120.0,因為有一列對不到、被補了缺值。

所以讀 merge 的程式,要回答三個問題:用哪一欄當鍵、對不到的列怎麼處理、鍵有沒有重複。這三個問題決定結果有幾列、哪些欄位會出現 NaN。

顧客表 customers 訂單表 orders cid name city C01 王小明 台北 C02 林雅婷 台中 C03 陳志豪 高雄 C04 張美玲 台北 oid cid amount 101 C01 120 102 C02 260 103 C01 90 104 C09 150 105 C03 55 依 cid 對起來 才能算「各城市營收」 陷阱一:筆數變少 C09 在顧客表找不到, 預設 inner 直接丟掉這 150 元 陷阱二:筆數變多 C01 若在顧客表建檔兩次, 他的兩筆訂單會變成四列
左右是兩張原始表(虛構示意),共同欄位是 cid。合併後才能依城市彙總營收。下排兩個框是 merge 最常見的兩種「沒報錯但算錯」:紅框是對不到的列被丟掉,橘框是鍵重複讓列數相乘。後面三個實驗室分別拆解這兩件事與工具的選擇。

生活比喻:社團迎新的報名表與繳費紀錄

社團總務小陳手上有兩份名單:一份是迎新活動的報名表(學號、姓名),一份是銀行匯款的繳費紀錄(學號、金額)。他要做一張「誰報了名、繳了多少」的總表。只列兩邊都有的人,是最保守的做法;以報名表為主、沒繳費的人金額欄留白,方便催繳;以繳費紀錄為主,可以找出「有人匯了錢卻沒報名」;兩份全部列出、對不到的留白,則是完整對帳。

麻煩出在重複。如果某位同學匯了兩次款(分期繳),他在總表上自然會出現兩列;但如果報名表上他也不小心被登記了兩次,認真照表核對的話,兩筆報名乘兩筆匯款,總表上他就有四列,金額看起來翻倍。另外,小陳偶爾也會把下學期的報名表直接疊在這學期的下面,那只是把兩疊紙疊成一疊,完全不需要核對學號。

回到程式:報名表是左表(呼叫 merge 的那一張),繳費紀錄是右表,學號就是鍵(key),寫成 on='cid'。只列兩邊都有的人是 how='inner'(預設);以報名表為主是 how='left';以繳費紀錄為主是 how='right';兩份全列是 how='outer'。留白的欄位就是 NaN。一位同學匯兩次款是一對多(one-to-many),兩邊都重複是多對多(many-to-many),列數是兩邊重複次數相乘。把兩疊紙疊在一起不核對學號,是 pd.concat。
小陳對帳(比喻) pandas 的對應 報名表、繳費紀錄 左表.merge(右表) 用學號核對 on='cid'(鍵) 只列兩邊都有的人 how='inner'(預設) 以報名表為主 how='left' 兩份全列、完整對帳 how='outer'+indicator 沒繳費的金額欄留白 NaN(整數欄變 float64) 一位同學匯了兩次款 一對多:出現兩列 報名也登記了兩次 多對多:2 × 2 = 4 列 兩疊報名表直接疊起 pd.concat(不比對鍵)
左欄是小陳的做法,右欄是對應的 pandas 寫法。第三到第五列是 how 參數的三種選擇(right 和 left 對稱,沒有另外畫);第七、八列是筆數變多的兩種情況;最後一列提醒:concat 和 merge 是不同的工具,前者不看鍵。

這個比喻有三個地方要修正。第一,小陳看到同一位同學被登記兩次,會停下來問是不是重複;pandas 不會,它照規則把每一對相等的鍵都配一次,列數直接相乘,所以要自己加 validate 檢查。第二,小陳會把「B10901」和「b10901」認成同一個人,pandas 只認完全相等的值,大小寫、前後空白都會造成對不到;鍵的型別也要一致,一邊是字串、一邊是整數時,pandas 3 會直接丟出 ValueError。第三,總表的列順序有規則:inner 與 left 保留左表的鍵順序,right 保留右表的,outer 則把鍵依字典順序排序。

🎮 互動實驗室一:四種 how 的連線動畫

左邊是顧客表(4 位會員,其中 C04 沒下過單),右邊是訂單表(5 筆,C01 有兩筆、C09 是查無此人的孤兒訂單)。切換 how,連線會畫出哪幾對列配成一列,被丟掉的列會淡掉、補 NaN 的列會標出來。勾選「顧客表重複建檔 C01」,看多對多讓列數怎麼變多。下方的結果表與 print 輸出都是 pandas 3 實際執行的結果。

how
選項
print(result) 的實際輸出

  
畫面說明:

🎮 互動實驗室二:筆數預測器

左表 L 有 a 列鍵是 A、另有一列 B;右表 R 有 b 列鍵是 A、另有一列 C。調整 a、b 與 how,先從四個數字裡猜 len(L.merge(R, on='k', how=…)) 是多少,按下後才揭曉連線與算式,並列出四種 validate 寫法哪些會丟出 MergeError。每一組數字都在 pandas 3 實跑核對過。

猜對 0 / 0
連續猜對 0
左表 A 的列數 a
右表 A 的列數 b
how
L['k']
R['k']
你猜結果有幾列?
畫面說明:先猜,再看右邊的連線與算式。

🎮 互動實驗室三:merge、join 還是 concat

每張卡是一個原創的資料整理情境,請從六種寫法裡選出最適合的一個。判斷時先問:兩張表要不要「依某個鍵比對」?要比對的話,是用欄位還是索引當鍵、對不到的列要不要留?不比對的話,是上下接(加列)還是左右接(加欄)?答完右邊的工具箱會亮起正解。

得分 0 / 0
連續答對 0
最佳連勝 0
畫面說明:先問要不要比對鍵。

📘 原理補完

1. merge 的配對規則:每一對相等的鍵產生一列

left.merge(right, on='cid', how='inner') 和 pd.merge(left, right, on='cid') 是同一件事。pandas 會找出兩邊鍵值相等的每一對列,每一對產生結果的一列,左表的欄位在前、右表的欄位在後,鍵欄只保留一份。how 只決定「對不到的列怎麼辦」:inner 丟掉、left 保留左表的、right 保留右表的、outer 兩邊都保留,保留下來那一側以外的欄位補 NaN。沒寫 on 時,pandas 會拿兩表所有同名欄位當鍵,欄名剛好重複時容易出錯,所以建議一律明確寫出 on。

how保留哪些鍵對不到的欄位列的順序對應 SQL晴空咖啡列數
'inner'(預設)兩邊都有的(不會出現)左表鍵的順序INNER JOIN4
'left'左表全部右表欄位補 NaN左表鍵的順序LEFT JOIN5(多 C04)
'right'右表全部左表欄位補 NaN右表鍵的順序RIGHT JOIN5(多 C09)
'outer'兩邊聯集兩邊都可能補 NaN鍵依字典順序排序FULL OUTER JOIN6
'cross'不看鍵不會有左表順序CROSS JOIN4 × 5 = 20
'left_anti'只在左表的鍵右表欄位全是 NaN左表鍵的順序LEFT JOIN … IS NULL1(C04)

最後兩列比較少見:cross 是兩表所有列的兩兩組合(笛卡兒積),left_anti/right_anti 是 pandas 3.0 新增的反連接,直接取出「只在一邊出現」的列。舊版本要用 indicator 篩 left_only 才做得到,後面第 5 節會看到。

how='inner' → 4 列 C04 C01 C02 C03 C09 C01 有兩筆訂單 → 2 列,再加 C02、C03 how='left' → 5 列 C04 C01 C02 C03 C09 C04 留下,訂單欄補 NaN how='right' → 5 列 C04 C01 C02 C03 C09 C09 留下,姓名、城市補 NaN how='outer' → 6 列 C04 C01 C02 C03 C09 兩邊都留,各自補 NaN 只在左表兩邊都有只在右表 how 只決定「對不到的鍵」的去留 對得到的部分四種寫法完全相同
把所有鍵分成三格:只在左表、兩邊都有、只在右表。四種 how 的差別只在左右兩格要不要保留,中間綠色那格永遠保留。列數則要再看每個鍵在兩邊各出現幾次,例如 C01 在訂單表有兩筆,所以中間那格貢獻 4 列,而不是 3 列。

2. 一對多、多對多:列數是乘出來的

同一個鍵在左表出現 m 次、在右表出現 n 次,merge 後就產生 m × n 列。一對一(m=n=1)列數不變;一對多是正常的業務關係,例如一位顧客有很多筆訂單,結果的列數跟著「多」的那一邊;麻煩的是多對多,通常代表某一張表裡有不該重複的鍵。列數變多的副作用是顧客那一側的數值被複製,接著做 sum 就會重複計算。要在合併時就把關,可以加 validate:'one_to_one'(兩邊鍵都要唯一)、'one_to_many'(左表鍵唯一)、'many_to_one'(右表鍵唯一)、'many_to_many'(不檢查),條件不符時丟出 pandas.errors.MergeError。

顧客表(1 列) C01 王小明 點數500 訂單表(2 列) 101 C01 120 103 C01 90 merge 結果(2 列) cid 姓名 點數 oid amount C01 王小明 500 101 120 C01 王小明 500 103 90 接著加總「點數」會變 1000 顧客那側的欄位被複製了; 要先彙總訂單再合併,或改用 drop_duplicates
左邊一位顧客對到兩筆訂單,右邊結果就有兩列,橘底標出被複製的顧客資訊(點數為示意欄位)。訂單金額加總仍然正確(120+90),但顧客的點數被算了兩次。實務上的解法是先把訂單依 cid 彙總成一列,再和顧客表做一對一合併。
左表 A × 2 列,右表 A × 3 列(示意) A① A② A㊀ A㊁ A㊂ 6 條線 = 2 × 3 = 結果 6 列 validate 檢查結果 'one_to_one'MergeError 'one_to_many'MergeError 'many_to_one'MergeError 'many_to_many'通過
左邊兩列 A 和右邊三列 A 兩兩配對,共 6 條線,結果就有 6 列。右邊是四種 validate 的檢查:one 指的是「那一側的鍵必須唯一」,左右兩側都重複時,只有不做檢查的 many_to_many 會通過。實驗室二可以自己改 a、b 驗證這張表。

3. 對不到的欄位:NaN 與型別變化

left、right、outer 會把對不到的那一側欄位補成缺值。文字欄在 pandas 3 是 str 型別,可以直接放 NaN,型別不變;整數欄(NumPy 的 int64)放不下 NaN,整欄會升格成 float64,所以訂單編號 101 會顯示成 101.0。inner 不會產生缺值,型別維持 int64。想保留整數,可以在合併後轉成 pandas 的可空整數(nullable integer) Int64(大寫 I),缺值會顯示為 <NA>;或在合併前就先把欄位轉成 Int64。

how='inner'how='left'.astype({'oid': 'Int64'}) 101103102105 int64 101.0103.0102.0105.0NaN float64 101103102105<NA> Int64 C04 沒有訂單 → 補 NaN → 整欄變浮點數;Int64 可以裝缺值
同一欄 oid 在三種情況下的樣子。中間紅框多了 C04 那一列的 NaN,其他四個整數也一起變成 101.0 這種寫法;右邊轉成可空整數 Int64 後,數字恢復成整數、缺值顯示為 <NA>。輸出預測題看到 .0 的選項,先想想有沒有補過缺值。

4. 欄名不同、欄名撞名:left_on/right_on 與 suffixes

兩表的鍵欄名不同時,用 left_on='customer_id', right_on='cid' 分別指定,結果會保留兩個鍵欄,需要時再 drop 掉其中一個。兩表除了鍵之外還有同名欄位時,pandas 不會覆蓋,而是加上後綴,預設 suffixes=('_x', '_y'),左表的加 _x、右表的加 _y;寫成 suffixes=('_送貨', '_會員') 可讀性好很多。鍵本身的型別也要一致,例如左邊 cid 是字串、右邊是整數時,pandas 3 會丟出 ValueError,提醒你兩邊型別不同。

訂單表(左) oid customer_id city 會員表(右) cid city left_on='customer_id', right_on='cid' 結果欄位(suffixes=('_送貨', '_會員')) oid customer_id city_送貨 cid city_會員 不寫 suffixes 時是 city_x、city_y;兩個鍵欄都保留,鍵欄同名(用 on)時才只留一份
上排是兩張原始表,藍與綠是鍵、黃色是撞名的 city。下排是結果:鍵欄名稱不同,所以 customer_id 和 cid 都留下;同名的 city 分別加上後綴。題目問合併後的欄名清單時,先數鍵欄有幾個,再看撞名欄位的後綴。

5. indicator=True 與反連接

indicator=True 會在結果最後加一欄 _merge,型別是 category,值是 both、left_only、right_only 三種之一,告訴你每一列是怎麼來的。搭配 outer 就是對帳表:value_counts() 一行看出有幾筆配不上。只留 left_only 的列,就是反連接(anti-join):找出「左表有、右表沒有」的資料,例如從未下單的顧客、帳號表裡沒有登入紀錄的閒置帳號。pandas 3.0 起也可以直接寫 how='left_anti'。

how='outer', indicator=True cid name oid _merge C01 王小明 101.0both C01 王小明 103.0both C02 林雅婷 102.0both C03 陳志豪 105.0both C04 張美玲 NaNleft_only C09 NaN 104.0right_only 篩 left_only 沒下過單的顧客:張美玲 ≡ how='left_anti'(3.0+) 篩 right_only 孤兒訂單:104(C09)
左邊是 outer 加上 indicator 的結果,最後一欄 _merge 標出每一列的來源。右邊兩個框是兩種反連接:篩 left_only 找出沒下單的顧客,篩 right_only 找出查無顧客的孤兒訂單。資料品質檢查和資安的帳號盤點都常用這個寫法。

6. merge、join、concat 的分工

三個工具的差別在「依什麼對齊」。merge 依欄位的值配對,最有彈性,也是讀程式時最常見的。join 是 DataFrame 的方法,用右表的索引當鍵,預設 how='left';可以用 on='欄名' 指定左表的某一欄去對右表的索引;兩表有同名欄時必須給 lsuffix 或 rsuffix,否則會報錯。反過來,merge 也能處理索引:on 可以寫索引層級的名稱,或用 left_index=True/right_index=True,所以 join 能做的 merge 都做得到,join 只是寫起來比較短。pd.concat 不比對任何鍵值,只是把多張表接起來:axis=0(預設)上下疊、增加列,欄名不同的地方補 NaN;axis=1 左右並排、增加欄,依索引對齊,預設 join='outer',索引對不到的位置補 NaN。上下疊時原本的索引會重複,常加 ignore_index=True 重新編號,或用 keys= 標示每一塊的來源。

寫法依什麼對齊預設結果變化典型用途
left.merge(right, on=…)欄位的值how='inner'依配對數決定列數訂單補上顧客資料、對帳
left.join(right)右表的索引(左表用索引或 on 欄位)how='left'以左表為主,加欄以日期、編號為索引的表並排
pd.concat([a, b])不比對,axis=0 依欄名排欄位axis=0、join='outer'加列,欄位取聯集多個月份、多家門市的同格式報表
pd.concat([a, b], axis=1)索引join='outer'加欄,索引取聯集同一批樣本的不同特徵並排
merge(on='cid')join(右表索引) concat(axis=0)concat(axis=1) cid=C01 cid=C02 cid=C01 cid=C02 鍵在欄位裡,比對值 on='cid' index C01 index C02 右表的索引當鍵,預設 left oid amount NaN oid NaN store 加列;欄名不同處補 NaN d1 rev NaN d2 rev temp d3 NaN temp 加欄;依索引對齊,預設 outer
上排兩種會比對鍵:merge 比對欄位的值,join 拿右表的索引當鍵(虛線表示左表的 on 欄位去找右表索引)。下排兩種 concat 不比對鍵值:axis=0 把表疊高,欄名不同的格子補 NaN;axis=1 把表並寬,依索引(黃色)對齊,d1、d3 各缺一邊。數值為示意。

7. 判斷步驟:寫或讀一行合併程式時

  1. 要不要比對鍵:不比對、只是接起來,用 concat;上下加列是 axis=0,左右加欄是 axis=1。
  2. 鍵在哪裡:鍵在欄位用 merge(欄名不同就 left_on/right_on),鍵在右表的索引用 join 或 merge(right_index=True)。
  3. 對不到的列要不要留:只要配得上的用 inner,以某一表為主用 left 或 right,對帳或找差異用 outer 加 indicator。
  4. 鍵會不會重複:先用 df['cid'].duplicated().any() 或 value_counts() 看一下,合併時加 validate;列數 = 每個鍵兩邊次數相乘再加總。
  5. 合併後檢查:比對合併前後的列數與金額總和、看哪些欄出現 NaN 或型別變成 float64、撞名的欄位是否加了後綴。
要依鍵比對嗎? 不用要 加列還是加欄? 鍵在欄位還是索引? axis=0 axis=1 merge join 都是 pd.concat;上下疊常加 ignore_index 對不到的列:inner 丟、left/right 留一邊 對帳找差異:outer+indicator=True 鍵可能重複?加 validate,並比對合併前後的列數與加總
第一層問要不要比對鍵,把 concat 和 merge/join 分開;第二層決定工具與方向;merge 之後再選 how。最底下的橘框適用於所有比對鍵的合併:列數是乘出來的,最好在程式裡就寫明預期的對應關係。

8. 兩段帶註解的完整範例

第一段:四種 how 的列數、indicator 對帳、把 float64 轉回可空整數、兩種反連接寫法,以及 validate 防呆。註解裡的數字都是實際執行的輸出。

import pandas as pd

customers = pd.DataFrame({'cid': ['C01', 'C02', 'C03', 'C04'],
                          'name': ['王小明', '林雅婷', '陳志豪', '張美玲'],
                          'city': ['台北', '台中', '高雄', '台北']})
orders = pd.DataFrame({'oid': [101, 102, 103, 104, 105],
                       'cid': ['C01', 'C02', 'C01', 'C09', 'C03'],
                       'amount': [120, 260, 90, 150, 55]})

for how in ['inner', 'left', 'right', 'outer']:
    print(how, len(customers.merge(orders, on='cid', how=how)))  # 4 5 5 6

full = customers.merge(orders, on='cid', how='outer', indicator=True)
print(full['_merge'].value_counts().to_dict())   # {'both': 4, 'left_only': 1, 'right_only': 1}
print(full['oid'].dtype)                          # float64:C04 沒訂單,整數欄被 NaN 撐成浮點數

full = full.astype({'oid': 'Int64', 'amount': 'Int64'})  # 可空整數:缺值顯示 <NA>
print(full['oid'].tolist())                       # [101, 103, 102, 105, <NA>, 104]

no_order = full.loc[full['_merge'] == 'left_only', 'name'].tolist()
print(no_order)                                   # ['張美玲']:沒下過單的顧客(anti-join)
orphan = orders.merge(customers, on='cid', how='left_anti')  # pandas 3.0 起才有的寫法
print(orphan['oid'].tolist())                     # [104]:孤兒訂單

try:
    orders.merge(customers, on='cid', validate='many_to_one')  # 每筆訂單最多對到一位顧客
    print('many_to_one 檢查通過')
    customers.merge(orders, on='cid', validate='one_to_one')
except pd.errors.MergeError as e:
    print('MergeError')                           # orders 的 C01 有兩筆,不是一對一

第二段:欄名不同與撞名的處理、以索引為鍵的 join,以及 concat 的上下疊與左右並排。

import pandas as pd

orders = pd.DataFrame({'oid': [101, 102, 103],
                       'customer_id': ['C01', 'C02', 'C01'],
                       'city': ['台北', '台中', '新竹']})          # 送貨城市
members = pd.DataFrame({'cid': ['C01', 'C02'],
                        'city': ['台北', '台中']})                 # 會員登記的城市

# 鍵的欄名不同:left_on/right_on;同名的非鍵欄位加上後綴
r = orders.merge(members, left_on='customer_id', right_on='cid',
                 suffixes=('_送貨', '_會員'))
print(r.columns.tolist())   # ['oid', 'customer_id', 'city_送貨', 'cid', 'city_會員']

# join:以「右表的索引」當鍵,預設 how='left'
j = orders.join(members.set_index('cid'), on='customer_id', rsuffix='_會員')
print(j.shape)              # (3, 4)

# concat axis=0:欄位相同的表上下疊,ignore_index 重新編號
jan = pd.DataFrame({'oid': [1, 2], 'amount': [100, 200]})
feb = pd.DataFrame({'oid': [3], 'amount': [300]})
print(pd.concat([jan, feb], ignore_index=True).index.tolist())   # [0, 1, 2]

# concat axis=1:依索引左右並排,對不到的位置補 NaN
x = pd.DataFrame({'rev': [100, 200]}, index=['d1', 'd2'])
y = pd.DataFrame({'temp': [30, 28]}, index=['d2', 'd3'])
print(pd.concat([x, y], axis=1).shape)                # (3, 2):d1、d2、d3 都留下

9. 容易寫錯或考錯的地方

預設是 inner:沒寫 how 就是 inner,對不到的列直接消失,不會有任何警告。題目問「合併後有幾列」,先找出兩邊都有的鍵,再看每個鍵的重複次數。

列數是乘法:鍵在左表出現 m 次、右表 n 次,就是 m × n 列;把各個鍵的乘積加起來,再加上 left/right/outer 保留下來的未配對列。干擾選項常給 m + n 或較大的那一個。

left 不保證列數不變:很多人以為 left join 之後列數一定等於左表,但右表的鍵若重複,左表那一列一樣會被複製。想確保列數不變,加 validate='many_to_one'。

出現 .0 的原因:整數欄只要被補了一個 NaN 就變 float64;inner 不會補 NaN,所以 inner 的結果通常維持 int64。轉回整數要用可空的 Int64,直接 astype('int64') 遇到 NaN 會報錯。

NaN 鍵不會被當成「對不到」:pandas 的 merge 會把兩邊鍵都是 NaN 的列配在一起,這和 SQL 的 NULL 不相等不同。合併前最好先處理鍵的缺值。

outer 會排序:inner、left 保留左表鍵的順序,right 保留右表的,outer 依鍵排序;問「第一列是誰」時要先確認 how。

concat 不是 merge:concat(axis=1) 依索引對齊、不看欄位的值;兩張表索引不同(例如一張 0~4、一張被篩選過)時,會出現一堆 NaN 列。上下疊之後索引會重複,用 loc[0] 可能取到兩列。

版本差異:pandas 3.0 新增 how='left_anti'、'right_anti',merge 與 join 都能用;文字欄預設型別是 str,缺值統一顯示 NaN;copy 參數在 3.0 起已無作用(Copy-on-Write)。在 2.x 版本上跑 left_anti 會報錯,教材若用 indicator 篩 left_only,兩種寫法結果相同。

✅ 自我檢測

6 題原創的程式閱讀題,前提資料是下面這兩張虛構的「北辰書店」員工表與部門表,每一題都從這份原始資料開始;所有輸出都以 pandas 3 實際執行確認過。選完會立即顯示對錯與解析,全部作答後出現總分。目前得分:0 / 6

import pandas as pd
emp = pd.DataFrame({'eid': [1, 2, 3], 'dept': ['D1', 'D2', 'D9']})
dept = pd.DataFrame({'dept': ['D1', 'D2', 'D3'], 'dname': ['業務', '研發', '客服']})

🎯 重點整理

  1. merge 把鍵相等的每一對列配成一列;how 只決定對不到的列:inner 丟、left/right 留一邊、outer 全留,預設是 inner。
  2. 列數是乘法:同一個鍵左邊 m 列、右邊 n 列就產生 m × n 列;多對多常代表有不該重複的鍵。
  3. 對不到的欄位補 NaN,整數欄因此變 float64;要保留整數用可空的 Int64。
  4. 鍵欄名不同用 left_on/right_on,同名的非鍵欄位加後綴(預設 _x、_y,可用 suffixes 改)。
  5. indicator=True 加上 _merge 欄,篩 left_only 就是反連接;pandas 3.0 起可寫 how='left_anti'。validate 在鍵重複時丟出 MergeError。
  6. merge 比對欄位值、join 用右表索引且預設 left、concat 不比對鍵:axis=0 加列、axis=1 依索引加欄。