
第十四章:数据合并:merge / join(Pandas)
-
- [14.1 本章你将学会什么](#14.1 本章你将学会什么)
- [14.2 一句话先讲清:merge / join / concat 区别是什么?](#14.2 一句话先讲清:merge / join / concat 区别是什么?)
- [14.3 连接类型(inner/left/right/outer)到底差在哪?](#14.3 连接类型(inner/left/right/outer)到底差在哪?)
- [14.4 准备两张"科研风格"的示例表](#14.4 准备两张“科研风格”的示例表)
- [14.5 最常用:pd.merge(明确写清 keys)](#14.5 最常用:pd.merge(明确写清 keys))
-
- [14.5.1 inner:只保留两边都出现的 sid](#14.5.1 inner:只保留两边都出现的 sid)
- [14.5.2 left:以左表为准(科研里最常见)](#14.5.2 left:以左表为准(科研里最常见))
- [14.5.3 outer:保留并集,用于"找差异、做核对"](#14.5.3 outer:保留并集,用于“找差异、做核对”)
- [14.6 多键合并:不止 sid,一个键不够怎么办?](#14.6 多键合并:不止 sid,一个键不够怎么办?)
- [14.7 合并前必须检查的 3 件事(不做就等着翻车)](#14.7 合并前必须检查的 3 件事(不做就等着翻车))
-
- [14.7.1 键的 dtype 是否一致(最隐蔽的坑)](#14.7.1 键的 dtype 是否一致(最隐蔽的坑))
- [14.7.2 键是否唯一:1:1、1:m、m:m 你得心里有数](#14.7.2 键是否唯一:1:1、1:m、m:m 你得心里有数)
- [14.7.3 用 validate 强制约束合并关系(强烈推荐)](#14.7.3 用 validate 强制约束合并关系(强烈推荐))
- [14.8 列名冲突:suffixes 一次解决](#14.8 列名冲突:suffixes 一次解决)
- [14.9 join:当索引就是键时,用它更顺手](#14.9 join:当索引就是键时,用它更顺手)
- [14.10 研究型 Checklist:合并质量自检清单](#14.10 研究型 Checklist:合并质量自检清单)
- [14.11 本章练习(建议你真跑一遍)](#14.11 本章练习(建议你真跑一遍))
-
- [练习 1:用 outer + indicator 找出不一致 sid](#练习 1:用 outer + indicator 找出不一致 sid)
- [练习 2:用 validate 保证 1:m 合并](#练习 2:用 validate 保证 1:m 合并)
- [练习 3:处理列名冲突](#练习 3:处理列名冲突)
- [14.12 小结:你应该带走的"工程化结论"](#14.12 小结:你应该带走的“工程化结论”)
- 下一篇:
在真实科研数据里,"清洗"只是前菜;真正让数据变得可分析的,往往是把来自不同来源的表 对齐并合并 。
这一章我们用一套可复现的小例子,把
merge / join / concat讲透,并把最常踩的坑一次性踩完、填平。
14.1 本章你将学会什么
- 理解 主键(key) 与 参照完整性:为什么"能合并"不代表"合并对"
- 掌握
pd.merge()的 4 种连接 :inner / left / right / outer - 处理真实场景的常见问题:重复键、列名冲突、缺失键、类型不一致
- 学会用
indicator=True做合并质量检查(科研数据必备)
14.2 一句话先讲清:merge / join / concat 区别是什么?
concat:上下拼接或左右拼接(按轴叠起来),不负责"对齐键"merge:按某个(些)键把两张表 像 SQL JOIN 一样对齐后合并join:更偏向 按索引(index)合并 (当然也能指定on,但本质是 merge 的语法糖)
记住一句话:
科研数据合并,优先考虑 merge;只有当索引就是键时,再用 join。
14.3 连接类型(inner/left/right/outer)到底差在哪?
你可以把合并想象成"以键为准,决定保留哪些行"。
outer
保留并集 keys
right
保留右表全部 keys + 匹配到的左表
left
保留左表全部 keys + 匹配到的右表
inner
只保留交集 keys
左表 keys
右表 keys
14.4 准备两张"科研风格"的示例表
我们用一个常见场景:
- 表 A:样本基本信息(subject-level)
- 表 B:测量指标(measurement-level)
python
import pandas as pd
df_subject = pd.DataFrame({
"sid": [101, 102, 103, 104],
"group": ["control", "control", "treatment", "treatment"],
"age": [23, 25, 22, 24]
})
df_score = pd.DataFrame({
"sid": [101, 102, 102, 105],
"score": [88, 91, 95, 77],
"timepoint": ["T1", "T1", "T2", "T1"]
})
df_subject, df_score
注意:df_score 里有:
sid=102重复键(同一被试多时间点)sid=105在 A 表不存在(右表多出来的键)
14.5 最常用:pd.merge(明确写清 keys)
14.5.1 inner:只保留两边都出现的 sid
python
inner = pd.merge(df_subject, df_score, on="sid", how="inner")
inner
适用场景:
- 你只关心"有完整信息且有测量记录"的样本
- 做回归、ANOVA 时常用(但要警惕样本被你默默删掉)
14.5.2 left:以左表为准(科研里最常见)
python
left = pd.merge(df_subject, df_score, on="sid", how="left")
left
适用场景:
- 你要保留全部被试,只是某些被试缺测(score 为 NaN)
- 后续再决定缺失处理策略(删除 / 插补 / 分析缺失机制)
14.5.3 outer:保留并集,用于"找差异、做核对"
python
outer = pd.merge(df_subject, df_score, on="sid", how="outer", indicator=True)
outer
这句 indicator=True 非常关键:它会生成一列 _merge,告诉你每行来自哪里:
both:两边都有left_only:只在左表right_only:只在右表
科研数据合并我建议形成习惯:
先 outer + indicator 做核对,再决定用 left/inner 进入正式分析。
14.6 多键合并:不止 sid,一个键不够怎么办?
典型例子:同一被试多个时间点,多次测量要严格对齐。
python
df_score2 = pd.DataFrame({
"sid": [101, 102, 102, 103],
"timepoint": ["T1", "T1", "T2", "T1"],
"score": [88, 91, 95, 90]
})
df_demo = pd.DataFrame({
"sid": [101, 102, 102, 103],
"timepoint": ["T1", "T1", "T2", "T1"],
"sleep_hours": [7.0, 6.5, 6.0, 7.5]
})
multi = pd.merge(df_score2, df_demo, on=["sid", "timepoint"], how="inner")
multi
当你的测量表是"长表"(long format)时,多键合并非常常见。
14.7 合并前必须检查的 3 件事(不做就等着翻车)
14.7.1 键的 dtype 是否一致(最隐蔽的坑)
python
df_subject["sid"].dtype, df_score["sid"].dtype
如果一边是 int,另一边是 str,合并会出现"看似没报错、但全是 NaN"的假象。
统一类型:
python
df_subject["sid"] = df_subject["sid"].astype(str)
df_score["sid"] = df_score["sid"].astype(str)
14.7.2 键是否唯一:1:1、1:m、m:m 你得心里有数
python
df_subject["sid"].is_unique, df_score["sid"].is_unique
df_subject是 1(subject-level)df_score是 m(多测量)
于是合并的结果是 1:m,行数会增加,这是正常的。
但如果两边都是重复键,就会变成 m:m,行数会指数级膨胀(灾难级)。
14.7.3 用 validate 强制约束合并关系(强烈推荐)
python
pd.merge(df_subject, df_score, on="sid", how="left", validate="one_to_many")
可选:
"one_to_one""one_to_many""many_to_one""many_to_many"(不建议,除非你真的确认)
这对科研数据特别重要:它让错误尽早暴露,而不是让你在统计结果阶段才发现"样本数不对"。
14.8 列名冲突:suffixes 一次解决
当左右表有同名列(比如都叫 age、date、source),合并后会自动加后缀。你也可以自己定义:
python
df_a = pd.DataFrame({"sid":[1,2], "age":[20,21]})
df_b = pd.DataFrame({"sid":[1,2], "age":[200,210]})
pd.merge(df_a, df_b, on="sid", suffixes=("_subj", "_measure"))
14.9 join:当索引就是键时,用它更顺手
python
left = df_subject.set_index("sid")
right = df_score.set_index("sid")
joined = left.join(right, how="left")
joined
经验结论:
- 你的键在列里:用
merge - 你的键在 index 里:用
join
14.10 研究型 Checklist:合并质量自检清单
合并完成后,至少回答这 5 个问题:
- 合并前后行数是否符合预期?(特别是 1:m、m:m)
_merge中left_only/right_only是否在合理范围?- key 的缺失值是否被引入?(是否要先 dropna 还是保留)
- 是否出现意外重复?(同一 sid×timepoint 是否变多)
- 是否存在 dtype 被无意改变?(如日期字符串变对象)
14.11 本章练习(建议你真跑一遍)
练习 1:用 outer + indicator 找出不一致 sid
- 输出只在
df_subject的 sid - 输出只在
df_score的 sid
练习 2:用 validate 保证 1:m 合并
- 让
merge在你不小心把左表也变成重复键时立刻报错
练习 3:处理列名冲突
- 人为制造同名列,使用
suffixes输出一个可读的表
14.12 小结:你应该带走的"工程化结论"
- 科研合并的第一原则:先核对,再合并(outer + indicator)
- 合并的第二原则:把关系说清楚(validate)
- 合并的第三原则:键的 dtype 要一致、键的唯一性要明确