3、数据预处理(上):缺失值处理、异常值检测与修正、数据重采样

各位好,欢迎来到数据预处理环节。说实话,很多做风电功率预测的朋友,一上来就急着调模型、调参数,结果效果不好,回头一看——数据没洗干净。我见过太多这样的案例了。数据预处理占整个项目落地时间的60%以上,一点都不夸张。你想想看,模型再牛,喂进去的是垃圾,吐出来的只能是更精致的垃圾。

这一章我们先讲三个核心操作:缺失值处理、异常值检测与修正、数据重采样。这三板斧抡好了,后面的建模工作才能站得住脚。

核心观点:数据预处理不是「能省则省」的步骤,而是「不得不做」的硬功夫。尤其是风电数据,传感器故障、通信中断、极端天气干扰,各种幺蛾子层出不穷。我个人的习惯是:拿到数据后,先花一天时间做可视化,看看数据长什么样,再动手处理。

数据预处理(上) 缺失值处理 删除 / 填充 / 插值 异常值检测与修正 3σ / IQR / 领域规则 数据重采样 降采样 / 升采样 / 对齐 直接删除 均值/中位数填充 时间插值 3σ 法则 IQR 箱线图 物理约束修正 10min→1h 聚合 1h→10min 插值 时间戳对齐 目标:构建干净、连续、等间隔的时序数据

3.1 缺失值处理——别让「空」坑了你

风电数据里缺失值太常见了。传感器偶尔抽风、通信丢包、维护停机,都会导致数据出现空洞。我记得有一次做某风场项目,拿到数据一看,某台风机连续3天的风速数据全是NaN。当时甲方还催得紧,我差点就想着「先跑个模型看看」。还好忍住了,回头一查,是那个传感器的采集模块坏了。

处理缺失值,我一般分三步走:

  1. 先诊断——看看缺失率有多高,缺失是随机的还是有规律的。
  2. 再决策——缺失率太高(比如超过30%),直接删掉这个特征或时间段;缺失率低,考虑填充。
  3. 后填充——选合适的方法把坑填上。

具体填充方法,我整理了一个表格,方便你对照着选:

方法 适用场景 优点 缺点
直接删除 缺失率 < 5%,且随机分布 简单粗暴,不影响分布 丢失信息,可能破坏连续性
均值/中位数填充 短时间缺失,数据平稳 计算快,容易实现 会降低方差,引入偏差
前向/后向填充 传感器短暂掉线(几分钟) 保持趋势,符合物理直觉 长时间填充会「平」掉波动
线性插值 缺失段较短(1-3个点) 平滑过渡,不突兀 对剧烈变化场景效果差
样条插值 缺失段稍长,数据有周期性 曲线更自然 容易过拟合,计算量大

我的小技巧:对于风速、功率这种有物理上下限的变量,我从来不用均值填充。为啥?因为均值可能落在「不可能」的区域。比如某风机额定功率1.5MW,你拿均值填充出一个1.2MW,但实际当时风速很低,根本发不出那么多电。我一般优先用前向填充或线性插值,更贴近真实物理过程。

代码示例(Python):

import pandas as pd
import numpy as np

# 读取数据
df = pd.read_csv('wind_data.csv', parse_dates=['time'], index_col='time')

# 检查缺失率
missing_rate = df.isnull().mean()
print('缺失率:\n', missing_rate)

# 前向填充(最多填充2个连续缺失点)
df['wind_speed'] = df['wind_speed'].fillna(method='ffill', limit=2)

# 剩余缺失用线性插值
df['wind_speed'] = df['wind_speed'].interpolate(method='linear')

# 如果还有缺失(比如开头或结尾),用后向填充兜底
df['wind_speed'] = df['wind_speed'].fillna(method='bfill')

3.2 异常值检测与修正——别让「野点」带偏了模型

异常值比缺失值更隐蔽,也更危险。缺失值你一眼就能看到NaN,但异常值看起来「像真的」,实际上却是错的。我在一个项目中就吃过这个亏:某台风机的功率曲线图上,有几个点功率特别高,看着像是「超发」,甲方还很高兴。结果一查,是传感器受雷击后信号漂移了。如果当时没处理,模型就会学着去预测「不可能的高功率」,后果可想而知。

