数据预处理基础:缺失值处理、异常值识别与标准化

数据预处理这件事,说实话,我见过太多人栽跟头了。你想想看,模型调得再好,数据一塌糊涂,结果就是白忙活。我个人习惯把预处理分成三大块:缺失值怎么处理、异常值怎么揪出来、数据尺度怎么统一。这三步走稳了,后面的分析才能站得住脚。

核心观点:预处理不是可选项,是必选项。跳过这一步,等于在沙子上盖楼。

缺失值处理:删还是补?这是个问题

拿到数据第一件事,先看看有没有空值。我习惯用 df.isnull().sum() 扫一眼,心里就有数了。缺失值处理无非两条路:删除或填充。但怎么选,得看场景。

删除缺失值

删除是最简单粗暴的办法。但别乱删,我总结了几条经验:

  • 行删除:当缺失比例小于 5%,且数据量够大时,直接删行。比如 10 万条数据缺了 2000 条,删了不心疼。
  • 列删除:如果某列缺失超过 70%,我建议直接扔掉。留着也是噪音。
  • 慎用场景:时间序列数据别乱删行,会破坏时间连续性。我曾经在 IoT 项目里删了几行,结果时序图直接断档,排查了半天才发现。
import pandas as pd
import numpy as np

# 模拟数据
df = pd.DataFrame({
    'A': [1, 2, np.nan, 4, 5],
    'B': [np.nan, 2, 3, 4, 5],
    'C': [1, 2, 3, 4, 5]
})

# 删除包含缺失值的行
df_drop_row = df.dropna()
print(df_drop_row)

# 删除缺失超过 50% 的列
df_drop_col = df.dropna(thresh=len(df) * 0.5, axis=1)
print(df_drop_col)

注意:删除操作不可逆。建议先备份原始数据,或者用 inplace=False 先看看效果。

填充缺失值

数据宝贵,能补就别删。填充方法有很多,我按场景排了个序:

方法 适用场景 代码示例
固定值填充 分类特征,如性别缺失填"未知" df.fillna('未知')
均值/中位数填充 数值特征,数据分布较对称用均值,有偏态用中位数 df.fillna(df.mean())
众数填充 离散特征,如城市、产品类别 df.fillna(df.mode().iloc[0])
前向/后向填充 时间序列,用前一个值或后一个值补 df.fillna(method='ffill')
插值法 连续型数据,线性或多项式插值 df.interpolate()

我个人最常用的是中位数填充。为什么?因为均值容易被极端值带偏。举个例子,一组工资数据:3000、3500、4000、100000,均值是 27625,中位数是 3750。你想想看,用均值填充明显不合理。中位数更稳健。

# 中位数填充示例
df['A'].fillna(df['A'].median(), inplace=True)

# 时间序列前向填充
df_time = pd.DataFrame({
    'date': pd.date_range('2024-01-01', periods=5),
    'value': [10, np.nan, 30, np.nan, 50]
})
df_time['value'].fillna(method='ffill', inplace=True)
print(df_time)

小技巧:填充后建议加一列标记,记录哪些值是填充的。这样后续分析时可以回溯,看看填充是否引入了偏差。

异常值初步识别:把"野马"找出来

异常值就是数据里的"野马"。它们可能是测量误差、录入错误,也可能是真正的极端事件。我见过最离谱的一次,某传感器数据里蹦出一个 99999,后来发现是通信协议解析错了。所以,识别异常值要讲究方法。

3σ 原则:适合正态分布

3σ 原则基于正态分布。数据落在均值 ±3 倍标准差之外的概率只有 0.3%,这些点可以视为异常。说白了,就是"三倍标准差之外,基本不是正常人"。

def detect_outliers_3sigma(data):
    mean = np.mean(data)
    std = np.std(data)
    lower = mean - 3 * std
    upper = mean + 3 * std
    outliers = data[(data < lower) | (data > upper)]
    return outliers, lower, upper

# 示例
data = np.random.normal(100, 15, 1000)
data = np.append(data, [200, -50])  # 加入两个异常值
outliers, low, up = detect_outliers_3sigma(data)
print(f"下界: {low:.2f}, 上界: {up:.2f}")
print(f"异常值个数: {len(outliers)}")

