DataFrame 合并与拼接
merge 用于按键关联行,concat 用于堆叠或对齐整个对象。
核心不在于语法,而在于明确你预期的数据关系。
明确连接意图
import pandas as pd
orders = pd.DataFrame({"order_id": [1, 2, 3], "customer_id": ["A", "A", "B"]})
customers = pd.DataFrame({"customer_id": ["A", "C"], "name": ["Ada", "Lin"]})
result = orders.merge(
customers,
on="customer_id",
how="left",
validate="many_to_one",
indicator=True,
suffixes=("_order", "_customer"),
)
基数校验
在查看输出前,先通过 validate 参数声明预期的关系契约:
one_to_one:两侧键均唯一。one_to_many:左侧键唯一。many_to_one:右侧键唯一。many_to_many:预期存在重复;需显式检查输出规模。
意外的重复键会导致行数倍增,进而破坏聚合结果。若数据关系属于业务契约的一部分,务必在合并前检查键的唯一性。
诊断未匹配键
设置 indicator=True 后,检查 _merge 列的值(left_only、right_only、both)。
不要直接丢弃该指示列,而应解释未匹配记录的原因。
注意:Pandas 在合并时会将空值键(null keys)相互匹配,这与典型 SQL 连接语义不同。 如果这种匹配没有业务意义,请先删除、填充或隔离空键。
拼接
january, february = orders.iloc[:2], orders.iloc[2:]
features = orders[["order_id"]]
labels = pd.Series([1, 0, 1], index=orders.index)
rows = pd.concat([january, february], ignore_index=True)
columns = pd.concat([features, labels.rename("target")], axis="columns")
按行拼接(默认)会对齐列名,按列拼接(axis="columns")会对齐索引。
仅当旧行标签无业务含义时,才使用 ignore_index=True。
若需保留来源分区的可追溯性,请添加 keys= 参数。
时间序列连接
在按连接键排序后,使用 merge_asof 进行最近键时间连接。
务必指定方向(direction)和容差(tolerance),防止“最近匹配”退化为任意匹配。
连接类型与行数膨胀
merge API 返回新的 DataFrame。本例左连接保留三条订单,姓名依次为 Ada、Ada、缺失,_merge 依次为 both、both、left_only。左连接无法发现没有订单的客户;外连接才能把 C 标为 right_only。内连接只保留匹配键(本例两行),右连接保留右侧键,外连接保留两侧键的并集。
同一键若左侧出现两次、右侧出现三次,就会产生六行。validate="many_to_many" 不检查唯一性,也不限制输出规模。如果 customers 中 A 重复,本例的 many_to_one 校验会抛出 pd.errors.MergeError。显式指定 on= 可避免意外地按所有同名列连接。