3. 数据预处理实战(上):缺失值处理、异常值检测与修正(3-Sigma、四分位法)
各位同学,欢迎来到数据预处理实战环节。
说实话,在功率预测这个领域摸爬滚打这么多年,我最大的体会就是:数据质量决定了模型的天花板。你算法再花哨,模型再复杂,喂进去一堆脏数据,结果就是两个字——白搭。今天咱们就来啃这块硬骨头,把缺失值和异常值这两个拦路虎给收拾了。
3.1 缺失值处理:别让空值毁了你的模型
先说说缺失值。我在项目里见过太多这样的情况:采集终端掉线、通信中断、传感器故障……结果就是数据表里一片空白。你想想看,模型训练时遇到NaN,直接报错给你看。
常见的缺失值处理策略有三种:
- 直接删除:简单粗暴,但会丢失信息。适合缺失比例极低的情况(<5%)。
- 填充法:用均值、中位数、众数或前后值填充。我习惯用中位数,因为它对异常值不敏感。
- 插值法:线性插值、多项式插值、时间序列插值。功率数据是时序的,所以线性插值往往效果不错。
核心原则:不要为了填充而填充。先分析缺失模式,再选方法。
举个例子,光伏功率数据在夜间本来就是0,如果夜间出现缺失,你用均值填充反而会引入错误。嗯,这里要注意。
import pandas as pd
import numpy as np
# 模拟功率数据
np.random.seed(42)
data = {
'time': pd.date_range('2024-01-01', periods=100, freq='15min'),
'power': np.random.randn(100) * 10 + 50
}
df = pd.DataFrame(data)
# 人为制造缺失值
df.loc[10:15, 'power'] = np.nan
df.loc[30, 'power'] = np.nan
# 方法1:中位数填充
df['power_filled'] = df['power'].fillna(df['power'].median())
# 方法2:线性插值(推荐用于时序数据)
df['power_interp'] = df['power'].interpolate(method='linear')
print("原始数据前20行:")
print(df.head(20))
我个人习惯先用interpolate()试试,如果缺失段太长(比如连续缺失超过3个点),再考虑用中位数或前向填充。为什么?因为功率变化是连续的,插值更符合物理规律。
3.2 异常值检测:揪出那些捣乱的数据点
异常值比缺失值更隐蔽。它不报错,但会悄悄拉偏你的模型。我在风电功率预测项目中遇到过一件事:某台风机连续几天功率曲线异常高,查了半天发现是风速仪被鸟粪糊住了……这就是典型的异常值。
异常值检测方法很多,今天重点讲两个最实用的:3-Sigma法则和四分位法(IQR)。
3.2.1 3-Sigma法则:正态分布下的利器
3-Sigma说白了就是:如果数据服从正态分布,那么99.7%的数据会落在均值±3个标准差范围内。超出这个范围的,我就认为它是异常值。
但这里有个坑——功率数据往往不是严格正态的。比如光伏功率在中午达到峰值,早晚低,分布是双峰的。所以3-Sigma更适合对残差或变化率做检测,而不是原始功率值。
def detect_3sigma(series, n=3):
mean = series.mean()
std = series.std()
lower = mean - n * std
upper = mean + n * std
outliers = (series < lower) | (series > upper)
return outliers, lower, upper
# 应用
outliers_3sigma, low, up = detect_3sigma(df['power_interp'])
print(f"3-Sigma检测到异常值个数:{outliers_3sigma.sum()}")
print(f"下界:{low:.2f}, 上界:{up:.2f}")
避坑指南:我曾经在风电数据上直接用3-Sigma检测原始功率,结果把正常的高风速段功率全标成了异常。后来改成对功率变化率(一阶差分)做检测,效果就好多了。
3.2.2 四分位法(IQR):不依赖分布的稳健方法
四分位法不要求数据服从正态分布,所以适用范围更广。它的逻辑是:计算Q1(25%分位数)和Q3(75%分位数),IQR = Q3 - Q1。然后定义异常值区间为 [Q1 - 1.5*IQR, Q3 + 1.5*IQR] 之外的值。
你可能会问:为什么是1.5倍?其实这个系数可以调。在功率预测中,我经常用2倍或3倍,因为功率波动本身就比较大,1.5倍太敏感了。
def detect_iqr(series, multiplier=1.5):
Q1 = series.quantile(0.25)
Q3 = series.quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - multiplier * IQR
upper = Q3 + multiplier * IQR
outliers = (series < lower) | (series > upper)
return outliers, lower, upper
# 应用
outliers_iqr, low_iqr, up_iqr = detect_iqr(df['power_interp'], multiplier=2.0)
print(f"IQR法检测到异常值个数:{outliers_iqr.sum()}")
print(f"下界:{low_iqr:.2f}, 上界:{up_iqr:.2f}")
3.3 异常值修正:不是删除,是修复
检测出异常值后怎么办?直接删掉?别急。在功率预测中,每个时间点都很重要,删掉会破坏时序连续性。我建议用修正代替删除。
常用的修正方法:
- 用边界值替换:将异常值替换为检测阈值(上界或下界)。适合轻微异常。
- 用前后均值替换:取异常点前后各2个点的平均值。适合孤立异常点。
- 用插值替换:和缺失值处理一样,用线性插值。适合连续异常段。
def correct_outliers(series, outliers, method='clip'):
corrected = series.copy()
if method == 'clip':
# 用边界值替换
_, lower, upper = detect_iqr(series)
corrected = corrected.clip(lower=lower, upper=upper)
elif method == 'interpolate':
# 将异常值设为NaN,然后插值
corrected[outliers] = np.nan
corrected = corrected.interpolate(method='linear')
return corrected
# 修正
df['power_corrected'] = correct_outliers(df['power_interp'], outliers_iqr, method='interpolate')
print("修正完成。")
注意:如果异常值连续出现超过5个点,建议先检查数据源是否出了问题。修正只是权宜之计,找到根因才是正道。
3.4 知识体系总览
下面这张图把今天讲的内容串起来了。你可以看到,数据预处理不是孤立的一步,而是和后续的特征工程、模型训练紧密相连的。
3.5 实战建议与总结
好了,今天的内容就到这里。我最后给你几个实战建议:
- 先看数据分布:用
df.describe()和df.hist()快速了解数据特征,再选方法。 - 不要迷信单一方法:3-Sigma和IQR各有优劣,我通常两个都跑一遍,对比结果。
- 保留原始数据:永远在副本上做预处理,原始数据留作备份。这是血的教训——我曾经直接覆盖了原始数据,结果发现处理错了,又得重新采集……
- 记录处理日志:哪些点被修正了?用了什么方法?这些信息对后续模型调优很有帮助。
数据预处理是个细致活,急不得。你花80%的时间在数据清洗上,后面建模就会顺风顺水。反之,数据没处理好,后面再怎么调参也是白费力气。
下一节我们继续聊数据预处理的另外两个重要环节:归一化和特征工程。到时候见。
公众号:蓝海资料掘金营,微信deep3321