首页 / Pandas 入门教程 / 连接:merge 基础

Pandas 入门教程

连接:merge 基础

本教程共 54 篇 · 第 34 篇 · 更新于 2026-08-11 · 约 6 分钟阅读

PandasPandas 入门教程merge数据连接inner joinleft join

本节目标:会用 merge 按公共列连接两张表,分清 inner/left/right/outer 四种模式,理解一对多连接时行数怎么变。

merge:按键连接两张表

concat 是把表”摞”起来,merge 是把两张表”拼”起来:拿一列(或几列)当钥匙(key),值相同的行配对合并。这就是数据库里的 JOIN,用过 SQL 会非常眼熟。

import pandas as pd

students = pd.DataFrame({
    "学号": ["S001", "S002", "S003"],
    "姓名": ["张三", "李四", "王五"],
})
scores = pd.DataFrame({
    "学号": ["S001", "S002", "S003"],
    "数学": [85, 92, 78],
})

result = pd.merge(students, scores, on="学号")
print(result)

on 指定两表同名的键列。结果里”学号”只出现一次,两边其余列并排合在一起。

merge 的基本流程:

  1. 确定键列(同名用 on,不同名用 left_on/right_on);
  2. 确定连接模式 how;
  3. 合并后检查行数变化是否符合预期。

how:四种连接模式

merge 默认 inner(内连接),只保留两边键都匹配的行。how 参数切换模式:

scores2 = pd.DataFrame({
    "学号": ["S001", "S002", "S004"],
    "数学": [85, 92, 66],
})

print(pd.merge(students, scores2, on="学号", how="inner"))
print(pd.merge(students, scores2, on="学号", how="left"))
print(pd.merge(students, scores2, on="学号", how="right"))
print(pd.merge(students, scores2, on="学号", how="outer"))
  • inner:交集,两边都有的学号;
  • left:以左表为准,右表没匹配到的填 NaN;
  • right:以右表为准,左表没匹配到的填 NaN;
  • outer:并集,两边独有行都保留,缺失填 NaN。

四种模式对应 SQL 的 INNER JOIN、LEFT/RIGHT OUTER JOIN、FULL OUTER JOIN。没匹配上的位置一律填 NaN,所以连接后出现 NaN 不一定数据有缺失,可能是键没配上。想区分这两种情况,用下一章的 indicator。

结果列的顺序也有规律:键列保持在左表中的原位置,后面接右表其余列。想调整顺序,合并后用 df[列名列表] 重排即可。

Tip

记不住就抓重点:left 是”给左表补信息”,outer 是”两边都不丢”。实际工作中 left 和 inner 用得最多。

还有个 cross 模式:不按键匹配,两表所有行两两组合(笛卡尔积),行数等于两边行数相乘:

a = pd.DataFrame({"x": ["a", "b"]})
b = pd.DataFrame({"y": [1, 2, 3]})
print(pd.merge(a, b, how="cross"))

键不在同一列:left_on / right_on

两表键列名不同时,分别指定:

scores3 = pd.DataFrame({
    "student_id": ["S001", "S002", "S003"],
    "数学": [85, 92, 78],
})

result = pd.merge(students, scores3, left_on="学号", right_on="student_id")
print(result)

结果里”学号”和”student_id”都在,内容一样,想清爽就合并后 drop 掉一列。

一对一、一对多:行数怎么变

merge 的结果行数由键的匹配情况决定:

  1. 一对一:两边键都唯一,一行配一行;
  2. 一对多:一边键唯一、另一边有重复,重复方每行配一次,行数变多;
  3. 多对多:两边都有重复键,配对组合相乘(笛卡尔积),行数膨胀最快。

判断”谁重复谁就是多”:每个学号一个学生、每个班级一个教室,键就该唯一;一条订单多行明细,订单号必然重复。

先看一对多:一个班级配一个教室,但左表里班级重复出现。

left = pd.DataFrame({
    "班级": ["1班", "1班", "2班"],
    "班主任": ["陈老师", "陈老师", "刘老师"],
})
right = pd.DataFrame({"班级": ["1班", "2班"], "教室": ["A101", "A102"]})

result = pd.merge(left, right, on="班级")
print(result)

左表”1班”两行,右表”1班”一行,结果里两行各自配上教室 A101。方向反过来(左表唯一、右表重复)效果对称。

多对多更刺激:两边各有两个”1班”,结果里会出现 2×2 个”1班”的组合行。键重复 + outer 的组合也一样按笛卡尔积配对,只是独有行额外保留:

dl = pd.DataFrame({"A": [1, 2], "B": [2, 2]})
dr = pd.DataFrame({"A": [4, 5, 6], "B": [2, 2, 2]})
print(pd.merge(dl, dr, on="B", how="outer"))

B=2 在两边都重复,结果 2×3=6 行,同名列自动加 _x/_y 后缀区分。

Warning

多对多连接行数急剧膨胀,两边重复键一多,结果可能比原表大几个数量级,甚至撑爆内存。合并前先用 duplicated 查键,或用下一章的 validate 兜底。

连接后的 NaN 怎么处理

连接产生的 NaN 是”没配上”的标记,不是脏数据。要不要清掉看业务:left 连接后右表列出现 NaN,说明左表这些行在右表没有对应记录,这本身就是信息,不该随手 dropna 掉。

想清理时用 §24/25 的 dropna/fillna;想区分”原本就缺失”和”连接没配上”,用下一章的 indicator。

索引也能当键

键不一定是普通列。想按索引对齐(像 concat 的对齐那样),用 left_index=True / right_index=True:

scores_by_index = scores.set_index("学号")
result = pd.merge(students, scores_by_index, left_on="学号", right_index=True)
print(result)

left_index/right_index 和 left_on/right_on 可以混用——一边按列、一边按索引,是”主表 + 字典表”连接时的常见写法。更省事的索引连接是 DataFrame.join,它默认按索引配对,§36 再展开。先把列键连接玩熟。

小结

merge 按键列连接两张表:on 指定同名键、how 切换四种模式(inner/left/right/outer)、left_on/right_on 处理异名键。行数变化由键的匹配情况决定——一对多会变多、多对多会膨胀,连接前先查键的唯一性。没配上的位置填 NaN,那不是脏数据,是”没对上”的标记。