4. 数据预处理实战(下):归一化、重采样与特征工程

好,咱们接着聊。上一节我们把缺失值、异常值这些「脏数据」处理干净了。但说实话,那只是热身。真正让模型起飞的关键,在于今天要讲的这三个环节:归一化/标准化、时间序列重采样、特征工程

我刚开始做功率预测那会儿,总觉得特征越多越好。结果呢?模型训练慢得像蜗牛,效果还差。后来才明白——数据不归一化,模型根本学不动。你想想看,功率值动辄几百兆瓦,温度才几十度,神经网络一算,全被大数值带偏了。

核心观点: 数据预处理不是「洗菜」,而是「配菜」。洗得干净只是基础,切得好、搭配得当,才能炒出好菜。

4.1 数据归一化与标准化

这两个概念经常被混用,我简单区分一下:

  • 归一化(Min-Max Scaling):把数据压缩到 [0,1] 区间。公式是 (x - min) / (max - min)。适合数据分布比较均匀的情况。
  • 标准化(Z-score):把数据变成均值为0、标准差为1的分布。公式是 (x - μ) / σ。适合数据有异常值或分布偏态的情况。

我个人习惯:功率预测场景下,优先用标准化。为什么?因为风电、光伏功率经常有极端天气导致的尖峰值,归一化会被这些尖峰压扁,导致正常区间内的分辨率丢失。

我的经验: 有一次做光伏功率预测,某天下午突然乌云密布,功率从800MW骤降到50MW。如果用归一化,这个50MW会被映射到接近0,模型就学不到「低功率」的规律了。改用标准化后,效果明显改善。

代码实现很简单,用 scikit-learn 就行:

from sklearn.preprocessing import StandardScaler, MinMaxScaler

# 标准化
scaler_std = StandardScaler()
df['power_scaled'] = scaler_std.fit_transform(df[['power']])

# 归一化
scaler_mm = MinMaxScaler()
df['power_norm'] = scaler_mm.fit_transform(df[['power']])
注意: 一定要先拆分训练集和测试集,再用训练集的参数去变换测试集。千万别对整个数据集做 fit_transform,否则会造成数据泄露,模型效果虚高。

4.2 时间序列重采样

原始数据往往是秒级或分钟级的,但我们的预测目标通常是15分钟或1小时。这就涉及重采样

重采样说白了就是改变时间频率。有两种方向:

  • 降采样(Downsampling):从高频到低频。比如1分钟数据 -> 15分钟数据。常用聚合函数:均值、最大值、最小值。
  • 升采样(Upsampling):从低频到高频。比如1小时数据 -> 15分钟数据。常用插值方法:线性插值、前向填充。

功率预测里,降采样更常见。我一般这样处理:

# 确保时间列是datetime类型
df['time'] = pd.to_datetime(df['time'])
df.set_index('time', inplace=True)

# 降采样到15分钟,取均值
df_15min = df.resample('15T').mean()

# 如果数据有缺失,用前向填充
df_15min.fillna(method='ffill', inplace=True)

嗯,这里要注意:重采样前一定要检查时间索引是否连续。我曾经遇到过数据采集设备跳变,导致时间戳不连续,重采样后出现了大段空白。后来我养成了习惯,先做时间索引的完整性检查:

# 检查时间是否连续
time_diff = df.index.to_series().diff()
print(time_diff.value_counts().head())

4.3 特征工程:构建滞后特征

功率预测的核心规律是什么?历史会重演。今天的功率和昨天的同一时刻、前1小时、前15分钟,都有强相关性。这就是滞后特征(Lag Features)

我常用的滞后窗口:

滞后类型 窗口 说明
短期滞后 1步、2步、3步 捕捉最近时刻的惯性
周期滞后 96步(15分钟粒度,24小时) 捕捉日周期性
周周期滞后 672步(15分钟粒度,7天) 捕捉周周期性

代码实现:

# 构建滞后特征
for lag in [1, 2, 3, 96, 672]:
    df[f'lag_{lag}'] = df['power'].shift(lag)

# 删除因shift产生的NaN行
df.dropna(inplace=True)
避坑指南: 我曾经一口气构建了50个滞后特征,结果模型过拟合得一塌糊涂。后来我学乖了——先用自相关函数(ACF)分析,看看哪些滞后步长真正重要,再针对性构建。

4.4 特征工程:滚动统计特征

滞后特征只能看到「点」,但功率变化往往有「趋势」。滚动统计特征就是用来捕捉这种趋势的。

常用的滚动统计量:

  • 滚动均值:过去N个时刻的平均值,反映趋势
  • 滚动标准差:过去N个时刻的波动程度,反映稳定性
  • 滚动最大值/最小值:过去N个时刻的极值,反映边界
  • 滚动偏度/峰度:过去N个时刻的分布形态,反映异常

我一般这样构建:

# 滚动窗口大小:1小时(4个15分钟点)
window = 4

df['rolling_mean'] = df['power'].rolling(window=window).mean()
df['rolling_std'] = df['power'].rolling(window=window).std()
df['rolling_max'] = df['power'].rolling(window=window).max()
df['rolling_min'] = df['power'].rolling(window=window).min()

# 删除前window-1个NaN值
df.dropna(inplace=True)

你想想看,如果只给模型看当前功率值,它怎么知道现在是「上升趋势」还是「下降趋势」?滚动均值就能告诉它——哦,过去1小时平均功率在涨,那下一时刻大概率也涨。

注意: 滚动窗口大小需要根据预测步长来定。预测未来15分钟,窗口用1小时(4个点)就够了。预测未来1小时,窗口可能需要4小时(16个点)。窗口太小,噪声大;窗口太大,反应迟钝。

4.5 知识体系总览

说了这么多,我画了一张图帮你理清思路。这张图展示了数据预处理的完整流程,从原始数据到最终特征矩阵:

数据预处理流程总览 原始数据 秒级/分钟级功率数据 数据清洗 缺失值/异常值处理 归一化/标准化 MinMax / Z-score 时间序列重采样 降采样到15分钟 特征工程 滞后特征 + 滚动统计 特征矩阵 输入模型训练 关键要点 • 标准化比归一化更适合功率预测场景 • 重采样前必须检查时间索引连续性 • 滞后特征用ACF分析确定最优步长 • 滚动窗口大小 = 预测步长 × 4 • 先拆分训练/测试集,再归一化 • 特征不是越多越好,避免过拟合 • 删除NaN值要在所有特征构建之后 • 保存scaler参数,预测时复用

这张图把整个流程串起来了。从原始数据开始,经过清洗、归一化、重采样,再到特征工程,最后得到干净、规范、信息丰富的特征矩阵。每一步都有坑,但每一步也都有技巧。

好了,这一节的内容就到这里。数据预处理是模型效果的地基,地基打不牢,上面盖再高的楼也白搭。希望你能把这些方法真正用起来,下次跑模型的时候,你会发现——原来效果提升的秘诀,就藏在这些细节里。

专注资料整理