2. 数据清洗误区:缺失值处理、异常值检测、去重与归一化

数据清洗这事儿,看着简单,坑却不少。我见过太多团队在第一步就栽跟头——要么把有用的数据删光了,要么把噪声当宝贝留着。今天咱们就聊聊四个最容易踩坑的环节。

核心原则:清洗不是越狠越好,而是越准越好。每删一条数据,你都得知道为什么删。

数据清洗核心流程与决策点 原始数据 缺失值处理 异常值检测 重复数据去重 标准化/归一化 删除 填充 3σ 法 IQR 法 完全去重 模糊去重 Min-Max Z-score 避坑要点 • 缺失率 < 5% 可删除,否则需填充 • 异常值先分析原因,再决定处理方式 • 去重前确认业务逻辑,归一化选择看模型

2.1 缺失值处理:删除还是填充?

遇到缺失值,很多人的第一反应就是删。但我想说——别急着动手。我有个项目,因为删了太多缺失行,直接把一个稀有类别的样本全删光了,模型训练出来直接偏到姥姥家。

什么时候该删?

  • 缺失率极低(< 5%):直接删行,不影响整体分布。我个人习惯先看一眼缺失行是不是随机分布,如果是,放心删。
  • 特征缺失率过高(> 70%):这个特征基本废了,删列比填充更靠谱。
  • 缺失与标签强相关:比如用户没填收入,可能本身就是低收入群体。这时候删了反而引入偏差。

什么时候该填充?

  • 数值型特征:中位数填充(对异常值鲁棒)或均值填充(数据正态时)。
  • 分类型特征:众数填充,或者单独设一个"未知"类别。
  • 时间序列:前向填充(ffill)或插值法。我记得有一次做传感器数据,用线性插值效果比均值好很多。

我的小技巧:先做缺失值可视化——用热图看缺失模式。如果缺失呈现"单调"模式(比如越往后缺失越多),那可能是系统性问题,填充前得先修数据采集流程。

import pandas as pd
import numpy as np

# 缺失率计算
def missing_analysis(df):
    missing_ratio = df.isnull().mean().sort_values(ascending=False)
    return missing_ratio[missing_ratio > 0]

# 填充策略示例
df['age'].fillna(df['age'].median(), inplace=True)  # 中位数填充
df['income'].fillna(df.groupby('education')['income'].transform('median'), inplace=True)  # 分组填充

避坑指南:我曾经在一个信贷评分项目中,直接用全局均值填充收入缺失值,结果模型在低收入群体上表现极差。后来改用分组中位数填充,效果才正常。记住:填充不是补数,是还原数据本该有的样子。

2.2 异常值检测:3σ 还是 IQR?

异常值检测,说白了就是找"不对劲"的数据。但什么算不对劲?这得看你的数据长什么样。

3σ 法则(正态分布假设)

如果数据服从正态分布,99.7% 的数据落在均值 ±3σ 范围内。超出这个范围的,就算异常。

  • 优点:理论清晰,计算简单。
  • 缺点:对极端值敏感——一个异常值就能把均值和标准差拉偏,导致更多正常值被误判。

IQR 方法(四分位距)

不依赖分布假设。定义下界 Q1 - 1.5×IQR,上界 Q3 + 1.5×IQR,超出就算异常。

  • 优点:对极端值鲁棒,适合偏态分布。
  • 缺点:对样本量敏感,小样本时 IQR 可能不稳定。
对比维度 3σ 法 IQR 法
分布假设 正态分布 无假设
对极端值敏感度 高 低
适用场景 对称分布、大样本 偏态分布、小样本
误判率(正态数据下) 约 0.3% 约 0.7%

我的经验:实际项目中,我更喜欢先用 IQR 做初步筛选,再用业务规则二次确认。比如电商订单金额,IQR 能抓到大部分异常,但还得结合业务——双十一的大单不是异常,是正常波动。

def detect_outliers_iqr(data, factor=1.5):
    Q1 = data.quantile(0.25)
    Q3 = data.quantile(0.75)
    IQR = Q3 - Q1
    lower_bound = Q1 - factor * IQR
    upper_bound = Q3 + factor * IQR
    return (data < lower_bound) | (data > upper_bound)

def detect_outliers_3sigma(data):
    mean = data.mean()
    std = data.std()
    return (data < mean - 3*std) | (data > mean + 3*std)

避坑指南:千万别把异常值直接删了完事。我见过一个团队删了 15% 的"异常"数据,结果模型上线后频繁误报——那些"异常"其实是业务中的长尾需求。正确的做法是:先标记,再分析,最后决定是删除、修正还是保留。

2.3 重复数据去重:别把"长得像"当成"同一个"

重复数据去重,听起来简单——drop_duplicates() 一行代码搞定。但现实往往更复杂。

完全重复 vs 部分重复

  • 完全重复:所有字段都一样。这种直接删,没商量。
  • 部分重复:比如两条用户记录,姓名一样但地址不同。这时候得看业务逻辑——是同一个人搬家了,还是两个不同的人重名?

模糊去重

文本数据里常见:比如"北京市"和"北京"、"张三"和"张 三"。我习惯用编辑距离(Levenshtein Distance)或 Jaccard 相似度来做模糊匹配。

from fuzzywuzzy import fuzz

def dedup_fuzzy(df, col, threshold=90):
    """基于模糊匹配的去重"""
    duplicates = []
    for i in range(len(df)):
        for j in range(i+1, len(df)):
            score = fuzz.ratio(df.iloc[i][col], df.iloc[j][col])
            if score >= threshold:
                duplicates.append((i, j, score))
    return duplicates

我的习惯:去重之前,先确认业务上"唯一"的定义是什么。比如用户 ID 唯一,但同一个用户可能在不同时间有多个订单——这不叫重复,这叫正常行为。

2.4 标准化 vs 归一化:选错了模型会"罢工"

标准化和归一化,很多人混着用。其实它们解决的问题不一样。

Min-Max 归一化

把数据缩放到 [0, 1] 区间。公式:(x - min) / (max - min)。

  • 适用场景:数据有明确边界(如图像像素 0-255),或算法对尺度敏感(如 KNN、神经网络)。
  • 缺点:对异常值敏感——一个极端值就能把其他值压到很小。

Z-score 标准化

把数据变成均值为 0、标准差为 1 的分布。公式:(x - mean) / std。

  • 适用场景:数据近似正态分布,或算法假设数据服从正态分布(如线性回归、PCA)。
  • 缺点:不保证数据落在固定区间。
方法 输出范围 对异常值敏感 适用算法
Min-Max [0, 1] 是 KNN、神经网络、SVM
Z-score 无固定范围 否 线性回归、PCA、聚类

我的建议:如果你不确定选哪个,先试试 Z-score。它在大多数场景下表现更稳定。但如果你用树模型(随机森林、XGBoost),其实标准化和归一化都不需要——树模型对尺度不敏感。

from sklearn.preprocessing import MinMaxScaler, StandardScaler

# Min-Max 归一化
scaler_minmax = MinMaxScaler()
X_train_scaled = scaler_minmax.fit_transform(X_train)

# Z-score 标准化
scaler_std = StandardScaler()
X_train_std = scaler_std.fit_transform(X_train)

避坑指南:记得先拆分训练集和测试集,再用训练集的参数(min/max 或 mean/std)去变换测试集。我见过有人把整个数据集一起标准化,然后才拆分——这会导致数据泄露,模型评估结果虚高。

数据清洗没有银弹。每个决策都取决于你的数据分布、业务场景和模型选择。嗯,记住一点:清洗的目的是让数据更真实,而不是更"干净"。