数据清洗与预处理:缺失值、异常值与标准化

FreeGuideOnline 最新 2026-06-16

数据清洗与预处理完全指南:缺失值、异常值与标准化

数据清洗与预处理是任何数据分析、机器学习项目中最耗时但最重要的环节。原始数据往往包含噪声、不一致和缺失,直接建模会导致“垃圾进,垃圾出”的结果。本教程将系统讲解三大核心任务:缺失值处理异常值检测与处理以及数据标准化/归一化,帮助你建立扎实的数据预处理技能。


1. 理解数据清洗的必要性

在接触具体技术前,先明确数据质量问题的主要来源:

  • 人为错误:数据录入错误、测量误差。
  • 系统缺陷:传感器故障、数据管道中断。
  • 数据整合:多源数据合并时的格式不统一、编码冲突。
  • 隐私处理:脱敏操作可能引入缺失。

高质量的数据预处理可以显著提升模型表现,甚至比选择复杂模型更有效。


2. 缺失值处理

缺失值表现为 NaNNone、空字符串或占位符(如 -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 缺失值处理流程总结

  1. 分析缺失模式与比例。
  2. 若需保留数据,选择填补策略(简单统计、回归、KNN)。
  3. 对于分类变量,可单独设立“未知”类别。
  4. 交叉验证时避免数据泄露:先切分,再填补。

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. 总结

数据清洗与预处理是一门艺术与科学的结合。牢固掌握缺失值、异常值和标准化处理,能让你在真实项目中游刃有余。始终记住:

  • 先理解数据,再选择技术。
  • 用可视化辅助决策。
  • 将预处理封装为可复用的流程。

动手实践,从原始脏数据到干净特征,你的模型将获得质的飞跃。