数据清洗与预处理:缺失值、异常值与标准化
数据清洗与预处理完全指南:缺失值、异常值与标准化
数据清洗与预处理是任何数据分析、机器学习项目中最耗时但最重要的环节。原始数据往往包含噪声、不一致和缺失,直接建模会导致“垃圾进,垃圾出”的结果。本教程将系统讲解三大核心任务:缺失值处理、异常值检测与处理以及数据标准化/归一化,帮助你建立扎实的数据预处理技能。
1. 理解数据清洗的必要性
在接触具体技术前,先明确数据质量问题的主要来源:
- 人为错误:数据录入错误、测量误差。
- 系统缺陷:传感器故障、数据管道中断。
- 数据整合:多源数据合并时的格式不统一、编码冲突。
- 隐私处理:脱敏操作可能引入缺失。
高质量的数据预处理可以显著提升模型表现,甚至比选择复杂模型更有效。
2. 缺失值处理
缺失值表现为 NaN、None、空字符串或占位符(如 -999)。处理方法取决于缺失机制和业务背景。
2.1 识别缺失值
使用 Python 的 pandas 快速探查:
import pandas as pd
df = pd.read_csv('data.csv')
# 查看每列缺失数量
print(df.isnull().sum())
# 可视化缺失分布
import missingno as msno
msno.matrix(df)
- 完全随机缺失(MCAR):缺失概率与任何变量无关。
- 随机缺失(MAR):缺失概率与其他可观测变量相关(例如年轻人更不愿报告收入)。
- 非随机缺失(MNAR):缺失与自身值相关(例如高收入者故意漏填)。
MCAR 可直接删除,MAR/MNAR 需谨慎填补。
2.2 删除缺失值
适用场景:缺失比例极低(<5%)、完全随机缺失且样本充足。
# 删除任何包含缺失值的行
df_drop = df.dropna()
# 仅当指定列缺失时才删除行
df_drop_col = df.dropna(subset=['age', 'income'])
# 删除缺失超过阈值(如80%)的列
df_drop_cols = df.dropna(thresh=0.8 * len(df), axis=1)
注意:删除会导致信息损失,尤其在小数据集上可能产生偏差。
2.3 填补缺失值(Imputation)
2.3.1 简单统计填补
用集中趋势度量填充,适合数值型变量:
# 均值填补
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)
2.3.2 前向/后向填充
适用于时间序列或有序数据:
df['sensor'].fillna(method='ffill', inplace=True) # 前向填充
df['sensor'].fillna(method='bfill', inplace=True) # 后向填充
2.3.3 模型预测填补
利用其他特征预测缺失值,更准确但计算成本高:
from sklearn.impute import KNNImputer
imputer = KNNImputer(n_neighbors=5)
df_filled = pd.DataFrame(imputer.fit_transform(df), columns=df.columns)
或使用 IterativeImputer(基于回归)、SimpleImputer 等。务必在训练集上拟合填补器,再应用于测试集。
2.3.4 标记缺失
有时“缺失”本身是信息,可额外创建二值指示列:
df['income_missing'] = df['income'].isnull().astype(int)
df['income'].fillna(0, inplace=True) # 任意常数填补
2.4 缺失值处理流程总结
- 分析缺失模式与比例。
- 若需保留数据,选择填补策略(简单统计、回归、KNN)。
- 对于分类变量,可单独设立“未知”类别。
- 交叉验证时避免数据泄露:先切分,再填补。
3. 异常值检测与处理
异常值(离群点)是远离其他观测值的点,可能由错误引起,也可能蕴含稀有事件(如欺诈)。
3.1 检测方法
3.1.1 统计描述与可视化
- 箱线图:基于四分位数范围(IQR = Q3 – Q1),上下边界定义为 Q1 - 1.5*IQR 和 Q3 + 1.5*IQR。
- 直方图/密度图:观察分布的尾部。
- 散点图:多变量查看。
import seaborn as sns
sns.boxplot(x=df['price'])
3.1.2 Z-score 方法
适用于近似正态分布的变量。计算每个值与均值的标准差距离:
from scipy import stats
z_scores = stats.zscore(df['value'])
outliers = (z_scores > 3) | (z_scores < -3)
通常 Z-score 绝对值 > 3 视为异常。
3.1.3 IQR 方法
不依赖分布假设,鲁棒性强:
Q1 = df['value'].quantile(0.25)
Q3 = df['value'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
outliers = (df['value'] < lower_bound) | (df['value'] > upper_bound)
3.1.4 孤立森林(Isolation Forest)
适用于高维、非线性的异常检测,算法随机分割数据,异常点更容易被孤立:
from sklearn.ensemble import IsolationForest
clf = IsolationForest(contamination=0.05) # 预估异常比例
outlier_pred = clf.fit_predict(df[['feat1', 'feat2']])
# 标记为 -1 的是异常点
3.1.5 DBSCAN 聚类
基于密度的聚类,无法归为任何簇的点视为噪声(异常)。
3.2 异常值处理策略
- 删除:当确认为数据错误且样本量充足时直接移除。
- 修正:用合理的值替换(如业务逻辑修正、插值)。
- 保留并转换:使用对数、平方根变换降低异常值影响。
- 分箱/离散化:将连续变量分段,使异常值落入尾部区间。
- 单独建模:如果异常值代表稀有事件,可将其视为另一类问题。
注意:不要在未理解业务含义时自动删除,异常值可能恰恰是最有价值的信息。
4. 数据标准化与归一化
不同特征的量纲差异会扭曲基于距离的算法(如 KNN、SVM、逻辑回归梯度下降)。标准化和归一化解决此问题。
4.1 归一化(Min-Max Scaling)
将数据线性映射到 [0, 1] 区间(或自定义范围)。
$$X_{norm} = \frac{X - X_{min}}{X_{max} - X_{min}}$$
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
df_scaled = scaler.fit_transform(df[['age', 'income']])
缺点:对极端异常值敏感,会压缩正常数据范围。
4.2 标准化(Z-score Standardization)
使数据符合均值为 0、标准差为 1 的分布。
$$X_{std} = \frac{X - \mu}{\sigma}$$
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
df_std = scaler.fit_transform(df)
适用于大多数机器学习算法,不要求数据严格正态,但假设数据大致对称。
4.3 稳健标准化(Robust Scaling)
使用中位数和四分位距,对异常值鲁棒:
$$X_{robust} = \frac{X - \text{median}}{IQR}$$
from sklearn.preprocessing import RobustScaler
scaler = RobustScaler()
df_robust = scaler.fit_transform(df)
特别适合包含离群点的数据集。
4.4 其他变换
- 对数变换:
np.log1p(x),用于右偏分布(如收入、人口)。 - Box-Cox 变换:需数据为正,自动寻找最优 λ 参数。
- 分位数变换:将特征映射到均匀或正态分布。
from sklearn.preprocessing import QuantileTransformer
qt = QuantileTransformer(output_distribution='normal')
df_quantile = qt.fit_transform(df)
4.5 何时使用哪种缩放
| 场景 | 推荐方法 |
|---|---|
| 特征符合正态分布、无极端异常 | StandardScaler |
| 有异常值 | RobustScaler |
| 需保留零值稀疏性(如词频) | MinMaxScaler |
| 需要固定范围(如像素值 0-255) | MinMaxScaler |
| 数据极偏态 | 先取对数,再标准化 |
| 神经网络输入 | MinMaxScaler 或 StandardScaler + 批次归一化 |
核心原则:缩放器应在训练集上拟合,然后 transform 测试集,避免数据泄露。
5. 完整预处理流水线示例
使用 sklearn.pipeline 将预处理与模型训练串联,防止交叉验证时信息泄露。
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer
# 数值列处理
numeric_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='median')),
('scaler', StandardScaler())
])
# 分类列处理
categorical_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='constant', fill_value='missing')),
('onehot', OneHotEncoder(handle_unknown='ignore'))
])
# 组合预处理器
preprocessor = ColumnTransformer(
transformers=[
('num', numeric_transformer, numeric_features),
('cat', categorical_transformer, categorical_features)
])
# 完整流水线
clf = Pipeline(steps=[
('preprocessor', preprocessor),
('classifier', LogisticRegression())
])
# 交叉验证
from sklearn.model_selection import cross_val_score
scores = cross_val_score(clf, X, y, cv=5)
6. 常见陷阱与最佳实践
- 数据窥探:切勿在切分数据集前进行全局填补或标准化。
- 缺失值填补后的分布扭曲:大量均值填补会压缩方差,考虑多重填补(MICE)或在模型中加入不确定性。
- 异常值处理不一致:训练集和测试集应使用相同的边界(如 IQR 来自训练集)。
- 标准化二进制变量? 通常不需要,保持 0/1 即可,但如与其他连续变量同时输入线性模型,可考虑标准化。
- 日志记录:记录每一步转换的参数与顺序,保证可复现。
7. 总结
数据清洗与预处理是一门艺术与科学的结合。牢固掌握缺失值、异常值和标准化处理,能让你在真实项目中游刃有余。始终记住:
- 先理解数据,再选择技术。
- 用可视化辅助决策。
- 将预处理封装为可复用的流程。
动手实践,从原始脏数据到干净特征,你的模型将获得质的飞跃。