3-2 重复记录的去重会诊 本节摘要:duplicated负责标记重复,dropduplicates负责去重,keep参数决定留哪条,subset决定按哪些列判重。本节把重复分成整行重复与关键字段重复两型,讲清各自的会诊流程与复核动作——去重不是一行代码的事,是"判型、去重、复核"三步。 上一节处理了缺失,内科还有一类高频病:周六补录时同一笔交易进了两遍系统。重复记录的危害在聚合端爆发——第05章一分组,销售额凭空翻倍。内科阶段就要把它清干净。 先判型:两种重复,两种治法 整行重复可以直接删——两条信息完全一样,留哪条都无所谓。关键字段重复不能直接删:它说明系统里存在冲突的记录,删哪条都是业务决策,不是技术决策。 整行重复:直接处置 💡 关键直觉:keep=False是我最推荐的复核姿势。
本节摘要:duplicated负责标记重复,drop_duplicates负责去重,keep参数决定留哪条,subset决定按哪些列判重。本节把重复分成整行重复与关键字段重复两型,讲清各自的会诊流程与复核动作——去重不是一行代码的事,是"判型、去重、复核"三步。
上一节处理了缺失,内科还有一类高频病:周六补录时同一笔交易进了两遍系统。重复记录的危害在聚合端爆发——第05章一分组,销售额凭空翻倍。内科阶段就要把它清干净。
import pandas as pd df = pd.DataFrame({ '交易号': ['T01', 'T02', 'T02', 'T03', 'T03'], '门店': ['A', 'B', 'B', 'C', 'C'], '金额': [1280, 960, 960, 880, 880], '备注': ['正常', '补录', '正常', '正常', '复核中'], }) # 型一:整行完全重复(系统重放、导入跑了两遍) print(df.duplicated()) # 默认keep='first',首次出现为False # 0 False # 1 False # 2 True # 3 False # 4 False # T03备注不同,整行不算重复 # 型二:关键字段重复(同一交易号出现多次,其余字段有出入) print(df.duplicated(subset=['交易号'])) # 0 False # 1 False # 2 True # 3 False # 4 True # 交易号重了,哪怕备注不同
整行重复可以直接删——两条信息完全一样,留哪条都无所谓。关键字段重复不能直接删:它说明系统里存在冲突的记录,删哪条都是业务决策,不是技术决策。
clean = df.drop_duplicates() # 等价于keep='first' print(clean.shape) # (4, 4) # keep='last':保留最后一次(补录场景常见,后录的覆盖先录的) clean2 = df.drop_duplicates(keep='last') print(clean2.shape) # (4, 4) # keep=False:重复的全部标出来,一条不留——用于复核而非删除 dups = df[df.duplicated(keep=False)] print(dups['交易号'].tolist()) # ['T02', 'T02']
💡 关键直觉:keep=False是我最推荐的复核姿势。去重前先看一眼"到底有哪些组在重复、每组几条",比闷头删掉更符合会诊精神——万一是采集端在刷数据呢?
# 第一步:找重号组,每组条数 counts = df['交易号'].value_counts() conflicts = counts[counts > 1] print(conflicts) # 交易号 # T02 2 # T03 2 # 第二步:把重号组全捞出来人工过目 suspects = df[df['交易号'].isin(conflicts.index)] print(suspects[['交易号', '金额', '备注']]) # 交易号 金额 备注 # 1 T02 960 补录 # 2 T02 960 正常 # 3 T03 880 正常 # 4 T03 880 复核中 # 第三步:确认金额一致才可机械去重;金额不一致必须回业务系统对账 consistent = suspects.groupby('交易号')['金额'].nunique() print(consistent) # 交易号 # T02 1 # 金额只有一种取值,可安全去重 # T03 1 resolved = df.drop_duplicates(subset=['交易号'], keep='last') print(resolved.shape) # (3, 4)

# 去重后行号是断的,给下游前重排 final = resolved.drop_duplicates().reset_index(drop=True) # 复核动作:去重前后金额总数对比,差额应恰好等于删掉的重复行 print(df['金额'].sum()) # 5960 print(final['金额'].sum()) # 3120 print('删掉的重复金额:', 5960 - 3120) # 2840 = 960 + 880 + ... 对得上才算闭环
一个常被问到的问题:去重该在读入后马上做,还是留到分析前?我的答案是按业务键尽早去。重复记录每多留一个环节,就多一次被聚和、被合并、被当作样本放大的机会——第05章的分组、第06章的merge都会忠实地把重复放大到结果里。读入后第一件事做整行去重(系统重放型),业务键冲突型的会诊可以留到聚合前,但绝不留到聚合后:那时已经分不清"翻倍的销售额"是业绩还是脏数据了。
# 读入即整行去重,成本一次,收益全程 raw = pd.read_csv('sales.csv') clean = raw.drop_duplicates() print(len(raw) - len(clean), '条整行重复已清除')
两类基础病各自的口诀再压缩一次:缺失——探测用isna、判断看语义、补值跟指标、复核查行数;重复——先判型再动手、关键字段先核金额、keep=False盘组、去重后对总额。内科的全部输出应该是一张"处置留痕"的表:哪几行缺了补了、哪几组重了删了,都有据可查。留痕不是形式主义,下个月同样的脏数据再来时,这张记录就是现成的医嘱。
内科结束。下一章进专科门诊:数据类型转科、异常值影像判定、字符串规范化。