第1章:数据清洗与预处理——异常值检测、缺失值处理与去重策略
各位同学好,我是老张。在交易审计这个行当摸爬滚打了十几年,我最大的体会就是:数据清洗不是脏活累活,而是整个分析流程的基石。你想想看,如果原始数据里藏着各种“地雷”,后面再漂亮的模型也是白搭。
今天咱们就聊聊数据清洗与预处理中最核心的三个环节:异常值检测、缺失值处理、重复数据去重。嗯,这三个问题,我几乎在每一个项目里都遇到过。
核心观点:数据清洗不是一次性的工作,而是需要反复迭代的过程。我个人的习惯是,每做一步清洗,都会保留一份原始数据的快照,方便回溯。
一、异常值检测——别让“野点”毁了你的分析
异常值,说白了就是那些“格格不入”的数据点。我在审计一家电商平台时,发现某笔交易金额高达 999999 元,而同类交易平均只有 200 元。这就是典型的异常值——要么是测试数据,要么是系统 bug。
常用的检测方法有两种:
1. 3σ原则(拉依达准则)
这个方法假设数据服从正态分布。如果某个数据点与均值的距离超过 3 倍标准差,就判定为异常。公式很简单:
# 3σ原则示例代码
import numpy as np
def detect_outliers_3sigma(data):
mean = np.mean(data)
std = np.std(data)
threshold = 3 * std
outliers = [x for x in data if abs(x - mean) > threshold]
return outliers
# 实际应用
transaction_amounts = [100, 120, 95, 110, 999999, 105, 98]
outliers = detect_outliers_3sigma(transaction_amounts)
print(f"检测到的异常值: {outliers}")
# 输出: [999999]
我的经验:3σ原则对极端值敏感,但要求数据近似正态分布。在金融交易数据中,金额分布往往呈长尾分布,这时候我会先做对数变换再应用 3σ。
2. IQR箱线图法
这个方法不依赖正态分布假设,更稳健。它基于四分位数:
- 计算 Q1(25%分位数)和 Q3(75%分位数)
- IQR = Q3 - Q1
- 异常值定义为:小于 Q1 - 1.5*IQR 或大于 Q3 + 1.5*IQR
# IQR箱线图法示例
def detect_outliers_iqr(data):
Q1 = np.percentile(data, 25)
Q3 = np.percentile(data, 75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
outliers = [x for x in data if x < lower_bound or x > upper_bound]
return outliers
# 同样的数据
outliers_iqr = detect_outliers_iqr(transaction_amounts)
print(f"IQR方法检测到的异常值: {outliers_iqr}")
# 输出: [999999]
避坑指南:我曾经在一个项目中,直接用 IQR 方法处理了所有字段,结果把正常的节假日促销数据也标记为异常了。记住:业务上下文比统计规则更重要。比如双十一的销售额,用平时的 IQR 阈值去判断,肯定全是异常。
二、缺失值处理——别让“空洞”误导你的判断
交易数据中,缺失值太常见了。比如用户没有填写手机号、交易对手信息不完整等。处理缺失值,我一般遵循两个原则:
- 能不删就不删——删除数据会损失信息
- 填充要有依据——不能为了填充而填充
1. 均值填充
最简单的方法,用该字段的均值或中位数填充缺失值。适用于数值型字段,且缺失率不高(一般低于 20%)。
# 均值填充示例
import pandas as pd
df = pd.DataFrame({
'交易金额': [100, 200, None, 150, 300, None, 250],
'交易时间': ['2024-01-01', '2024-01-02', '2024-01-03', '2024-01-04',
'2024-01-05', '2024-01-06', '2024-01-07']
})
# 用均值填充缺失的交易金额
mean_amount = df['交易金额'].mean()
df['交易金额_填充'] = df['交易金额'].fillna(mean_amount)
print(df)
我的习惯:均值填充前,我会先看看数据分布。如果分布偏态严重,用中位数比均值更靠谱。比如用户收入数据,用中位数填充能避免被高收入人群拉高。
2. 业务规则推导
这是我最喜欢的方法——利用业务逻辑来推算缺失值。举个例子:
- 如果交易金额缺失,但手续费和费率已知,可以反推交易金额
- 如果用户年龄缺失,但身份证号已知,可以提取出生年份
- 如果交易对手信息缺失,但交易流水号关联了其他表,可以关联补全
# 业务规则推导示例
def derive_missing_amount(row):
"""根据手续费和费率推导缺失的交易金额"""
if pd.isna(row['交易金额']) and not pd.isna(row['手续费']) and not pd.isna(row['费率']):
return row['手续费'] / row['费率']
return row['交易金额']
df['交易金额_推导'] = df.apply(derive_missing_amount, axis=1)
核心原则:业务规则推导的优先级永远高于统计填充。因为业务规则反映了真实的交易逻辑,而统计填充只是数学上的近似。
三、重复数据去重——别让“复制粘贴”污染你的数据
重复数据是交易审计中的“隐形杀手”。我见过一个案例,因为系统重试机制导致同一笔交易被记录了 3 次,结果审计报告里的交易总额虚增了 200%。
去重策略的核心是:确定唯一标识。常见的做法有:
| 去重策略 | 适用场景 | 注意事项 |
|---|---|---|
| 单字段去重 | 有唯一交易流水号 | 确保流水号本身不重复 |
| 多字段联合去重 | 没有唯一ID,但多个字段组合可唯一标识 | 字段选择要谨慎,避免过度去重 |
| 时间窗口去重 | 短时间内重复提交的交易 | 需要定义合理的时间窗口 |
# 多字段联合去重示例
df_clean = df.drop_duplicates(subset=['交易时间', '交易金额', '付款方账号', '收款方账号'])
# 时间窗口去重(5分钟内相同金额的交易视为重复)
df['交易时间'] = pd.to_datetime(df['交易时间'])
df_sorted = df.sort_values('交易时间')
df_sorted['时间差'] = df_sorted.groupby(['交易金额', '付款方账号'])['交易时间'].diff()
df_unique = df_sorted[df_sorted['时间差'].isna() | (df_sorted['时间差'] > pd.Timedelta(minutes=5))]
避坑指南:我曾经在去重时,只用了交易金额和交易时间两个字段,结果把两笔金额相同但交易对手不同的正常交易也删掉了。记住:去重字段要能唯一标识一笔交易,宁可少去重,不可过度去重。
四、实战建议——我的数据清洗工作流
说了这么多,最后分享一个我常用的数据清洗工作流:
- 先备份原始数据——这是铁律,任何时候都不要在原数据上直接操作
- 先做去重——重复数据会影响后续的统计和异常检测
- 再做缺失值处理——先尝试业务规则推导,再考虑统计填充
- 最后做异常值检测——异常值检测要结合业务上下文,不能只看统计指标
- 记录每一步的清洗日志——包括删除了多少条、填充了多少条、修改了哪些字段
最后说一句:数据清洗没有标准答案,每个项目都有自己的“脾气”。但只要你掌握了 3σ、IQR、均值填充、业务规则推导、去重策略这些基本功,再结合对业务的理解,就能把数据清洗做得八九不离十。
公众号:蓝海资料掘金营,微信deep3321