如何获取Pandas对比DataFrame时标记为left_only/right_only的行的差异列信息(结合Issr列校验场景)
如何获取Pandas对比DataFrame时标记为left_only/right_only的行的差异列信息(结合Issr列校验场景)
要解决你的核心问题——精准定位DataFrame对比时的差异列,并结合Issr列的规则校验违规行,我们需要跳出仅依赖merge(indicator=True)的思路,转而通过唯一键匹配行+逐列对比差异的方式实现。以下是分步解决方案:
步骤1:修正数据结构,添加唯一标识列
你的表格中SNo是每行的唯一标识(区分更新行和新增行),先将其补充到数据源中,方便后续行匹配:
import pandas as pd # 补充SNo列的base数据 base = { 'SNo': [1, 2, 3, 4], 'Rank': [10,14,15,50], 'Ctry': ['A', 'B', 'C', 'D'], 'Cat': ['Book', 'Laptop', 'Pen', 'Pen'], 'Issr': ['Y', 'C', 'J', ''], 'Ref': ['100', '101', '102', '103'] } # 补充SNo列的current数据 current = { 'SNo': [1, 2, 3, 4, 5, 6], 'Rank': [10,14,15,50,24,24], 'Ctry': ['A', 'B', 'C', 'D', 'K', 'RT'], 'Cat': ['Book1', 'Laptop', 'Pen', 'Pen', 'Pencil', 'Pencil'], 'Issr': ['Y', '', 'J', 'U', 'W', ''], 'Ref': ['100', '101', '102', '103', '101', '201'] } # 转换为DataFrame并设置SNo为索引(简化行匹配逻辑) base_df = pd.DataFrame(base).set_index('SNo') current_df = pd.DataFrame(current).set_index('SNo')
步骤2:分离新增行与更新行
将current中的行分为两类,分别处理:
- 新增行:
base中不存在的行 - 更新行:
base中已存在,但可能有列值变化的行
# 1. 筛选新增行(current有,base无) new_rows = current_df[~current_df.index.isin(base_df.index)] # 2. 筛选更新行的索引,并提取两边的对应数据 common_idx = current_df.index.intersection(base_df.index) current_common = current_df.loc[common_idx] base_common = base_df.loc[common_idx]
步骤3:定位更新行的差异列(含Issr)
对于更新行,我们可以逐列对比,找出所有变化的列,重点关注Issr列的变化是否违反规则:
# 生成布尔值DataFrame:True表示该列值在两行中不同 all_column_changes = current_common.ne(base_common) # 为每个更新行生成差异列的列表 row_diff_details = all_column_changes.apply( lambda row: row.index[row].tolist(), axis=1 ).rename('diff_columns') # 单独提取Issr列的变化细节,用于规则校验 issr_change_details = pd.DataFrame({ 'base_Issr': base_common['Issr'], 'current_Issr': current_common['Issr'], 'diff_columns': row_diff_details })
执行后,issr_change_details会清晰展示每个更新行的Issr原值、新值,以及所有差异列:
base_Issr current_Issr diff_columns SNo 1 Y Y [Cat] 2 C [Issr] 3 J J [] 4 U [Issr]
步骤4:结合规则校验违规行
根据你提出的两个Issr规则,标记所有违规行:
规则1:新增行的Issr必须为空('')
# 新增行中Issr非空的即为违规 new_row_violations = new_rows[new_rows['Issr'] != ''].assign( violation_type='新增行违规:Issr非空' )
规则2:更新行的Issr不能从非空改为非空(仅允许改为空或保持不变;原空值不能改为非空)
# 标记更新行中的Issr违规情况 issr_update_violations = issr_change_details[ # 情况1:原Issr非空,新值既不是原值也不是空 ((issr_change_details['base_Issr'] != '') & (issr_change_details['current_Issr'] != issr_change_details['base_Issr']) & (issr_change_details['current_Issr'] != '')) | # 情况2:原Issr为空,新值非空 ((issr_change_details['base_Issr'] == '') & (issr_change_details['current_Issr'] != '')) ].join(current_common).assign( violation_type='更新行违规:Issr非法修改' )
步骤5:汇总所有违规行
# 合并两类违规行 all_violations = pd.concat([new_row_violations, issr_update_violations]) print("所有违规行:") print(all_violations[['Rank', 'Ctry', 'Cat', 'Issr', 'Ref', 'violation_type']])
输出结果完全匹配你提到的违规场景:
Rank Ctry Cat Issr Ref violation_type SNo 5 24 K Pencil W 101 新增行违规:Issr非空 4 50 D Pen U 103 更新行违规:Issr非法修改
关键说明
- 为什么不用
merge(indicator=True)?
它是按整行内容判断是否存在,行中任意一列变化都会被标记为left_only/right_only,无法定位具体差异列。而通过唯一键匹配+逐列对比,能精准找到变化的列。 - 唯一标识的选择:
这里用SNo作为唯一键,你可以根据实际业务调整为其他组合键(比如Ref+Cat等),确保能准确匹配base和current中的同一行。
备注:内容来源于stack exchange,提问作者adarsh
相关产品推荐
相关产品推荐

