数据清洗实战:缺失值处理与异常值检测

好,咱们进入实战环节。数据清洗这步,说白了就是给原始数据「洗澡」。我见过太多人拿到数据直接跑模型,结果一塌糊涂。为什么?因为数据里藏着各种「脏东西」——缺失值、异常值,它们会严重干扰随机森林的判断。

我个人习惯,拿到数据第一件事就是做描述性统计。看看有没有空值,看看分布是否合理。你想想看,如果某个污染指标有30%都是缺失的,你直接扔进模型,那结果能靠谱吗?

缺失值处理:三种主流方法

缺失值处理没有银弹。我做过一个工业园区的项目,PM2.5监测数据缺失了将近20%。当时试了三种方法,效果差异很大。下面我把代码和心得一起给你。

方法一:均值/中位数填充

这是最基础的方法。适用于数据分布比较对称的情况。如果数据有偏态,用中位数更稳健。

import pandas as pd
import numpy as np

# 模拟污染源数据
np.random.seed(42)
data = {
    'SO2': [12.3, 15.6, np.nan, 11.2, 14.8, 13.1, np.nan, 16.2],
    'NO2': [45.2, 52.1, 48.7, np.nan, 50.3, 47.6, 49.9, 51.4],
    'PM10': [85.3, 92.1, 88.6, 90.2, np.nan, 87.4, 91.5, 89.8]
}
df = pd.DataFrame(data)

# 均值填充
df_mean = df.fillna(df.mean())
print("均值填充结果:")
print(df_mean)

# 中位数填充(推荐用于有异常值的情况)
df_median = df.fillna(df.median())
print("\n中位数填充结果:")
print(df_median)
我的经验: 如果数据量不大(比如少于1000条),均值填充会引入偏差。我曾经在一个小型监测站的数据上吃过亏,均值填充后模型精度反而下降了。后来改用中位数,效果好了不少。

方法二:插值法

插值法更适合时间序列数据。比如你监测的是连续时间点的污染浓度,用前后值来推断中间缺失值,逻辑上更合理。

# 线性插值
df_interp = df.interpolate(method='linear')
print("线性插值结果:")
print(df_interp)

# 时间序列插值(如果索引是时间)
# df_interp_time = df.interpolate(method='time')

嗯,这里要注意:插值法假设数据是平滑变化的。如果污染源突然排放异常,插值可能会「平滑掉」真实信号。我在化工厂的监测数据里就遇到过这种情况——一个突发的泄漏事件,被插值法给抹平了。

避坑指南: 我曾经用插值法处理一个风速传感器的缺失数据,结果插出来的值完全不符合实际。后来发现是因为风速变化太剧烈,线性插值根本hold不住。这种情况下,宁可删掉那几条数据,也别强行插值。

异常值检测:3σ与IQR

异常值检测是数据清洗的重头戏。随机森林虽然对异常值有一定鲁棒性,但太多异常值会把模型带偏。我一般先用3σ法快速筛查,再用IQR法做精细判断。

3σ原则(拉依达准则)

这个方法的逻辑很简单:假设数据服从正态分布,那么99.7%的数据会落在均值±3个标准差之内。超出这个范围的,就标记为异常。

def detect_outliers_3sigma(df, column):
    mean = df[column].mean()
    std = df[column].std()
    lower_bound = mean - 3 * std
    upper_bound = mean + 3 * std
    
    outliers = df[(df[column] < lower_bound) | (df[column] > upper_bound)]
    return outliers, lower_bound, upper_bound

# 测试
outliers_so2, lb, ub = detect_outliers_3sigma(df_mean, 'SO2')
print(f"SO2的3σ范围:({lb:.2f}, {ub:.2f})")
print(f"检测到的异常值:\n{outliers_so2}")
关键点: 3σ法对数据分布敏感。如果数据本身不是正态分布(比如偏态严重),这个方法会误判很多正常值。我建议先用直方图看一眼分布形态。

IQR(四分位距法)

IQR法不依赖正态分布假设,更稳健。它用Q1(25%分位数)和Q3(75%分位数)来定义正常范围。通常取1.5倍IQR作为阈值。

def detect_outliers_iqr(df, column):
    Q1 = df[column].quantile(0.25)
    Q3 = df[column].quantile(0.75)
    IQR = Q3 - Q1
    lower_bound = Q1 - 1.5 * IQR
    upper_bound = Q3 + 1.5 * IQR
    
    outliers = df[(df[column] < lower_bound) | (df[column] > upper_bound)]
    return outliers, lower_bound, upper_bound

# 测试
outliers_no2, lb_iqr, ub_iqr = detect_outliers_iqr(df_mean, 'NO2')
print(f"NO2的IQR范围:({lb_iqr:.2f}, {ub_iqr:.2f})")
print(f"检测到的异常值:\n{outliers_no2}")

你想想看,为什么IQR更常用?因为实际环境监测数据很少是完美正态的。我记得有一次处理某钢铁厂的排放数据,SO2浓度分布严重右偏,3σ法把一堆正常的高值都标成了异常,改用IQR后就好多了。

实战建议:先清洗,再建模

我总结一下个人经验:

  • 缺失率低于5%:直接删除缺失行,简单粗暴有效
  • 缺失率5%-20%:用中位数或插值法填充
  • 缺失率超过20%:这个特征可能本身就有问题,建议重新考虑是否保留
  • 异常值处理:先用IQR法标记,然后结合业务知识判断是否真的异常。不要机械地删除
一个小技巧: 我习惯在清洗前和清洗后各跑一次随机森林,对比特征重要性排序。如果某个特征的重要性发生了剧烈变化,说明清洗过程可能引入了偏差。这时候要回头检查。

知识体系总览

下面这张图帮你理清数据清洗的核心逻辑:

数据清洗核心流程 原始监测数据 缺失值处理 均值/中位数填充 插值法 删除缺失行 异常值检测(3σ / IQR)

这张图把流程串起来了。从原始数据出发,先处理缺失值,再检测异常值。每一步都有多种方法可选,具体用哪个,取决于你的数据特点。

好了,数据清洗这块就讲到这里。记住一句话:垃圾进,垃圾出。数据清洗花的时间,会在模型效果上十倍回报给你。

专注资料整理