数据清洗实战:缺失值处理与异常值检测
好,咱们进入实战环节。数据清洗这步,说白了就是给原始数据「洗澡」。我见过太多人拿到数据直接跑模型,结果一塌糊涂。为什么?因为数据里藏着各种「脏东西」——缺失值、异常值,它们会严重干扰随机森林的判断。
我个人习惯,拿到数据第一件事就是做描述性统计。看看有没有空值,看看分布是否合理。你想想看,如果某个污染指标有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)
方法二:插值法
插值法更适合时间序列数据。比如你监测的是连续时间点的污染浓度,用前后值来推断中间缺失值,逻辑上更合理。
# 线性插值
df_interp = df.interpolate(method='linear')
print("线性插值结果:")
print(df_interp)
# 时间序列插值(如果索引是时间)
# df_interp_time = df.interpolate(method='time')
嗯,这里要注意:插值法假设数据是平滑变化的。如果污染源突然排放异常,插值可能会「平滑掉」真实信号。我在化工厂的监测数据里就遇到过这种情况——一个突发的泄漏事件,被插值法给抹平了。
异常值检测: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}")
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法标记,然后结合业务知识判断是否真的异常。不要机械地删除
知识体系总览
下面这张图帮你理清数据清洗的核心逻辑:
这张图把流程串起来了。从原始数据出发,先处理缺失值,再检测异常值。每一步都有多种方法可选,具体用哪个,取决于你的数据特点。
好了,数据清洗这块就讲到这里。记住一句话:垃圾进,垃圾出。数据清洗花的时间,会在模型效果上十倍回报给你。