数据预处理:数据清洗、缺失值处理、异常值检测与处理

数据预处理,说白了就是给数据「洗澡」。

我见过太多项目,模型选得再好,算法调得再精,结果数据一塌糊涂,最后全白干。你想想看,垃圾进,垃圾出,这是数据挖掘的铁律。

这一章,咱们就聊聊怎么把数据洗干净。

数据清洗:先看看数据有多脏

拿到数据的第一件事,不是建模,是看数据长什么样。

我个人习惯,先用 df.info()df.describe() 扫一眼。看看字段类型对不对,数值范围合不合理。

常见的数据脏问题:

  • 格式不一致:比如日期字段,有的写"2024-01-01",有的写"2024/01/01",还有写"2024年1月1日"的。
  • 重复数据:同一个用户被录入了两次,特征一模一样。
  • 无效值:年龄字段出现 -1,或者性别字段出现"男"、"male"、"M"混用。
  • 逻辑错误:注册时间晚于最后登录时间,这种明显不合理。

我在项目中遇到过最离谱的一次,某个字段叫「收入」,结果有人填了"保密",有人填了"0",还有人填了"999999999"。嗯,这种数据不洗,模型根本没法用。

# 快速检查数据质量
import pandas as pd

df = pd.read_csv('raw_data.csv')
print(df.info())          # 看字段类型和非空数量
print(df.describe())      # 看数值分布
print(df.duplicated().sum())  # 看重复行数

缺失值处理:别急着删

缺失值,是数据预处理里最头疼的问题之一。

很多人一看到缺失值,第一反应就是删掉。我建议你冷静一下。缺失值背后可能藏着信息。

缺失值的三种类型

类型 含义 例子
完全随机缺失 缺失与数据本身无关 问卷被风吹走了一页
随机缺失 缺失与其他已知变量有关 高收入人群更不愿意填收入
非随机缺失 缺失与缺失值本身有关 病情越重的人越容易漏填症状

为什么会这样?因为不同类型的缺失,处理方式完全不同。

常用的处理方法

  1. 直接删除:缺失比例很小(比如 <5%),且是随机缺失,可以删。
  2. 填充均值/中位数:数值型字段,分布比较对称用均值,有偏态用中位数。
  3. 填充众数:分类字段,用出现最多的类别填充。
  4. 前向/后向填充:时间序列数据,用上一个或下一个值填充。
  5. 模型预测填充:用其他字段训练模型,预测缺失值。这个方法比较重,但效果最好。

我的经验:千万别用0去填充缺失值,除非你确定0有意义。我曾经见过有人把年龄缺失全填0,结果模型把0岁用户当成了一个重要群体……

# 缺失值处理示例
# 数值型字段用中位数填充
df['age'].fillna(df['age'].median(), inplace=True)

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

# 时间序列用前向填充
df['temperature'].fillna(method='ffill', inplace=True)

异常值检测:别让一个点带偏整个模型

异常值,就是那些「格格不入」的数据点。

你想想看,如果全班同学身高都在160-180之间,突然来了个3米高的,那这个点就会把均值拉偏。模型也一样,一个异常值可能让回归线歪到天上去。

常用的检测方法

方法 适用场景 原理
Z-Score 数据近似正态分布 超过3个标准差视为异常
IQR(四分位距) 数据有偏态 Q1-1.5*IQR 或 Q3+1.5*IQR 之外
孤立森林 高维数据 随机切分,异常点更容易被孤立
DBSCAN 有聚类结构的数据 不在任何簇中的点视为异常

注意:异常值不一定是错误。比如信用卡欺诈检测,异常值恰恰是我们想找的目标。所以,先搞清楚业务含义,再决定怎么处理。

# IQR方法检测异常值
Q1 = df['income'].quantile(0.25)
Q3 = df['income'].quantile(0.75)
IQR = Q3 - Q1

lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR

outliers = df[(df['income'] < lower_bound) | (df['income'] > upper_bound)]
print(f"检测到 {len(outliers)} 个异常值")

异常值的处理方式

  • 删除:确认是录入错误,直接删。
  • 截尾:把异常值替换成边界值(比如用上下限替换)。
  • 视为缺失:先标记为缺失,再用缺失值处理方法处理。
  • 保留:如果异常值有业务意义,比如高净值客户,那就留着。

避坑指南:我曾经在处理电商数据时,发现某个用户的订单金额是其他用户的100倍。一开始想删掉,后来一查,是个企业大客户。嗯,要是删了,整个模型就失真了。

数据预处理的整体流程

说了这么多,咱们理一下顺序。

  1. 第一步:数据概览——看字段、看类型、看缺失、看分布。
  2. 第二步:数据清洗——去重、格式统一、逻辑校验。
  3. 第三步:缺失值处理——分析缺失类型,选择填充或删除。
  4. 第四步:异常值检测与处理——用IQR或Z-Score检测,结合业务决定去留。
  5. 第五步:数据保存——把清洗后的数据存下来,方便后续使用。

我个人习惯,每一步都会写一个检查点。比如清洗后看看还有没有重复值,填充后看看分布有没有变化。这样出了问题,能快速定位。

# 完整的数据预处理流水线
def preprocess_data(df):
    # 1. 去重
    df = df.drop_duplicates()
    
    # 2. 缺失值处理
    for col in df.columns:
        if df[col].dtype in ['int64', 'float64']:
            df[col].fillna(df[col].median(), inplace=True)
        else:
            df[col].fillna(df[col].mode()[0], inplace=True)
    
    # 3. 异常值处理(IQR方法)
    for col in df.select_dtypes(include=['int64', 'float64']).columns:
        Q1 = df[col].quantile(0.25)
        Q3 = df[col].quantile(0.75)
        IQR = Q3 - Q1
        lower = Q1 - 1.5 * IQR
        upper = Q3 + 1.5 * IQR
        df[col] = df[col].clip(lower, upper)
    
    return df

clean_df = preprocess_data(df)

数据预处理这件事,看起来琐碎,但恰恰是最能体现功力的地方。

我见过很多新手,花80%的时间调模型,结果数据预处理只花了20%。真正有经验的人,会把60%的时间花在数据准备上。因为数据质量,决定了模型的天花板。

嗯,这一章就到这里。下一章咱们聊聊特征工程,怎么从原始数据里挖出更有用的信息。