4. 数据预处理:缺失值处理、异常值检测与处理、数据标准化与归一化
各位同学,咱们直接进入正题。数据预处理这步,说白了就是给模型「喂饭」之前,先把食材洗干净、把烂叶子摘掉。我在风控项目里见过太多因为数据没处理好,模型上线就崩的案例。嗯,今天咱们就把这块掰开揉碎了讲清楚。
4.1 缺失值处理
缺失值这东西,在风控数据里太常见了。用户填资料漏填、系统采集失败、数据传输丢包……原因五花八门。但处理不好,模型就会学歪。
4.1.1 缺失值类型
我个人习惯先分三类:
- 完全随机缺失(MCAR):缺失跟数据本身没关系,比如系统抽风丢了几条。这种最简单,直接删掉或填充都行。
- 随机缺失(MAR):缺失跟其他变量有关。比如收入高的用户更不愿意填收入,但跟收入本身无关。这种需要小心处理。
- 非随机缺失(MNAR):缺失跟变量本身有关。比如收入特别高的人故意不填。这种最难搞,我建议先做业务分析再决定。
重要提醒:千万别一上来就全删掉。我在项目中遇到过,某次风控模型训练时删了30%的缺失样本,结果模型在线上对那部分用户完全失效。后来一查,那些缺失的用户恰恰是高风险群体。
4.1.2 处理方法
常用的方法就这几种,我按推荐顺序排:
- 删除法:缺失比例小于5%且随机,直接删行。但别删太多。
- 均值/中位数填充:连续变量用中位数(抗异常值),分类变量用众数。
- 前向/后向填充:时间序列数据常用。比如用户连续几天没交易,用前一天的值补。
- 模型预测填充:用其他变量做回归或KNN预测缺失值。精度高,但计算量大。
- 标记法:单独加一列「是否缺失」,让模型自己学。我比较喜欢这招。
# Python示例:缺失值处理
import pandas as pd
import numpy as np
# 读取数据
df = pd.read_csv('risk_data.csv')
# 查看缺失比例
missing_ratio = df.isnull().mean()
print(missing_ratio[missing_ratio > 0])
# 中位数填充连续变量
df['income'].fillna(df['income'].median(), inplace=True)
# 众数填充分类变量
df['education'].fillna(df['education'].mode()[0], inplace=True)
# 标记缺失(推荐)
df['income_missing'] = df['income'].isnull().astype(int)
我的小技巧:对于风控场景,我建议对缺失值做「分箱处理」。比如收入缺失的单独归为一类,模型往往能学到「缺失本身就是风险信号」这个规律。
4.2 异常值检测与处理
异常值,就是那些「离谱」的数据点。比如用户年龄填了200岁,或者月收入填了1个亿。你想想看,这种数据丢进模型,模型不学歪才怪。
4.2.1 检测方法
我常用的方法有这几种:
| 方法 | 适用场景 | 优缺点 |
|---|---|---|
| 3σ原则 | 正态分布数据 | 简单快速,但对非正态分布效果差 |
| 箱线图(IQR) | 任意分布 | 鲁棒性强,不受极端值影响 |
| Z-Score | 标准化后数据 | 需要数据近似正态 |
| DBSCAN聚类 | 多维数据 | 能发现局部异常,但参数敏感 |
| 孤立森林 | 高维数据 | 效率高,适合大数据 |
我个人最常用的是箱线图法。为什么?因为它不假设数据分布,而且对极端值不敏感。我曾经在一个信贷项目中,用3σ原则检测异常值,结果把一批高收入但正常还款的用户给误杀了。后来换成IQR法,问题就解决了。
4.2.2 处理方法
检测到异常值后,怎么处理?我的建议是:
- 业务确认:先问业务人员,这个值是不是合理的。比如用户年龄120岁,大概率是录入错误。
- 截尾处理:把超过99%分位数的值替换成99%分位数。这叫「Winsorize」,我常用。
- 分箱处理:把连续变量离散化,异常值单独成一箱。
- 模型容忍:树模型对异常值不敏感,可以不用处理。但线性模型必须处理。
# Python示例:异常值检测与处理
from scipy import stats
import numpy as np
# 箱线图法检测
Q1 = df['income'].quantile(0.25)
Q3 = df['income'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
# 标记异常值
df['income_outlier'] = ((df['income'] < lower_bound) | (df['income'] > upper_bound)).astype(int)
# 截尾处理(Winsorize)
df['income_winsorized'] = df['income'].clip(lower=lower_bound, upper=upper_bound)
避坑指南:我曾经在一个反欺诈项目中,把所有异常值都删掉了。结果模型在线上对「高消费但正常」的用户频繁误报。后来才发现,那些「异常值」其实是正常的高端用户。所以,处理异常值前一定要做业务分析,别一刀切。
4.3 数据标准化与归一化
标准化和归一化,说白了就是把不同量纲的数据拉到同一个尺度上。你想想看,收入从0到100万,年龄从0到100,这两个变量直接丢进模型,收入会主导模型训练。这不合理。
4.3.1 标准化(Z-Score)
公式很简单:z = (x - μ) / σ。处理后数据均值为0,标准差为1。适合数据近似正态分布的场景。
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
df[['income', 'age']] = scaler.fit_transform(df[['income', 'age']])
4.3.2 归一化(Min-Max)
公式:x' = (x - min) / (max - min)。处理后数据在[0,1]区间。适合有边界的数据,比如评分卡分数。
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
df[['income', 'age']] = scaler.fit_transform(df[['income', 'age']])
4.3.3 怎么选?
我个人的经验是:
- 树模型:不需要标准化,因为树模型是基于分裂点的,不受量纲影响。
- 线性模型、SVM、KNN、神经网络:必须标准化或归一化。
- 有异常值:用标准化(Z-Score)更鲁棒,因为Min-Max会被极端值拉偏。
- 数据有明确边界:比如评分0-100,用Min-Max更直观。
重要提醒:标准化和归一化的参数(均值、标准差、最小值、最大值)一定要用训练集计算,然后应用到验证集和测试集。千万别用全量数据计算,否则会造成数据泄露。我在项目中见过有人犯这个错,模型评估结果虚高,上线就崩。
4.4 知识体系总览
下面这张图,是我自己画的,把数据预处理的整个流程串起来了。你仔细看看,心里就有谱了。
这张图把整个流程串起来了。从原始数据开始,经过缺失值处理、异常值检测、标准化归一化,最后得到干净的数据。每一步都有多种方法,具体选哪个,看你的数据特点和业务场景。
我的建议:数据预处理没有银弹。我一般会先做探索性数据分析(EDA),看看数据分布、缺失模式、异常情况,然后再选方法。别一上来就套模板,数据会「骗人」的。
公众号:蓝海资料掘金营,微信deep3321