数据预处理(上):缺失值处理、异常值检测与标准化
大家好,我是老蓝。今天咱们聊聊数据预处理。
说实话,很多初学者一上来就急着调模型、跑算法,结果模型效果差得离谱。我见过太多这样的案例了。其实啊,数据预处理才是整个机器学习流程里最耗时间、也最见功力的环节。我个人习惯把预处理比作「做饭前的洗菜切菜」——菜没洗干净,再好的厨子也白搭。
这一章我们聚焦三个核心问题:缺失值、异常值和数据标准化。这三个问题处理不好,模型基本就废了。咱们一个一个来。
核心观点:数据预处理不是可选项,是必选项。你花在预处理上的每一分钟,都会在模型效果上得到回报。
一、缺失值处理:别让空值毁了你的模型
先问个问题:你拿到一份数据,发现有20%的样本某个字段是空的,你会怎么办?
我刚开始做项目时,第一反应就是「删掉」。后来发现,很多时候删掉反而会丢失重要信息。比如在金融风控场景中,某些字段缺失本身可能就是一种信号——用户故意不填,说明有问题。
1.1 删除缺失值
什么时候可以删?我个人总结了两条经验:
- 缺失比例极低(比如小于5%),且样本量足够大
- 缺失完全是随机的,跟目标变量没关系
代码很简单:
import pandas as pd
# 删除含有缺失值的行
df.dropna(inplace=True)
# 删除某一列缺失比例超过50%的列
df.dropna(thresh=len(df) * 0.5, axis=1, inplace=True)
注意:千万别一上来就 dropna()。我曾经在一个客户流失预测项目里,直接删掉了所有含缺失值的行,结果模型准确率从85%掉到了62%。后来才发现,缺失值集中在「最近一次投诉时间」这个字段上——没有投诉记录的用户,恰恰是流失风险最高的群体。
1.2 填充缺失值
大多数情况下,我们选择填充而不是删除。常用的方法有:
| 方法 | 适用场景 | 代码示例 |
|---|---|---|
| 均值填充 | 数值型、数据分布较对称 | df['age'].fillna(df['age'].mean()) |
| 中位数填充 | 数值型、有异常值 | df['income'].fillna(df['income'].median()) |
| 众数填充 | 类别型 | df['gender'].fillna(df['gender'].mode()[0]) |
| 前向/后向填充 | 时间序列数据 | df.fillna(method='ffill') |
| KNN填充 | 特征间有相关性 | from sklearn.impute import KNNImputer |
嗯,这里要注意:均值填充对异常值非常敏感。如果你的数据里有极端值,均值会被拉偏,这时候用中位数更靠谱。
我的小技巧:对于时间序列数据,我习惯先用前向填充,再用后向填充兜底。比如传感器数据,前一秒的值往往比全局均值更有参考价值。
二、异常值检测:揪出数据里的「害群之马」
异常值是什么?说白了就是那些「画风不对」的数据点。比如用户年龄填了200岁,或者月收入填了-5000块。
但有些异常值没那么明显。比如在电商数据里,某个用户一天下单1000次——这可能是刷单,也可能是数据采集错误。怎么判断?
2.1 3σ原则
这个方法基于正态分布。如果数据服从正态分布,那么99.7%的数据会落在均值±3个标准差范围内。超出这个范围的,就视为异常。
import numpy as np
def detect_outliers_3sigma(data):
mean = np.mean(data)
std = np.std(data)
lower = mean - 3 * std
upper = mean + 3 * std
return (data < lower) | (data > upper)
注意:3σ原则对非正态分布的数据效果很差。我记得有一次处理收入数据,收入分布是严重右偏的,用3σ原则把很多高收入用户都标记成了异常——这显然不合理。
2.2 IQR箱线图法
这个方法不依赖正态分布假设,更稳健。它基于四分位数:
- 计算Q1(25%分位数)和Q3(75%分位数)
- IQR = Q3 - Q1
- 异常值定义为:小于 Q1 - 1.5×IQR 或 大于 Q3 + 1.5×IQR
def detect_outliers_iqr(data):
Q1 = data.quantile(0.25)
Q3 = data.quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR
return (data < lower) | (data > upper)
我个人更偏爱IQR方法。为什么?因为它对极端值不那么敏感。你想想看,如果数据本身就有几个极端值,用3σ原则算出来的标准差会被拉大,反而检测不出真正的异常。而IQR只看中间50%的数据,稳得很。
2.3 处理异常值的策略
检测到异常值后怎么办?通常有几种选择:
- 删除:如果异常值明显是错误数据,直接删
- 截尾:把异常值替换为边界值(比如用Q3+1.5×IQR替换)
- 当作缺失值处理:用填充方法处理
- 保留:如果异常值本身有业务含义(比如欺诈交易),反而要重点分析
避坑指南:我曾经在房价预测项目里,把那些「天价豪宅」当作异常值删掉了。结果模型在高端房产上的预测一塌糊涂。后来才意识到,那些「异常值」恰恰是我们要预测的目标之一。所以,处理异常值之前,先问问自己:这个异常值是真的错误,还是真实存在的极端情况?
三、数据标准化与归一化:让特征站在同一起跑线上
为什么需要标准化?举个例子:年龄范围是0-100,收入范围是0-100000。如果不做处理,模型会天然认为「收入」这个特征更重要,因为它的数值更大。但实际上,年龄和收入对预测结果的影响权重应该由模型自己学习,而不是被数值范围绑架。
3.1 Z-Score标准化
这是最常用的方法。公式很简单:
z = (x - μ) / σ
其中μ是均值,σ是标准差。标准化后的数据均值为0,标准差为1。
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
df_scaled = scaler.fit_transform(df[['age', 'income']])
什么时候用Z-Score?当你的数据分布接近正态分布时,效果最好。如果数据有大量异常值,Z-Score会被带偏。
3.2 Min-Max归一化
把数据缩放到[0,1]区间:
x' = (x - min) / (max - min)
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
df_normalized = scaler.fit_transform(df[['age', 'income']])
Min-Max对异常值非常敏感。如果数据里有一个极端大的值,其他所有值都会被压缩到很小的区间里。我建议在数据没有明显异常值的情况下使用。
| 方法 | 输出范围 | 对异常值敏感度 | 适用算法 |
|---|---|---|---|
| Z-Score标准化 | 无固定范围(通常-3到3) | 中等 | SVM、线性回归、PCA |
| Min-Max归一化 | [0, 1] | 高 | 神经网络、KNN、K-Means |
重要提醒:标准化和归一化的参数(均值、标准差、最小值、最大值)一定要只从训练集计算,然后用同样的参数去转换测试集。千万别把测试集的数据混进来一起算——这叫「数据泄露」,会导致模型评估结果虚高。
四、实战小贴士:预处理流程建议
说了这么多,我给大家总结一个我常用的预处理流程:
- 先看数据概况:用 df.info() 和 df.describe() 快速了解缺失情况和分布
- 处理缺失值:先判断缺失原因,再选择删除或填充
- 检测异常值:用IQR方法初步筛查,结合业务判断
- 标准化/归一化:根据算法需求选择合适的方法
- 验证效果:可视化检查处理前后的分布变化
记住,没有放之四海皆准的预处理方案。每个数据集都有自己的脾气,多试几种方法,找到最适合你业务场景的那一套。
最后说一句:数据预处理做得好,模型训练事半功倍。我见过太多人花80%的时间调参,却只花20%的时间做预处理——方向反了。把预处理做扎实,你会发现模型效果自然就上去了。