You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python Pandas对比两个DataFrame并标注差异的实现方法

方案说明

你之前两种写法的核心问题如下:

  • 第一段merge代码未指定匹配键,默认以所有同名字段为连接键,且最终仅筛选left_only结果,天然会过滤掉右表独有的所有记录,结果必然不全。
  • 第二段merge代码未做字段级差异聚合,同一条匹配键对应的左右表差异会拆分为多列展示,没有汇总差异信息,大表下排查效率极低。

70万行量级无主键场景下的全量比对可以按以下步骤实现,内存占用低、运行速度快,最终可输出带Comments列的标准化差异结果。


前置准备

无主键场景做字段级差异比对前,必须先指定业务匹配键组合:即若干个字段联合起来可以唯一确定一条业务记录(比如姓名+手机号+注册时间,或者你场景里的Title+其他唯一属性字段),否则无法判定两条部分字段不同的记录是「同一条数据的内容变更」还是「完全无关的两条独立记录」。
如果只需要做整行级别的差异筛查(即判断整行是否完全一致),不需要指定匹配键,直接用整行哈希匹配即可。


实现代码

1. 预处理(必做)

统一两个表的字段、数据类型,避免同值不同类型导致的误判:

import pandas as pd
import numpy as np

# 先提取两个表共有的字段,避免独有名列导致的匹配错误
common_cols = [col for col in df1.columns if col in df2.columns]
# 统一列顺序、重置索引、统一数据类型(数值类字段可按需保留原类型,转字符串是为了规避int/str格式的误判)
df1 = df1[common_cols].reset_index(drop=True).fillna('').astype(str)
df2 = df2[common_cols].reset_index(drop=True).fillna('').astype(str)

2. 整行快速比对(可选,适合先快速统计差异量级)

用整行哈希做匹配,比全字段merge快3-5倍,70万行数据10秒内可出结果:

# 为每行生成唯一哈希值,避免逐字段比对的性能损耗
df1['row_hash'] = pd.util.hash_pandas_object(df1[common_cols], index=False)
df2['row_hash'] = pd.util.hash_pandas_object(df2[common_cols], index=False)

# 直接筛选三类结果
only_in_xls = df1[~df1['row_hash'].isin(df2['row_hash'])].drop(columns='row_hash') # xls独有
only_in_csv = df2[~df2['row_hash'].isin(df1['row_hash'])].drop(columns='row_hash') # csv独有
full_match_rows = df1[df1['row_hash'].isin(df2['row_hash'])].drop(columns='row_hash') # 完全匹配

3. 带Comments列的字段级全量比对

替换代码里的match_keys为你选定的业务匹配键组合即可运行,结果会自动标注匹配状态、具体差异字段、单边记录来源:

# 替换为你的业务匹配键列表,例如 match_keys = ['Title', 'user_id', 'create_time']
match_keys = ['Title']
# 剩余所有字段为待比对的内容字段
compare_cols = [col for col in common_cols if col not in match_keys]

# 仅以匹配键做外连接,减少不必要的列匹配,降低内存占用
merged_res = pd.merge(
    df1, df2,
    on=match_keys,
    how='outer',
    suffixes=('_xls', '_csv'),
    indicator='source_mark'
)

# 初始化Comments列
merged_res['Comments'] = ''

# 标记完全匹配的记录
both_exist_mask = merged_res['source_mark'] == 'both'
exact_match_mask = both_exist_mask
for col in compare_cols:
    exact_match_mask = exact_match_mask & (merged_res[f'{col}_xls'] == merged_res[f'{col}_csv'])
merged_res.loc[exact_match_mask, 'Comments'] = '完全匹配'

# 标记有字段差异的记录,汇总具体差异字段和值
field_diff_mask = both_exist_mask & (~exact_match_mask)
def mark_diff(row):
    diff_detail = []
    for col in compare_cols:
        if row[f'{col}_xls'] != row[f'{col}_csv']:
            diff_detail.append(f"{col}:xls值为{row[f'{col}_xls']},csv值为{row[f'{col}_csv']}")
    return f'字段存在差异:{";".join(diff_detail)}'
merged_res.loc[field_diff_mask, 'Comments'] = merged_res.loc[field_diff_mask].apply(mark_diff, axis=1)

# 标记单边存在的记录
merged_res.loc[merged_res['source_mark'] == 'left_only', 'Comments'] = '仅在xls文件中存在'
merged_res.loc[merged_res['source_mark'] == 'right_only', 'Comments'] = '仅在csv文件中存在'

# 整理最终输出结构,合并同名字段,去除冗余后缀
final_diff_df = merged_res[match_keys + [f'{col}_xls' for col in compare_cols] + ['Comments']].copy()
for col in compare_cols:
    final_diff_df.rename(columns={f'{col}_xls': col}, inplace=True)
    # 补全csv独有的记录的字段值
    right_only_rows = merged_res['source_mark'] == 'right_only'
    final_diff_df.loc[right_only_rows, col] = merged_res.loc[right_only_rows, f'{col}_csv']

大表性能优化提示
  • 不要用逐行迭代的原生Python循环做比对,上述代码用Pandas向量化逻辑+哈希预筛,70万行数据在16G内存的普通PC上运行时间不超过1分钟。
  • 预处理阶段必须统一空值格式,将np.nan、None、空字符串、全空格等统一为空字符串或固定标识,避免空值格式差异导致的误判。
  • 如果单文件内存占用过高,可以用pd.read_csv(chunksize=100000)、pd.read_excel(chunksize=100000)分块读取,逐块完成比对后再拼接结果,避免内存溢出。
  • 如果不需要查看具体差异内容,仅需要统计差异占比,直接用整行哈希的集合运算即可,不需要做merge操作。

内容的提问来源于stack exchange,提问作者trier89

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 01:45:46