数据预处理:把脏数据喂给模型之前,先清理干净

说实话,我带过不少团队,大家最喜欢干的事就是拿到数据直接塞进模型。结果呢?跑出来的结果乱七八糟。我几年前接过一个金融反欺诈项目,数据里一堆null值和离群点,模型训练完准确率不到六成,排查了两天才发现是原始数据就没洗干净。

数据预处理,说白了,就是给模型吃的东西先过筛子。你想想看,拿发霉的面粉去做面包,面包能好吃吗?模型也一样。所以这一章,咱们把数据清洗、标准化、缺失值和异常值这四个硬骨头啃下来。

4.1 数据清洗:先把脏东西挑出去

数据清洗,我习惯把它想成「洗菜」。从各个数据源拿到的数据,总会有重复、无效、格式不一致的问题。具体怎么做?我列一下我常用的步骤——

  • 重复值处理:用 DataFrame.duplicated() 检查,再用 .drop_duplicates() 删掉。
  • 格式统一:比如日期字段有的是“2024-01-15”,有的是“01/15/2024”。全转成标准 datetime。
  • 无关特征剔除:像用户ID、时间戳这种,不是特别场景就别留着。

我遇到过最离谱的一个项目,因为用户ID列里混了全角和半角的空格,导致整个关联查询全错。该不会是格式问题吧?嗯,后来排查才发现是清洗阶段漏了这一步。

个人实战建议:清洗阶段不要贪快。我每次会打印 df.info() 和 df.describe(),先肉眼扫一遍有没有离谱的值。

4.2 标准化:让所有特征站在同一个尺度上

标准化,说白了,就是让不同尺度的特征能在同一个舞台上跳舞。比如一个特征是年龄(0~100),另一个特征是年收入(0~100000),如果不做标准化,模型会自动认为收入这个特征更重要——但实际情况可能完全不是这样。

我常用的两种方法:

方法公式适用场景注意事项
Z-score 标准化\( z = \frac{x-\mu}{\sigma} \)数据近似正态分布,存在离群点离群点会影响均值和标准差
Min-Max 标准化\( x' = \frac{x-min}{max-min} \)数据有明确边界,比如像素值0-255对离群点敏感,新数据可能超出[0,1]

我个人习惯:如果后续要做异常检测,一般先用 Z-score,因为它能保留离群点的相对关系。比如在金融交易数据里,一笔大额交易直接会被 Z-score 打到3σ以外,这对后续检测特别友好。

一个坑:标准化参数(均值和标准差)只能在训练集上计算,然后用同一套参数去变换测试集或新数据。别问我为什么——我在一个项目里用全量数据算标准化,结果模型上线后新数据漂移了,排查半天才发现参数泄漏了。

4.3 缺失值处理:别轻易扔掉那些“空”

缺失值在现实数据里太常见了。有些团队直接一删了事,但我告诉你,这样很可能会出事。为什么?因为缺失本身可能就包含了信息。

我的处理策略通常有三种:

  1. 直接删除:适用于缺失比例极小(<5%),且随机缺失的情况。用 df.dropna() 就行。
  2. 填充:均值/中位数/众数填充。注意:分类变量用众数,连续变量用中位数(避免受离群点影响)。
  3. 模型预测:把缺失字段当目标,用其他字段预测它。我记得做用户画像项目时,用随机森林对缺失的年龄做预测,效果比均值填充好了近10个点。
千万别直接删! 我曾经在金融风控项目里一股脑删了所有含空值的行。结果呢?模型直接崩了,因为缺失与违约行为强相关——逾期用户往往不想填收入。删掉缺失值就是删掉关键信息。

一个简单的代码示例:

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

# 中位数填充连续变量
df['income'].fillna(df['income'].median(), inplace=True)

# 众数填充分类变量
df['gender'].fillna(df['gender'].mode()[0], inplace=True)

4.4 异常值初步筛选:先别急着戴帽子

异常值检测是个大学问,这里我们先做初步筛选——说白了,就是把那些明显“离谱”的值先挑出来,别让它们污染后面的标准化和建模。

我常用的快速方法:

  • IQR 规则:小于 Q1 - 1.5×IQR 或大于 Q3 + 1.5×IQR 的,标记为可疑点。
  • Z-score 规则:Z-score 值大于3的,初步判定异常。

为什么不直接删?因为有些异常值反而是关键信号。比如反欺诈场景,一笔大额交易可能就是诈骗,你删了就等于把目标样本扔了。所以初步筛选只是“标记”,不是“删除”。

import numpy as np

# IQR 方法
Q1 = df['amount'].quantile(0.25)
Q3 = df['amount'].quantile(0.75)
IQR = Q3 - Q1
outliers = df[(df['amount'] < Q1 - 1.5*IQR) | (df['amount'] > Q3 + 1.5*IQR)]
我的经验:先做标准化,再做异常值筛选,顺序别搞反。我一开始就吃过这个亏——标准化前异常值把均值和方差拉偏了,导致正常点也被判定为异常。正确的流程是:先清洗→标准化→缺失值处理→异常值标记。

数据预处理整体流程

下面这张图是我个人总结的数据预处理工作流,把这四个环节串起来,你就不会乱了:

数据预处理工作流 数据采集 数据清洗 标准化 缺失值处理 异常值筛选 构建模型 先标记异常,不要直接删除 后续再决定是否处理

这张图的核心逻辑:前四个环节是串行的,缺一不可。异常值筛选放在缺失值处理之后,是因为有些缺失值本身就是异常信号,先填充再筛异常会更合理。箭头最后指向模型构建,但别忘了——预处理只是一个开始。

嗯,上面这些就是数据预处理的核心内容。做异常检测,这部分如果偷懒,后面再怎么调参都没用。下一环节我们来好好聊聊特征工程,那是把原始数据变成模型真正能用的“料”的过程。