异常值检测,我常用的方法有三种:

  • 3σ 法则:假设数据服从正态分布,超出均值±3倍标准差的值视为异常。适合风速、温度这类近似正态的变量。
  • IQR 箱线图法:用四分位距来界定异常。Q1 - 1.5*IQR 以下,Q3 + 1.5*IQR 以上算异常。对偏态分布更鲁棒。
  • 物理约束法:根据风机参数设定硬边界。比如功率不能超过额定值,风速不能超过切出风速。这个最可靠,我强烈建议你优先用。

注意:千万不要机械地套用统计方法!我曾经遇到一个案例,用3σ法则把台风期间的高风速全标成了异常,直接删掉了。结果模型在极端天气下完全失效。异常值检测一定要结合业务知识,先问自己一句:「这个值在物理上可能发生吗?」

修正异常值,我的做法是:

  1. 先标记异常点,不急着删。
  2. 对于明显是传感器故障的(比如功率突变为0又恢复),用前后正常值插值替换。
  3. 对于物理上不可能的值(比如风速200m/s),直接置为NaN,然后走缺失值处理流程。
  4. 对于边界附近的「可疑值」,保留但打上标签,后续建模时可以加一个「异常标记」特征。

代码示例:

# IQR 法检测异常
Q1 = df['power'].quantile(0.25)
Q3 = df['power'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR

# 标记异常
df['power_anomaly'] = (df['power'] < lower_bound) | (df['power'] > upper_bound)

# 物理约束修正(功率不能超过额定值1.5MW)
df.loc[df['power'] > 1.5, 'power'] = np.nan
df['power'] = df['power'].interpolate(method='linear')

3.3 数据重采样——把时间轴「捋顺」

风电数据的时间分辨率五花八门。有的风场是10秒级采集,有的是10分钟级,还有的是1小时级。而且经常出现时间戳不齐的情况——比如某台风机上报时间比另一台晚了2分钟。如果不做重采样,后续建模时特征对齐就是一场噩梦。

我个人习惯是:统一重采样到10分钟或1小时。为什么是这两个粒度?因为10分钟是风电功率预测的行业标准粒度,1小时是电网调度常用的粒度。具体选哪个,取决于你的预测目标和数据量。

重采样有两种情况:

  • 降采样(从高频到低频):比如把10秒数据聚合成10分钟数据。聚合函数一般用均值,但也可以考虑中位数(抗异常值)或最大值(关注峰值)。
  • 升采样(从低频到高频):比如把1小时数据插值成10分钟数据。这时候要小心,插值会引入「虚假信息」,模型可能会学到不存在的细节。

我的建议:尽量做降采样,少做升采样。降采样是「信息压缩」,丢失的是噪声;升采样是「无中生有」,引入的是不确定性。如果非要做升采样,优先用线性插值或前向填充,别用高阶插值,否则模型会「想太多」。

代码示例:

# 确保时间索引是 datetime 类型,且排序
df.index = pd.to_datetime(df.index)
df = df.sort_index()

# 降采样:10秒 -> 10分钟,取均值
df_10min = df.resample('10T').mean()

# 如果10分钟内缺失太多(比如超过50%),标记为NaN
def custom_agg(series):
    if series.isnull().sum() / len(series) > 0.5:
        return np.nan
    return series.mean()

df_10min = df.resample('10T').apply(custom_agg)

# 升采样:1小时 -> 10分钟,线性插值
df_hourly = df.resample('1H').mean()
df_10min_up = df_hourly.resample('10T').interpolate(method='linear')

嗯,到这里,数据预处理的「上半场」就差不多了。缺失值、异常值、重采样,这三件事做扎实了,你的数据至少能打80分。剩下的20分,我们下一章接着聊——特征工程和时间序列平滑。别急,一口吃不成胖子,数据预处理这事儿,慢就是快。


专注资料整理