3. 数据预处理:数据清洗、标准化与归一化

做模式分类,数据预处理排第一。你想想看,垃圾数据进来,再牛的算法也白搭。我个人习惯把预处理分成两大块:先把数据洗干净,再把它弄成算法喜欢的尺度。今天咱们就聊聊这两步怎么做。

3.1 数据清洗——先把脏东西筛掉

数据清洗,说白了就是处理缺失值和异常值。

3.1.1 缺失值处理

缺失值很常见。传感器抽风、人工录入遗漏、系统传输丢包,都可能造成某个特征没值。我在项目中遇到过一份UCI的二手房数据,卧室数量那一列大概缺了12%。

怎么处理?我一般按这几个原则来:

  • 删除:缺失比例超过70%的特征,直接删列。数据量够大时,缺失比例小的样本也可以删行。
  • 填充:数值型用均值、中位数或众数。分类型用最频繁的类别。注意,极端分布时中位数比均值更靠谱。
  • 模型预测:缺失值当标签,用其他特征训练模型去补。比如用KNN或回归。
  • 标记:干脆把“是否缺失”当新特征扔给模型。树模型往往能利用这个信息。

举个简单的填充例子:

import pandas as pd
import numpy as np

# 读数据
df = pd.read_csv('houses.csv')

# 查看缺失比例
print(df.isnull().mean())

# 用中位数填充卧室数
df['bedrooms'].fillna(df['bedrooms'].median(), inplace=True)

# 用众数填充房屋朝向(分类型)
df['orientation'].fillna(df['orientation'].mode()[0], inplace=True)
小提示:填充前最好看看分布。如果缺失值来自极端值,中位数保留比均值更稳健。

3.1.2 异常值检测

异常值就是离群点。比如年龄列冒出个200岁,房价列出现负值。这些点会拉偏均值和方差,很多模型都对异常点敏感。

常用方法有几个:

  1. Z-Score法:假设数据正态,超过3个标准差的算异常。
  2. IQR法:四分位距。小于Q1-1.5×IQR或大于Q3+1.5×IQR就算异常,对偏态数据更鲁棒。
  3. 孤立森林:无监督,适合高维数据。

我常用IQR法,因为大部分真实数据并不正态。给你看看代码:

def detect_outliers_iqr(data, column):
    Q1 = data[column].quantile(0.25)
    Q3 = data[column].quantile(0.75)
    IQR = Q3 - Q1
    lower = Q1 - 1.5 * IQR
    upper = Q3 + 1.5 * IQR
    return data[(data[column] < lower) | (data[column] > upper)]

outliers = detect_outliers_iqr(df, 'price')
print(f'发现 {len(outliers)} 个异常房价')
注意:不要机械地删异常值。我曾经把一批真实的豪宅价格当成异常删了,结果模型在高端区间完全失效。得结合业务判断——这到底是不是真异常。

3.2 数据标准化与归一化

数据洗干净了,接下来要让不同特征的尺度统一。为什么?因为很多算法(SVM、KNN、PCA、神经网络)都依赖距离或梯度,特征范围差太多会出问题。

3.2.1 标准化(Z-Score)

标准化把数据变成均值为0、标准差为1。适合数据近似正态分布的情况。

from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)  # X是特征矩阵

转换公式很简单:

  • 新值 = (原始值 - 均值) / 标准差

我在项目中做过一个用户画像聚类,用标准化后轮廓系数从0.2跳到了0.7。效果很明显。

3.2.2 归一化(Min-Max)

归一化把数据缩放到[0,1]区间。适合那些有明确上下限的特征,比如像素0-255、评分1-5。

from sklearn.preprocessing import MinMaxScaler

scaler = MinMaxScaler()
X_scaled = scaler.fit_transform(X)

公式:

  • 新值 = (原始值 - 最小值) / (最大值 - 最小值)
选择原则:
数据近似正态 → 标准化
数据有边界(如RGB、百分比) → 归一化
存在异常值 → 标准化更抗干扰(归一化会被极端值压扁正常数据)

3.2.3 其他常见变换

方法适用场景备注
RobustScaler数据含大量异常值用中位数和IQR,不被离群点干扰
MaxAbsScaler稀疏矩阵缩放到[-1,1],保持0元素
对数/幂变换偏态分布把长尾拉近正态

举个RobustScaler的例子:

from sklearn.preprocessing import RobustScaler

scaler = RobustScaler()
X_robust = scaler.fit_transform(X)
数据预处理流程 原始数据 缺失值处理 异常值检测 标准化(Z-Score) 归一化(Min-Max) RobustScaler等 干净、尺度统一的数据 清洗阶段 尺度变换阶段

3.3 实战中的几个坑

最后说几个我踩过的坑。

  • 先切分,后缩放。一定先做训练/测试集切分,再用训练集的数据fit scaler,然后用这个scaler分别transform训练集和测试集。我之前在测试集上用了全局的均值和标准差,结果模型过拟合了。
  • 特征工程在缩放之后?先缩放再生成多项式特征,否则高次项会被放大。每次做特征变换,我都重新考虑顺序。
  • 缺失值填充不能太土。有一次我用全局均值填充,结果模型学了一个偏差。后来改用按聚类分组的均值,效果就好多了。
一句话总结: 数据预处理不是流水账,每一步都要带着问题去做。先清洗再缩放,是模式分类的起跑线。