4. 数据预处理与清洗:缺失值处理、异常值过滤、时间戳对齐、数据标准化
好,咱们进入正题。数据预处理这块,说白了就是给原始数据「洗澡」。我见过太多团队,模型选得再好,特征工程做得再花哨,结果数据一塌糊涂,最后全白干。嗯,咱们一步步来。
4.1 缺失值处理:别让「空」坑了你
交易数据里出现缺失值,太常见了。比如网络抖动、字段没采集到、或者上游系统抽风。我个人习惯,拿到数据第一件事就是跑个 df.isnull().sum() 看看全局。
处理策略分三种:
- 直接删除:缺失比例小于 5%,且是随机缺失。我一般直接 dropna()。但注意,别删太多,否则样本量不够。
- 填充:连续型字段用均值/中位数,离散型用众数。我在项目中遇到过,交易金额缺失用中位数填充比均值更稳,因为金额分布往往偏态严重。
- 模型预测填充:缺失值比较重要,比如用户信用分缺失,我会用其他字段训练一个简单模型去预测。但别过度,否则引入偏差。
避坑指南:我曾经在风控系统里,直接用 0 填充了「交易对手风险等级」这个字段,结果模型学出来一堆误报。后来发现,缺失值本身也是一种信息——它可能代表新用户或数据不全,单独标记出来反而更好。
# 示例:缺失值处理
import pandas as pd
import numpy as np
# 查看缺失
print(df.isnull().sum())
# 中位数填充金额字段
df['amount'].fillna(df['amount'].median(), inplace=True)
# 单独标记缺失
df['risk_level_missing'] = df['risk_level'].isnull().astype(int)
df['risk_level'].fillna('unknown', inplace=True)
4.2 异常值过滤:别让「野点」带偏模型
异常值在交易检测里,既是敌人也是朋友。为什么这么说?因为真正的欺诈交易本身就是异常值。但如果你把正常波动也当成异常,模型就学傻了。
我常用的方法:
- IQR 四分位法:适合单变量。Q1 - 1.5*IQR 到 Q3 + 1.5*IQR 之外算异常。简单粗暴,但有效。
- Z-Score 法:假设正态分布,3 个标准差之外算异常。注意,交易数据往往不是正态的,用之前先 log 变换一下。
- 孤立森林:高维场景下,我更喜欢用这个。它不假设分布,速度快。
注意:别一刀切删除所有异常值。你想想看,如果删掉了真正的欺诈样本,模型还学什么?我建议先标记,再结合业务判断。比如单笔交易金额超过 100 万,先别删,看看是不是大客户正常交易。
# 示例:IQR 法过滤
Q1 = df['amount'].quantile(0.25)
Q3 = df['amount'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
# 标记异常,不直接删除
df['amount_outlier'] = ((df['amount'] < lower_bound) | (df['amount'] > upper_bound)).astype(int)
4.3 时间戳对齐:让数据「同步」说话
交易数据来自不同系统,时间戳格式五花八门。有的用 Unix 时间戳,有的用字符串,有的甚至带时区。如果不对齐,你算出来的「3 分钟内连续交易」可能全是错的。
我的标准流程:
- 统一转成 datetime 类型:用
pd.to_datetime(),指定 format 参数,别让 pandas 自己猜,否则性能堪忧。 - 统一时区:全部转成 UTC。我吃过亏,有一次系统用的东八区,另一个用的 UTC,结果时间差 8 小时,特征全乱套。
- 重采样对齐:如果需要按分钟、小时聚合,用
resample()或groupby() + pd.Grouper()。
小技巧:时间戳对齐后,我习惯生成一个「时间差」特征——比如当前交易与上一笔交易的时间间隔。这个特征在检测高频交易、撞库攻击时特别有用。
# 示例:时间戳对齐
df['trade_time'] = pd.to_datetime(df['trade_time'], format='%Y-%m-%d %H:%M:%S')
df['trade_time'] = df['trade_time'].dt.tz_localize('Asia/Shanghai').dt.tz_convert('UTC')
# 按用户分组,计算交易间隔
df = df.sort_values(['user_id', 'trade_time'])
df['time_diff'] = df.groupby('user_id')['trade_time'].diff().dt.total_seconds()
4.4 数据标准化:让特征「公平竞争」
交易数据里,金额可能是几百万,次数可能是几十次,这两个量级差太多。如果不标准化,模型会天然偏向金额大的特征。说白了,就是「大数欺负小数」。
我常用的方法:
| 方法 | 适用场景 | 公式 |
|---|---|---|
| Z-Score 标准化 | 数据近似正态分布,或使用 SVM、线性回归等模型 | (x - mean) / std |
| Min-Max 归一化 | 数据有明确边界,或使用神经网络 | (x - min) / (max - min) |
| Robust 标准化 | 数据有较多异常值 | (x - median) / IQR |
我个人偏好 Robust 标准化。为什么?因为交易数据里异常值太多,Z-Score 会被异常值拉偏,Min-Max 会被极端值压缩。Robust 用中位数和四分位距,稳得很。
避坑指南:我曾经在训练集上做了标准化,但忘了保存标准化参数。上线时直接用训练集的 mean 和 std 去变换线上数据,结果分布变了,模型直接崩了。记住:标准化参数必须持久化,用 joblib 或 pickle 存下来。
# 示例:Robust 标准化
from sklearn.preprocessing import RobustScaler
import joblib
scaler = RobustScaler()
df[['amount', 'trade_count']] = scaler.fit_transform(df[['amount', 'trade_count']])
# 保存参数
joblib.dump(scaler, 'scaler.pkl')
知识体系总览
下面这张图,是我梳理的本章核心逻辑。数据预处理不是孤立步骤,它贯穿整个特征工程。你想想看,缺失值没处理好,异常值没过滤,时间戳没对齐,标准化没做——后面模型再牛也白搭。
嗯,数据预处理这块,说难不难,说简单也不简单。关键是要有「洁癖」——对数据质量零容忍。我见过太多项目,模型选得再好,数据一塌糊涂,最后全白干。记住一句话:垃圾进,垃圾出。
最后提醒一句:预处理流程一定要做成 pipeline,每次跑数据都走同一套流程。别今天用中位数填充,明天用均值填充,后天又忘了标准化——那样你永远不知道模型效果变差是因为数据变了,还是模型本身的问题。