数据预处理实战:缺失值处理、异常值检测与标准化
数据预处理,说白了就是给数据「洗澡」。我见过太多同学拿到数据直接开跑模型,结果效果一塌糊涂。其实80%的问题都出在数据质量上。今天咱们就把这块硬骨头啃下来。
一、缺失值处理:别让空值毁了你的模型
缺失值是最常见的问题。我刚开始做项目时,遇到缺失值就删,结果样本量从10万掉到2万,模型直接废了。后来才明白,缺失值处理要分情况讨论。
1. 删除法:简单粗暴,但有代价
什么时候用删除?我个人习惯是:
- 缺失比例超过50%的列,直接删
- 缺失比例小于5%的行,可以删
- 关键特征缺失,千万别删
import pandas as pd
import numpy as np
# 删除缺失比例超过50%的列
df = df.dropna(thresh=len(df)*0.5, axis=1)
# 删除缺失值所在的行
df_clean = df.dropna()
⚠️ 避坑指南:我曾经在金融风控项目里直接删了缺失行,结果发现坏账率高的样本全被删了。模型训练出来,上线就出问题。所以删除前,一定要看看缺失模式是不是随机的。
2. 填充法:均值、中位数、众数
填充比删除更常用。但怎么选?我一般这么判断:
| 数据类型 | 推荐填充方式 | 原因 |
|---|---|---|
| 正态分布数据 | 均值填充 | 均值能代表整体水平 |
| 偏态分布数据 | 中位数填充 | 不受极端值影响 |
| 分类数据 | 众数填充 | 取出现最多的类别 |
# 均值填充
df['age'].fillna(df['age'].mean(), inplace=True)
# 中位数填充
df['income'].fillna(df['income'].median(), inplace=True)
# 众数填充
df['gender'].fillna(df['gender'].mode()[0], inplace=True)
💡 我的经验:如果数据量够大,我会用KNN或回归模型来预测缺失值。虽然计算量大,但效果比简单填充好很多。不过面试时,先讲清楚均值/中位数/众数,再提高级方法,显得更扎实。
二、异常值检测:揪出数据里的「害群之马」
异常值就像团队里那个不按套路出牌的人。有时候他是天才,有时候他是麻烦。怎么判断?我常用两种方法。
1. 3σ原则:适合正态分布
这个原理很简单:数据落在均值±3个标准差之外的概率只有0.3%。所以超出这个范围的,基本可以认定为异常。
def detect_outliers_3sigma(data):
mean = data.mean()
std = data.std()
lower = mean - 3 * std
upper = mean + 3 * std
return data[(data < lower) | (data > upper)]
# 使用
outliers = detect_outliers_3sigma(df['price'])
print(f'发现 {len(outliers)} 个异常值')
注意:3σ原则对异常值本身很敏感。如果数据里已经有几个极端值,均值和标准差会被拉偏,导致误判。我一般先做一次初步筛选,再用3σ。
2. IQR方法:更稳健的选择
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[(data < lower) | (data > upper)]
# 使用
outliers_iqr = detect_outliers_iqr(df['price'])
⚠️ 避坑指南:我曾经在电商数据里用IQR检测价格异常,结果把「双十一」的促销价全标成了异常。后来我加了时间维度,才区分出真正的异常和业务波动。所以,异常值检测一定要结合业务背景。
三、数据标准化与归一化:让特征站在同一起跑线
为什么需要标准化?你想想看,年龄范围是0-100,收入范围是0-100万,这两个特征直接丢进模型,收入会主导结果。说白了,就是量纲不同导致的偏差。
1. 标准化(Z-score):适合数据服从正态分布
公式很简单:减去均值,除以标准差。处理后数据均值为0,标准差为1。
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
df_scaled = scaler.fit_transform(df[['age', 'income']])
2. 归一化(Min-Max):适合有边界的数据
把数据压缩到[0,1]区间。公式: (x - min) / (max - min)
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
df_normalized = scaler.fit_transform(df[['age', 'income']])
| 方法 | 适用场景 | 特点 |
|---|---|---|
| 标准化 | 数据近似正态分布 | 不改变分布形状,保留异常值信息 |
| 归一化 | 数据有明确上下界 | 对异常值敏感,会压缩正常数据 |
💡 我的经验:在深度学习里,我习惯用标准化。因为归一化会把数据压到0-1,如果遇到极端值,大部分数据会被挤到很小的区间,模型学起来很吃力。标准化就没这个问题。
四、实战中的坑与建议
最后分享几个我踩过的坑:
- 先拆分再处理:一定要先划分训练集和测试集,再分别做标准化。否则测试集信息泄露,模型评估会虚高。
- 保存参数:标准化用的均值和标准差,归一化用的min和max,都要保存下来。上线预测时要用同样的参数。
- 不要盲目处理:有些算法(如决策树、随机森林)对量纲不敏感,不需要标准化。先了解模型特性,再决定要不要做。
数据预处理没有银弹。每个项目都要根据数据特点、业务场景、模型需求来灵活选择。嗯,多练几次,你就能找到感觉了。