差分对比需先按业务主键对齐索引、统一列名与顺序、标准化数据类型(数值转float64、字符串fillna("")、时间转datetime),再用compare()或merge+布尔掩码提取差异,最后结合业务规则定义差异语义。

差分对比的本质是“按行对齐后逐列比较”
直接用 df1 != df2 会出错——Pandas 默认要求索引和列完全一致才允许广播比较。实际中两个 DataFrame 往往存在:行顺序不同、索引类型不匹配(如一个是整数索引,一个是字符串 ID)、列顺序不一致、甚至列名有细微差异("user_id" vs "userid")。这时候不能硬比,得先对齐。
核心思路是:把两个 DataFrame 都重设为以业务主键(比如 "id" 或 ["order_no", "date"])为索引,再用 reindex 统一列顺序,最后用 compare()(Pandas ≥ 1.5)或手动布尔运算做差异提取。
用 compare() 前必须确保索引可对齐
compare() 不接受任意索引,它默认按当前索引位置对齐(类似 NumPy 的逐行比较),不是按值匹配。所以如果 df1 和 df2 的索引不一致,结果全是 NaN 或误报。
- 先检查是否已有业务主键列:如果有
"id"列,用df1.set_index("id")和df2.set_index("id")统一索引 - 若无单一主键,用多列组合:
df1.set_index(["user_id", "timestamp"]) - 若索引是默认
RangeIndex但语义上应按顺序比(如日志流水),显式重置:df1.reset_index(drop=True)+df2.reset_index(drop=True) - 列名需严格一致:用
df2.columns = df1.columns强制对齐,或提前映射别名(如df2.rename(columns={"userid": "user_id"}))
手动实现差分时避开 dtypes 不兼容陷阱
当某列在 df1 是 int64、在 df2 是 float64(比如空值被转成 NaN),直接 df1 == df2 会返回全 False —— Pandas 对类型敏感,1 != 1.0 在某些版本下成立。
- 统一数值列类型:对要比较的列批量转
float64或object(后者保留NaN行为):df1[col].astype("float64") - 字符串列务必用
.fillna("")再比,否则NaN != NaN导致漏差 - 时间列用
pd.to_datetime()标准化后再比,避免字符串格式("2023-01-01"vs"01/01/2023")干扰 - 布尔列注意
None和np.nan差异,建议先df.fillna(False)或明确替换
生成可读差分报告的关键字段控制
compare() 默认只返回差异单元格,但生产环境需要知道“哪一行、哪一列、旧值多少、新值多少”。靠 result = df1.compare(df2, keep_shape=True, keep_equal=False) 只能得到带 self/other 层级的结构,不易直接导出表格。
更实用的做法是构造差异 DataFrame:
diff = df1.merge(df2, left_index=True, right_index=True, suffixes=('_old', '_new'), how='outer')
mask = (diff.filter(regex='_old$') != diff.filter(regex='_new$')).any(axis=1)
diff_report = diff[mask].copy()
这样每行含原始索引 + 所有旧/新字段,后续可按需筛选、导出 CSV 或高亮渲染。注意 how='outer' 能捕获新增/删除行,若只关心共有的行,改用 how='inner'。
真正难的不是算出差,而是定义“什么算差异”——空字符串和 None 是否等价?金额四舍五入到分是否容忍?这些逻辑必须在对齐和类型处理之前就明确,否则后面所有操作都失真。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











