3. 去重规则误伤:基于全字段去重丢失有效重复记录的修复
问题定位:全字段去重为什么误伤合法交易
某支付流水表 pay_flow 在清洗阶段执行了 drop_duplicates()(默认按全部列去重),清洗后行数从 128,640 降到 121,905,少了 6,735 行。业务方反馈:同一用户在同一天的多笔小额充值被合并成一笔,对账金额出现缺口。这类问题的根因不是"去重错了",而是去重键选错了——全字段去重把"字段值恰好相同"误当成"业务上重复"。
先看一个最小复现:同一用户 U1001 在 10:02 和 10:05 各充值 50 元,两笔交易除流水号外字段完全一致。全字段去重会保留第一笔、丢弃第二笔,而这两笔在业务上是两笔独立交易。
import pandas as pd
raw = pd.DataFrame({
"flow_id": ["F001", "F002", "F003", "F004"],
"user_id": ["U1001", "U1001", "U1002", "U1001"],
"trade_time":["2024-05-01 10:02", "2024-05-01 10:05",
"2024-05-01 10:02", "2024-05-01 10:02"],
"amount": [50.0, 50.0, 50.0, 50.0],
"channel": ["APP", "APP", "APP", "APP"],
})
# 错误做法:全字段去重
bad = raw.drop_duplicates()
print(len(raw), "->", len(bad)) # 4 -> 3,F002 被误删
关键判断:flow_id 是支付系统生成的全局唯一流水号,天然具备唯一性;user_id + trade_time + amount + channel 只是业务属性,不能作为唯一标识。因此正确的去重键应当是 flow_id,而不是全部字段。
重构去重键并验证保留记录
修复分两步:先确认业务主键,再按主键去重,最后用金额合计与行数双重校验。注意 flow_id 本身也可能存在重复(例如上游重放),因此去重键应设为 flow_id,并保留 keep="first" 以保留首次入库记录。
# 1) 确认业务主键:flow_id 应唯一
dup_flow = raw[raw.duplicated(subset=["flow_id"], keep=False)]
print("flow_id 重复行数:", len(dup_flow)) # 示例数据为 0
# 2) 按业务主键去重
fixed = raw.drop_duplicates(subset=["flow_id"], keep="first")
print(len(raw), "->", len(fixed)) # 4 -> 4
# 3) 双重校验:行数与金额合计
assert len(fixed) == len(raw), "合法记录被误删"
assert fixed["amount"].sum() == raw["amount"].sum(), "金额合计不一致"
print("校验通过,金额合计:", fixed["amount"].sum()) # 200.0
若 flow_id 确实存在重复(上游重放),则需进一步判断:重复的 flow_id 是否金额、时间也一致。若完全一致,说明是重放,保留一条即可;若金额不同,说明是流水号生成冲突,应上报而非静默去重。可用如下检查区分:
conflict = (raw.groupby("flow_id")["amount"]
.nunique()
.loc[lambda s: s > 1])
print("流水号相同但金额不同的冲突:", conflict.to_dict())
# 示例数据为空;若不为空,应保留全部并标记待人工核查
去重键的选择原则:优先使用系统生成的唯一标识(流水号、订单号、事件 ID);只有当业务上确实不存在唯一标识时,才退化为"业务属性组合键",且必须经过业务方确认该组合能唯一确定一笔交易。
不要用
drop_duplicates() 不带参数直接跑在生产数据上。默认全字段去重会静默删除"看起来一样"的合法记录,且不报错、不留痕,是最难排查的清洗缺陷之一。修复后的验收清单
| 检查项 | 方法 | 通过条件 |
|---|---|---|
| 去重键唯一性 | 对候选主键做 duplicated 统计 | 重复行数为 0,或重复项已标记待核查 |
| 行数守恒 | 对比清洗前后行数 | 仅删除真实重复,合法记录数不变 |
| 金额守恒 | 对比清洗前后 amount 合计 | 合计一致,无缺口 |
| 用户维度核对 | 按 user_id 分组对比笔数与金额 | 与业务系统对账结果一致 |
本例的核心结论:去重不是"删掉一样的行",而是"按业务唯一性删掉真正的重复"。把去重键从全字段改为 flow_id 后,行数与金额双重守恒,合法重复交易得以保留。