4. 数据预处理:缺失值处理、异常值检测与处理、数据标准化与归一化

各位同学,咱们直接进入正题。数据预处理这步,说白了就是给模型「喂饭」之前,先把食材洗干净、把烂叶子摘掉。我在风控项目里见过太多因为数据没处理好,模型上线就崩的案例。嗯,今天咱们就把这块掰开揉碎了讲清楚。

4.1 缺失值处理

缺失值这东西,在风控数据里太常见了。用户填资料漏填、系统采集失败、数据传输丢包……原因五花八门。但处理不好,模型就会学歪。

4.1.1 缺失值类型

我个人习惯先分三类:

  • 完全随机缺失(MCAR):缺失跟数据本身没关系,比如系统抽风丢了几条。这种最简单,直接删掉或填充都行。
  • 随机缺失(MAR):缺失跟其他变量有关。比如收入高的用户更不愿意填收入,但跟收入本身无关。这种需要小心处理。
  • 非随机缺失(MNAR):缺失跟变量本身有关。比如收入特别高的人故意不填。这种最难搞,我建议先做业务分析再决定。

重要提醒:千万别一上来就全删掉。我在项目中遇到过,某次风控模型训练时删了30%的缺失样本,结果模型在线上对那部分用户完全失效。后来一查,那些缺失的用户恰恰是高风险群体。

4.1.2 处理方法

常用的方法就这几种,我按推荐顺序排:

  1. 删除法:缺失比例小于5%且随机,直接删行。但别删太多。
  2. 均值/中位数填充:连续变量用中位数(抗异常值),分类变量用众数。
  3. 前向/后向填充:时间序列数据常用。比如用户连续几天没交易,用前一天的值补。
  4. 模型预测填充:用其他变量做回归或KNN预测缺失值。精度高,但计算量大。
  5. 标记法:单独加一列「是否缺失」,让模型自己学。我比较喜欢这招。
# 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 知识体系总览

下面这张图,是我自己画的,把数据预处理的整个流程串起来了。你仔细看看,心里就有谱了。

数据预处理知识体系 原始数据 缺失值处理 异常值检测与处理 标准化与归一化 处理方法 • 删除法(缺失<5%) • 均值/中位数/众数填充 • 前向/后向填充 • 模型预测填充 检测方法 • 3σ原则(正态分布) • 箱线图IQR(鲁棒) • Z-Score • DBSCAN/孤立森林 方法对比 • Z-Score标准化 • Min-Max归一化 • 树模型不需要 • 线性模型必须做 清洗后数据

这张图把整个流程串起来了。从原始数据开始,经过缺失值处理、异常值检测、标准化归一化,最后得到干净的数据。每一步都有多种方法,具体选哪个,看你的数据特点和业务场景。

我的建议:数据预处理没有银弹。我一般会先做探索性数据分析(EDA),看看数据分布、缺失模式、异常情况,然后再选方法。别一上来就套模板,数据会「骗人」的。


公众号:蓝海资料掘金营,微信deep3321