其他连接方式:join 与 compare
本教程共 54 篇 · 第 36 篇 · 更新于 2026-08-11 · 约 6 分钟阅读
本节目标:学会用 join 按索引拼接表格、用 compare 对比两个表的差异,把合并家族的最后一环补齐。
前面两章讲了 concat 和 merge。concat 按轴堆叠,merge 像 SQL 一样按列匹配。这一章补两个常用工具:DataFrame.join(按索引连接)和 compare(差异对比)。
join:按索引连接
DataFrame.join 做的事情和 merge 很像,区别在于它默认按索引对齐,而不是按列。
import pandas as pd
import numpy as np
left = pd.DataFrame(
{"A": ["A0", "A1", "A2"], "B": ["B0", "B1", "B2"]},
index=["K0", "K1", "K2"],
)
right = pd.DataFrame(
{"C": ["C0", "C2", "C3"], "D": ["D0", "D2", "D3"]},
index=["K0", "K2", "K3"],
)
left.join(right)
结果只有 K0、K1、K2 三行:left 是调用者,K3 在 left 里不存在,所以没出现。这正是”左连接”的行为——join 的 how 参数默认就是 "left",还可以取 "outer"、"inner"、"right"。
left.join(right, how="outer") # 保留 K3 行,left 侧数据为 NaN
left.join(right, how="inner") # 只保留两边都有的 K0、K2
Tipjoin 默认左连接,等价于
pd.merge(left, right, left_index=True, right_index=True, how="left")。习惯 SQL 的人可以用 merge 的写法,喜欢简洁的用 join。
用 on 参数:列和索引对齐
有时候右边表的索引才是连接键,左边却把键存在一列里。用 on 参数把左边的列和右边的索引对上:
left = pd.DataFrame(
{"A": ["A0", "A1", "A2", "A3"],
"B": ["B0", "B1", "B2", "B3"],
"key": ["K0", "K1", "K0", "K1"]}
)
right = pd.DataFrame(
{"C": ["C0", "C1"], "D": ["D0", "D1"]},
index=["K0", "K1"],
)
left.join(right, on="key")
left 的 key 列里 K0 出现两次,right 的 K0 就被复制出来匹配两行——这是一对多连接。
on 支持传多列,但这时右边表的索引必须是 MultiIndex(多级索引)。多级索引的连接规则在 §22 讲过,这里先记住”多键 join 需要 MultiIndex”。
一次连接多张表
join 也接受列表,把多张表按索引依次拼到调用者右边:
right2 = pd.DataFrame({"v": [7, 8, 9]}, index=["K1", "K1", "K2"])
left.join([right, right2])
比反复 merge 清爽。
如果两边列名撞了,join 会报错,需要指定后缀:
left = pd.DataFrame({"key": [1, 2], "v": ["a", "b"]}, index=["K0", "K1"])
right = pd.DataFrame({"v": ["c", "d"]}, index=["K0", "K1"])
left.join(right, lsuffix="_l", rsuffix="_r")
结果里两个 v 列被改名为 v_l、v_r,互不干扰。
多级索引连接
左边表用普通索引、右边表是 MultiIndex 时,只要层级名对得上,join 会自动按层级匹配:
left = pd.DataFrame(
{"A": ["A0", "A1"], "B": ["B0", "B1"]},
index=pd.Index(["K0", "K1"], name="key"),
)
right_index = pd.MultiIndex.from_tuples(
[("K0", "X0"), ("K1", "X1"), ("K1", "X2")],
names=["key", "X"],
)
right = pd.DataFrame({"C": ["C0", "C1", "C2"]}, index=right_index)
left.join(right, how="inner")
结果索引变成两层(key, X),left 的 K1 匹配到 right 的两行 K1,一对多展开。两张表都是 MultiIndex 时,右边的层级必须是左边层级的子集,规则稍微严格一些,入门阶段用上面这种”单级对多级”就够。
combine_first:缺啥补啥
合并家族还有个轻量成员 combine_first。它的逻辑是:自己的缺失值,用另一张表对应位置的非缺失值补上。
df1 = pd.DataFrame([[np.nan, 3.0], [-4.6, np.nan]])
df2 = pd.DataFrame([[-42.6, np.nan], [-5.0, 1.6]])
df1.combine_first(df2)
df1 的两个 NaN 都被 df2 的同位置值补上,df2 的空缺不影响。适合”主表 + 补丁表”的场景,比如一张表存大部分数据,另一张表只有零星修正。位置对齐同样按索引和列名来,两张表结构不同也能工作。
compare:找出两表差异
compare 不合并数据,专门”找不同”。两个表结构相同时,它把不一样的位置列出来:
df = pd.DataFrame({
"col1": ["a", "a", "b", "b", "a"],
"col2": [1.0, 2.0, 3.0, np.nan, 5.0],
"col3": [1.0, 2.0, 3.0, 4.0, 5.0],
})
df2 = df.copy()
df2.loc[0, "col1"] = "c"
df2.loc[2, "col3"] = 4.0
df.compare(df2)
输出里每个差异列变成两层:self 是原表的值,other 是对比表的值。相同的单元格显示 NaN,整行都相同的行直接省略。看输出有个技巧:先扫列名,再扫非 NaN 单元格,一眼就能定位哪行哪列变了。
几个常用参数:
df.compare(df2, align_axis=0) # 差异上下堆叠,而不是左右排列
df.compare(df2, keep_shape=True) # 保留所有行列,不省略相同行
df.compare(df2, keep_shape=True, keep_equal=True) # 相同的值也保留显示
align_axis=0 适合差异多的表,上下排比左右排更容易读;keep_equal=True 适合做”逐格核对”的报表。
Warningcompare 要求两个表结构(索引、列)一致,否则会报错。做数据校验前,先确认两边列名和索引对得上。
Series 也有 compare,用来对比一列数据,用法一样。
合并家族怎么选
到这里合并工具就齐了,选型思路:
- 上下或左右堆叠 →
concat(§33) - 按列的值匹配 →
merge(§34-35) - 按索引对齐 →
join - 补缺失 →
combine_first - 找差异 →
compare
Note老教程里
df.append()加行的写法已在 pandas 2.0 移除,现在统一用pd.concat。3.0 里写旧代码会直接报错。
小结
join 默认按索引做左连接(on 参数可把左列对上右索引),一次能拼多张表;combine_first 用另一张表补自己的缺失;compare 找出两表差异,align_axis、keep_shape、keep_equal 控制展示方式。合并家族选型:堆叠用 concat、按键匹配用 merge、按索引对齐用 join、补缺失用 combine_first、找差异用 compare。