4. 数据清洗:缺失值处理、异常值检测与数据平滑技术
各位同学,咱们今天聊聊数据清洗。说实话,很多做预测的人容易忽略这一步,觉得模型调参才是重头戏。但我得说句实在话——数据不干净,模型再花哨也是白搭。我在项目里见过太多这种情况了:数据里藏着几个坏点,结果整个预测曲线都跑偏了。
数据清洗说白了就三件事:缺的补上、坏的去掉、毛刺磨平。咱们一个一个来。
4.1 缺失值处理
发电量数据里,缺失值太常见了。传感器掉线、通信中断、存储故障……原因五花八门。我个人习惯,拿到数据第一件事就是看看缺失率。
核心原则:缺失率低于5%,直接填充;5%-20%,需要谨慎选择方法;超过20%,建议重新评估数据质量。
常用的填充方法有这么几种:
- 前向填充(ffill)——用上一个有效值填充。适合短时间缺失,比如几分钟的数据断点。
- 后向填充(bfill)——用下一个有效值填充。和ffill类似,方向相反。
- 线性插值——用前后两个点的平均值。我个人最常用这个,简单且稳定。
- 多项式插值——用更高阶的曲线拟合。嗯,这里要注意,阶数太高容易过拟合,我一般不超过3阶。
- 模型预测填充——用其他特征预测缺失值。比如用温度、风速来预测发电量。这个方法精度高,但计算量大。
举个例子,看看代码怎么写:
import pandas as pd
import numpy as np
# 模拟发电量数据
data = pd.Series([100, np.nan, np.nan, 120, 130, np.nan, 150])
# 前向填充
data_ffill = data.ffill()
# 线性插值
data_interp = data.interpolate(method='linear')
print("原始数据:", data.values)
print("前向填充:", data_ffill.values)
print("线性插值:", data_interp.values)
我的小技巧:对于发电量这种周期性很强的数据,可以试试按时间窗口分组插值。比如每天同一时刻的数据,用历史同期的均值来填充。我在一个光伏项目里用过,效果比普通插值好不少。
4.2 异常值检测
异常值是什么?说白了就是那些明显不合理的数据点。比如半夜12点发电量突然飙到满负荷,或者大晴天发电量突然掉到零。你想想看,这明显有问题。
我曾经在一个风电项目里遇到过:某台风机的发电量数据,连续三天都出现凌晨3点突然跳变。一开始以为是设备故障,查了半天才发现是数据采集卡接触不良。嗯,这就是典型的异常值问题。
常用的检测方法:
| 方法 | 适用场景 | 优缺点 |
|---|---|---|
| 3σ原则 | 数据近似正态分布 | 简单快速,但对非正态分布效果差 |
| 箱线图法(IQR) | 任意分布 | 鲁棒性好,但可能漏掉局部异常 |
| Z-score | 数据量较大 | 标准化后便于比较,但受极端值影响 |
| 孤立森林 | 高维数据 | 适合复杂场景,但参数调起来麻烦 |
我建议你从箱线图法开始。为什么?因为它不依赖数据分布,而且直观好理解。看看代码:
def detect_outliers_iqr(data, factor=1.5):
Q1 = data.quantile(0.25)
Q3 = data.quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - factor * IQR
upper_bound = Q3 + factor * IQR
outliers = data[(data < lower_bound) | (data > upper_bound)]
return outliers, lower_bound, upper_bound
# 示例
power_data = pd.Series([100, 102, 98, 105, 500, 101, 99, 103])
outliers, lb, ub = detect_outliers_iqr(power_data)
print(f"异常值: {outliers.values}")
print(f"正常范围: [{lb:.2f}, {ub:.2f}]")
注意:异常值不一定要直接删除。有时候异常值本身包含重要信息,比如设备故障的前兆信号。我建议你先标记出来,分析原因后再决定怎么处理。
4.3 数据平滑技术
发电量数据里总有些毛刺——不是异常值,就是正常的波动。比如风速突然变一下,光伏板被云遮了一下。这些毛刺会影响模型训练,让模型学到的不是趋势,而是噪声。
数据平滑,就是把这些毛刺磨平,让数据更干净。
常用的平滑方法:
- 移动平均——最简单的平滑方法。窗口大小是关键,窗口太小平滑效果差,窗口太大又会丢失细节。我一般从5开始试。
- 指数加权移动平均(EWMA)——给近期的数据更高权重。适合有趋势的数据。
- Savitzky-Golay滤波——用多项式拟合局部数据。这个我特别喜欢,因为它能保留数据的形状特征。
- 小波去噪——把信号分解到不同频率,去掉高频噪声。适合噪声频率和信号频率差异大的情况。
看看代码实现:
from scipy.signal import savgol_filter
# 模拟带噪声的发电量数据
np.random.seed(42)
time = np.arange(0, 100, 0.5)
power = 50 + 10 * np.sin(time * 0.1) + np.random.normal(0, 3, len(time))
# Savitzky-Golay滤波
power_smooth = savgol_filter(power, window_length=11, polyorder=3)
# 移动平均
window_size = 5
power_ma = np.convolve(power, np.ones(window_size)/window_size, mode='valid')
print("原始数据前10个点:", power[:10].round(2))
print("平滑后前10个点:", power_smooth[:10].round(2))
我的经验:平滑参数不要设得太激进。我曾经在一个项目里把窗口设得太大,结果把发电量的真实波动都磨没了,模型预测出来一条直线。后来我学乖了——先看数据频谱,再选平滑参数。
4.4 知识体系总览
说了这么多,咱们用一张图把整个数据清洗的流程串起来:
这张图把咱们今天讲的内容都串起来了。从原始数据出发,经过缺失值处理、异常值检测、数据平滑三个步骤,最终得到干净可用的数据。每一步都有多种方法可选,具体用哪个,得看你的数据特点。
总结一下:数据清洗不是一次性工作,而是需要反复迭代的过程。我建议你先用简单方法快速处理,看看模型效果,再根据反馈调整清洗策略。别想着一步到位,那不太现实。
好了,今天就聊到这儿。数据清洗这块,说白了就是「垃圾进,垃圾出」——数据质量决定了模型的天花板。下一节咱们聊聊特征工程,那又是另一番天地了。