
本文介绍使用 pandas 对具有相同 id 的重复行进行智能合并的方法:通过 bfill() + groupby().head(1) 或 ffill() + tail(1),自动填充并聚合每组中各列的首个有效值,实现“空值互补、一行归总”的清洗目标。
本文介绍使用 pandas 对具有相同 id 的重复行进行智能合并的方法:通过 bfill() + groupby().head(1) 或 ffill() + tail(1),自动填充并聚合每组中各列的首个有效值,实现“空值互补、一行归总”的清洗目标。
在数据清洗与报表生成场景中,常遇到原始数据因分步采集、多源拼接或系统导出限制,导致同一业务实体(如 P ID)被拆分为多行,仅在部分列(如 Q1、Q2、Q3)填写内容,其余为空(NaN 或空字符串)。此时简单去重(drop_duplicates())会丢失信息,而直接取 first()/last() 又可能遗漏关键字段。理想的合并策略是:对每个 ID 分组,将各列的非空值“补全”到同一行中,优先保留任意一个有效值即可。
pandas 提供了一种简洁高效的解决方案:利用前向填充(ffill)或后向填充(bfill)配合分组取首行(head(1))或末行(tail(1)),实现跨行信息聚合。
✅ 推荐方法:bfill().head(1)(推荐用于多数场景)
该方法对每组内行按从下到上顺序填充(即用下方的有效值向上覆盖),再取第一行——等效于“取每列第一个非空值”。
import pandas as pd
import numpy as np
# 构造示例数据(模拟原始 Excel 导入结果)
df = pd.DataFrame({
"P ID": [318, 318, 319, 319],
"T ID": [495, 495, 496, 496],
"C ID": ["00036282", "00036282", "00036283", "00036283"],
"Q1": ["NO", None, "Yes", None],
"Q2": [None, "Yes", None, "Yes"],
"Q3": [None, "All cost covered", "No additional costs", None],
})
# 按 P ID 分组,对每组执行 bfill 后取首行
merged_df = df.groupby("P ID").apply(lambda x: x.bfill().head(1)).reset_index(drop=True)
print(merged_df)输出:
立即学习“Python免费学习笔记(深入)”;
P ID T ID C ID Q1 Q2 Q3 0 318 495 00036282 NO Yes All cost covered 1 319 496 00036283 Yes Yes No additional costs
? 原理说明:bfill() 将每组内 Q1 列的 "NO" 向上填充(但本例中上方无空位),Q2 的 "Yes" 向上填充至第 0 行,Q3 的 "All cost covered" 同理;随后 head(1) 提取每组填充后最靠上的完整行,自然汇集所有非空字段。
⚙️ 替代方案:ffill().tail(1)
若数据中有效值更常出现在上方(如首行为主记录,后续行为补充),可改用前向填充 + 取末行:
merged_df_alt = df.groupby("P ID").apply(lambda x: x.ffill().tail(1)).reset_index(drop=True)效果一致,逻辑对称,可根据实际数据分布习惯选择。
⚠️ 注意事项与最佳实践
-
空值类型需统一:确保缺失值为 pd.NA 或 np.nan(而非空字符串 "" 或 " ")。若存在空字符串,建议预处理:
df = df.replace(r'^\s*$', np.nan, regex=True) # 清理空白字符串
-
ID 列必须完全一致:P ID、T ID、C ID 等分组键需严格匹配(注意数据类型,避免 int 与 str 混用)。可强制转换:
df["P ID"] = df["P ID"].astype(str).str.strip()
不适用于需聚合计算的数值列:此方法本质是“取非空值”,不支持求和、均值等统计操作。若含数值列且需汇总,请单独定义聚合函数(如 agg({'amount': 'sum', 'Q1': lambda x: x.dropna().iloc[0] if not x.dropna().empty else None}))。
性能提示:对超大数据集(>100 万行),避免在 apply 中调用 bfill;可改用 groupby(...).bfill().drop_duplicates(subset=['P ID'], keep='first') 实现等效逻辑,效率更高。
✅ 总结
面对“同 ID 多行、各列填空式分布”的典型脏数据,groupby().bfill().head(1) 是兼顾简洁性、可读性与鲁棒性的首选方案。它无需手动指定每列聚合方式,自动识别并提取每组内各字段的首个有效值,完美适配问卷类、工单类、客户档案类数据的标准化整合需求。将其嵌入 ETL 流程,即可将 3130 行原始数据稳定压缩至约 900 行高质量结果,真正实现自动化、零人工干预的精准合并。