避坑指南:3σ 对异常值本身很敏感。如果数据里已经有大量异常值,均值和标准差会被拉偏,导致漏检。我曾经在流量监控项目里吃过这个亏——异常值太多,3σ 反而失效了。后来改用 IQR 才搞定。

IQR 方法:更稳健的选择

IQR(四分位距)不依赖均值和标准差,抗干扰能力强。它的逻辑是:把数据分成四份,取中间 50% 的范围(Q1 到 Q3),然后向外扩展 1.5 倍 IQR。超出这个范围的,就是异常。

def detect_outliers_iqr(data):
    Q1 = np.percentile(data, 25)
    Q3 = np.percentile(data, 75)
    IQR = Q3 - Q1
    lower = Q1 - 1.5 * IQR
    upper = Q3 + 1.5 * IQR
    outliers = data[(data < lower) | (data > upper)]
    return outliers, lower, upper

# 示例
data = np.random.exponential(scale=2, size=1000)
data = np.append(data, [30, 40])
outliers, low, up = detect_outliers_iqr(data)
print(f"下界: {low:.2f}, 上界: {up:.2f}")
print(f"异常值个数: {len(outliers)}")

两种方法怎么选?我做了个对比:

方法 适用分布 优点 缺点
3σ 原则 正态分布或近似正态 计算简单,理论成熟 对异常值敏感,非正态分布效果差
IQR 方法 任意分布,尤其偏态分布 稳健,不受极端值影响 对尾部数据不敏感,可能漏掉极端值

我的建议是:先画个直方图看看分布。如果数据长得像钟形,用 3σ;如果歪歪扭扭的,用 IQR。两个都试试也行,取交集或并集,看业务需求。

数据标准化与归一化:让数据"同频"

你有没有遇到过这种情况?一个特征是年龄(0-100),另一个特征是收入(0-100000),直接丢进模型,收入把年龄"吃掉"了。这就是尺度不一致的问题。标准化和归一化就是来解决这个的。

Z-score 标准化

Z-score 把数据变成均值为 0、标准差为 1 的分布。公式很简单:(x - mean) / std。它不改变数据的分布形状,只是把坐标轴平移缩放了一下。

from sklearn.preprocessing import StandardScaler

data = np.array([[100, 0.1],
                 [200, 0.2],
                 [300, 0.3],
                 [400, 0.4]])

scaler = StandardScaler()
data_scaled = scaler.fit_transform(data)
print("标准化后:\n", data_scaled)
print("均值:", data_scaled.mean(axis=0))
print("标准差:", data_scaled.std(axis=0))

Min-Max 归一化

Min-Max 把数据压缩到 [0, 1] 区间。公式:(x - min) / (max - min)。它保留了原始数据的相对大小关系,但会把所有值挤到一个小范围里。

from sklearn.preprocessing import MinMaxScaler

scaler = MinMaxScaler()
data_normalized = scaler.fit_transform(data)
print("归一化后:\n", data_normalized)

两种方法怎么选?我一般这样判断:

  • Z-score:适合数据有异常值,或者算法假设数据服从正态分布(如线性回归、PCA)。
  • Min-Max:适合数据范围已知且固定,或者算法对数值范围敏感(如神经网络、KNN)。

个人经验:做聚类分析时,我习惯先用 Z-score。因为 Min-Max 会把异常值压缩到 0 或 1,导致聚类边界模糊。而 Z-score 保留了异常值的相对位置,聚类效果更真实。

本章知识体系总览

下面这张图把本章的核心逻辑串起来了。从原始数据出发,经过缺失值处理、异常值识别、尺度变换,最终得到干净可用的数据。每一步都有对应的策略和工具。

数据预处理知识体系 原始数据 缺失值处理 删除(行/列) 填充(均值/中位数/插值) 异常值识别 3σ 原则 IQR 方法 标准化/归一化 贯穿全程,最后一步执行

这张图里,标准化和归一化我放在了旁边。为什么?因为它们不是必须的,但一旦需要,通常放在预处理最后一步。先处理缺失和异常,再调整尺度,顺序别搞反了。

总结一下:缺失值处理是"补窟窿",异常值识别是"抓坏蛋",标准化归一化是"统一度量衡"。三步走完,数据基本就"干净"了。后面做分析、建模型,心里才有底。