数据预处理(上):缺失值处理、异常值检测与标准化

大家好,我是老蓝。今天咱们聊聊数据预处理。

说实话,很多初学者一上来就急着调模型、跑算法,结果模型效果差得离谱。我见过太多这样的案例了。其实啊,数据预处理才是整个机器学习流程里最耗时间、也最见功力的环节。我个人习惯把预处理比作「做饭前的洗菜切菜」——菜没洗干净,再好的厨子也白搭。

这一章我们聚焦三个核心问题:缺失值异常值数据标准化。这三个问题处理不好,模型基本就废了。咱们一个一个来。

核心观点:数据预处理不是可选项,是必选项。你花在预处理上的每一分钟,都会在模型效果上得到回报。

数据预处理 缺失值处理 删除 | 均值填充 | 中位数填充 前向填充 | KNN填充 异常值检测 3σ原则 | IQR箱线图 Z-Score | 孤立森林 数据标准化 Z-Score标准化 Min-Max归一化 目标:干净、一致、可训练的优质数据

一、缺失值处理:别让空值毁了你的模型

先问个问题:你拿到一份数据,发现有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 处理异常值的策略

检测到异常值后怎么办?通常有几种选择:

  1. 删除:如果异常值明显是错误数据,直接删
  2. 截尾:把异常值替换为边界值(比如用Q3+1.5×IQR替换)
  3. 当作缺失值处理:用填充方法处理
  4. 保留:如果异常值本身有业务含义(比如欺诈交易),反而要重点分析

避坑指南:我曾经在房价预测项目里,把那些「天价豪宅」当作异常值删掉了。结果模型在高端房产上的预测一塌糊涂。后来才意识到,那些「异常值」恰恰是我们要预测的目标之一。所以,处理异常值之前,先问问自己:这个异常值是真的错误,还是真实存在的极端情况?

三、数据标准化与归一化:让特征站在同一起跑线上

为什么需要标准化?举个例子:年龄范围是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

重要提醒:标准化和归一化的参数(均值、标准差、最小值、最大值)一定要只从训练集计算,然后用同样的参数去转换测试集。千万别把测试集的数据混进来一起算——这叫「数据泄露」,会导致模型评估结果虚高。

四、实战小贴士:预处理流程建议

说了这么多,我给大家总结一个我常用的预处理流程:

  1. 先看数据概况:用 df.info() 和 df.describe() 快速了解缺失情况和分布
  2. 处理缺失值:先判断缺失原因,再选择删除或填充
  3. 检测异常值:用IQR方法初步筛查,结合业务判断
  4. 标准化/归一化:根据算法需求选择合适的方法
  5. 验证效果:可视化检查处理前后的分布变化

记住,没有放之四海皆准的预处理方案。每个数据集都有自己的脾气,多试几种方法,找到最适合你业务场景的那一套。

最后说一句:数据预处理做得好,模型训练事半功倍。我见过太多人花80%的时间调参,却只花20%的时间做预处理——方向反了。把预处理做扎实,你会发现模型效果自然就上去了。


蓝海数据掘金营,专注资料整理