数据预处理基础:缺失值处理、异常值检测、数据标准化与归一化
大家好,我是老周。今天咱们聊数据预处理——说白了就是让原始数据变得“干净”且“规整”。你想想看,模型吃进去的是垃圾,吐出来的肯定是垃圾。我在项目里吃过不少亏,所以这部分经验我特别想分享出来。
1. 缺失值处理
缺失值最让人头大。不是所有缺失都一样,我习惯先分清楚机制:
| 类型 | 含义 | 通俗理解 |
|---|---|---|
| MCAR | 完全随机缺失 | 缺失和任何变量都没关系,就像随手扔掉了几个值 |
| MAR | 随机缺失(依赖于其他变量) | 比如收入缺失,但可能和职业、学历有关 |
| MNAR | 非随机缺失 | 缺失本身就隐含信息,比如高收入者故意不填 |
一般做法分两种:删和补。但直接删数据要慎重。
- 删除法:dropna() 简单粗暴,但样本少时别乱用。我见过有人直接删掉30%的行,结果模型直接崩了。
- 填充法:均值/中位数/众数填充最基础,但会改变分布。前向/后向填充适合时序数据。
- 插值法:pandas的interpolate() 对连续性数据很友好。
- 模型预测:KNNImputer、IterativeImputer 效果不错,但计算量大。我曾经在金融风控项目里用KNNImputer修复缺失字段,准确率提升了3个点。
import pandas as pd
from sklearn.impute import KNNImputer
df = pd.DataFrame({'A': [1, 2, None, 4], 'B': [5, None, 7, 8]})
imputer = KNNImputer(n_neighbors=2)
df_imputed = pd.DataFrame(imputer.fit_transform(df), columns=df.columns)
print(df_imputed)
我的习惯:先看缺失比例。大于50%的列直接扔掉,小于5%的用中位数填充,中间的尝试模型插值。记得填充前一定要分析缺失原因。
2. 异常值检测
异常值像老鼠屎,坏一锅汤。特别是线性模型和KNN这类对异常敏感的算法。
常用方法:
| 方法 | 适用场景 | 缺点 |
|---|---|---|
| Z-score(3σ原则) | 近似正态分布 | 对非正态分布不准 |
| IQR(箱线图) | 任意分布,快速 | 阈值固定(1.5倍IQR) |
| Isolation Forest | 高维数据,非线性 | 需要调参 |
| DBSCAN | 密度聚类异常检测 | 对密度变化敏感 |
我个人习惯先用IQR快速扫一遍。有一次线上跑模型,发现预测值忽高忽低,查了半天原来是某个传感器数据串了,IQR直接把它标出来了。之后再拿Isolation Forest处理高维数据。
import numpy as np
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
return (data < lower_bound) | (data > upper_bound)
避坑指南:我曾经在电信用户流失预测中误把高价值用户当作异常值删除,后来发现那些高额消费客户恰恰是模型要识别的目标。所以先想想业务含义再动手。
3. 数据标准化与归一化
标准化和归一化是让不同量纲的特征“站到同一起跑线”。
| 方法 | 公式 | 输出范围 | 适用场景 |
|---|---|---|---|
| Z-score标准化 (StandardScaler) | (x-μ)/σ | 均值0,标准差1 | 数据近似正态分布,有异常值也建议Robust |
| MinMax归一化 | (x-min)/(max-min) | [0, 1] | 有明确边界(如像素值0-255) |
| RobustScaler | (x-中位数)/IQR | 更抗异常值 | 数据包含异常值 |
记住一个铁律:先分割训练集和测试集,再对训练集fit transform,然后用训练集的参数transform测试集。我见过有人直接对整个数据集做标准化,结果模型在测试集上过拟合,因为包含了未来信息。
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
4. 处理顺序与协同
这三个步骤有先后逻辑。我建议:先处理缺失值,再检测异常值,最后做标准化或归一化。因为异常值检测时如果数据有缺失,很多方法会报错;而标准化前要保证数据完整。
核心原则:数据预处理不是流水线式的死步骤,要根据业务和数据特点灵活组合。比如树模型对标准化不敏感,但缺失值和异常值依然要处理。
5. 实战避坑
- 注意数据泄露:所有预处理参数(均值、标准差、异常阈值)都必须从训练集计算,不能全量计算。
- 缺失值与异常值的顺序:我建议先处理缺失,再检测异常。因为缺失值可能本身就是异常。
- 不要盲目删除异常值:先可视化分析,确认是否真的是噪声。有时候异常值恰恰是“金矿”。
- 标准化后特征含义改变:比如身高标准化后变成了“身高偏离均值几个标准差”,对可解释性要求高的场景要小心。
我曾经踩过的坑:在某次销量预测项目中,我直接对时间序列做了MinMax归一化,忽略了未来最大值未知的问题。结果测试集的最大值远大于训练集,归一化后数值全部大于1,模型彻底崩溃。正确做法是用滚动窗口或业务理论最大值。