第三章 时间序列数据处理:光伏/负荷数据的获取、清洗、归一化与特征工程

各位同学,欢迎来到第三章。前两章我们聊了储能调度的整体架构和算法选型,算是把“地图”和“武器”都认了一遍。从这章开始,咱们要真正动手了。

做调度算法,最怕什么?不是算法不够先进,而是数据一塌糊涂。我见过太多项目,算法模型调得漂漂亮亮,一上真实数据就崩盘。为什么?因为数据管道没建好。说白了,数据是算法的“粮食”,粮食有问题,再好的厨子也白搭。

这一章,我们就来搭建这条数据管道。从数据获取开始,到清洗、归一化,再到特征工程,一步步把原始数据变成算法能吃的“营养餐”。

3.1 数据获取:从哪里来?

光伏和负荷数据,来源其实挺多的。我个人习惯把它们分成三类:

  • 实测数据:从电站的SCADA系统、智能电表、逆变器直接采集。这是最真实的数据,但往往有缺失、有噪声。
  • 公开数据集:比如NREL的PVDAQ、澳大利亚的Ausgrid数据集。适合做研究、做原型验证。
  • 模拟数据:用PVsyst、HOMER等工具生成。适合做极端场景测试,但别太当真。

我在项目中遇到过最头疼的情况:客户给的数据是Excel手动记录的,时间戳格式五花八门,有的带时区,有的不带,有的甚至写的是“上午10点”。嗯,这种数据,清洗起来比写算法还累。

我的建议:无论数据从哪来,第一步先统一成标准格式。我个人习惯用UTC时间戳,精度到秒。这样后续处理会省很多麻烦。

3.2 数据清洗:把垃圾倒掉

数据拿到手,别急着分析。先看看有没有“脏数据”。常见的脏数据有这几类:

问题类型 典型表现 处理方法
缺失值 某段时间数据为空 插值、前向填充、删除
异常值 光伏功率突然飙到200% 3σ原则、IQR方法
重复值 同一时间戳出现多条记录 去重,保留最新或均值
时间戳错乱 时间顺序颠倒、跳跃 排序、重采样

举个例子,光伏数据在夜间应该是0,但有时候传感器会飘出几瓦的噪声。这种要不要处理?看情况。如果你做的是日发电量统计,那点噪声无所谓。但如果你做的是秒级调度,那必须滤掉。

避坑指南:我曾经在处理一个大型储能项目时,忽略了某台逆变器的数据偏移问题。结果模型训练出来,调度策略在下午3点总是异常。查了两天才发现,那台逆变器的时钟慢了4分钟。所以,时间同步检查一定要做。

下面是一段简单的清洗代码,用Python的pandas库实现:

import pandas as pd
import numpy as np

# 读取数据
df = pd.read_csv('pv_data.csv', parse_dates=['timestamp'])
df.set_index('timestamp', inplace=True)

# 1. 去重
df = df[~df.index.duplicated(keep='first')]

# 2. 排序
df.sort_index(inplace=True)

# 3. 处理缺失值(线性插值)
df['power_kw'] = df['power_kw'].interpolate(method='linear')

# 4. 处理异常值(3σ原则)
mean = df['power_kw'].mean()
std = df['power_kw'].std()
df = df[(df['power_kw'] >= mean - 3*std) & (df['power_kw'] <= mean + 3*std)]

# 5. 重采样到15分钟间隔
df_resampled = df.resample('15T').mean()

3.3 归一化:让数据“公平”竞争

归一化,说白了就是把不同量纲的数据拉到同一个尺度上。光伏功率可能是0-1000kW,负荷可能是0-500kW,温度可能是-10到40度。如果不归一化,算法会天然偏向数值大的特征。

常用的归一化方法有两种:

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

我个人习惯:对于光伏和负荷数据,优先用Min-Max。因为调度算法往往需要输出具体的功率值,Min-Max方便反归一化。但如果你发现数据里有明显的尖峰,那就用Z-score。

注意:归一化的参数(min、max、mean、std)一定要用训练集计算,然后应用到验证集和测试集上。千万别用全局数据算,否则会造成数据泄露。
from sklearn.preprocessing import MinMaxScaler

# 假设df已经清洗完毕,包含'pv_power'和'load_power'两列
scaler = MinMaxScaler()
df[['pv_power', 'load_power']] = scaler.fit_transform(df[['pv_power', 'load_power']])

# 保存scaler,后续反归一化用
import joblib
joblib.dump(scaler, 'scaler.pkl')

3.4 特征工程:从数据中“挖”出信息

特征工程,是数据管道里最考验经验的一步。你想想看,原始数据只有功率值和时间戳,但调度算法需要知道“现在是白天还是晚上”、“今天是工作日还是周末”、“未来两小时会不会下雨”。这些信息,都得靠特征工程来提取。

我常用的特征可以分为三类:

3.4.1 时间特征

  • 小时、星期几、月份、季节
  • 是否工作日、是否节假日
  • 距离日出/日落的时间(对光伏特别重要)

3.4.2 统计特征

  • 过去1小时、6小时、24小时的滑动均值
  • 过去1小时的方差(反映波动性)
  • 当前值与历史同期值的差异

3.4.3 外部特征

  • 天气预报数据(温度、辐照度、云量)
  • 电价信号(如果做的是经济调度)
  • 设备状态(逆变器是否在线、电池SOC)

举个例子,光伏功率的预测,光看历史功率是不够的。你加上“当前时刻的理论辐照度”这个特征,预测精度能提升一大截。这个理论辐照度可以用天文算法算出来,不需要传感器。

# 生成时间特征示例
df['hour'] = df.index.hour
df['day_of_week'] = df.index.dayofweek
df['is_weekend'] = df['day_of_week'].isin([5, 6]).astype(int)
df['month'] = df.index.month

# 滑动统计特征
df['pv_rolling_mean_1h'] = df['pv_power'].rolling(window=4).mean()  # 15分钟粒度,4个点=1小时
df['pv_rolling_std_1h'] = df['pv_power'].rolling(window=4).std()

# 理论辐照度(简化版,实际需要经纬度和时间)
# 这里假设一个简单的正弦曲线模拟
import numpy as np
df['solar_angle'] = np.sin(2 * np.pi * (df['hour'] - 6) / 24)
df['solar_angle'] = df['solar_angle'].clip(0)  # 夜间为0

3.5 数据管道:把一切串起来

好了,数据获取、清洗、归一化、特征工程都讲完了。但实际项目中,这些步骤不是一次性的。你需要一个自动化的数据管道,每天定时跑,把新数据灌进来,产出特征,喂给模型。

下面这张图,是我常用的数据管道架构:

数据源 数据清洗 归一化 特征工程 DB 调度算法模型 反馈优化 数据管道架构图

这张图里,数据从源端进来,经过清洗、归一化、特征工程,存入数据库。调度算法从数据库里读特征,做出决策。决策结果反馈回来,用于优化特征工程和模型参数。这是一个闭环。

我的经验:数据管道一定要做监控。我习惯在每个环节都打日志,记录数据量、缺失率、异常值比例。一旦某个指标超出阈值,立刻报警。这样能第一时间发现问题,而不是等到模型效果变差才去排查。

3.6 本章小结

这一章我们走完了数据管道的全流程。从数据获取的三种来源,到清洗的五大步骤,再到归一化的两种方法,最后是特征工程的三大类别。每一步都有坑,每一步也都有技巧。

记住一句话:数据管道的质量,决定了算法效果的上限。模型再先进,也救不了垃圾数据。所以,花时间把数据管道搭扎实,绝对是值得的。

下一章,我们会把这些处理好的数据,真正喂进调度算法里。到时候你就知道,前面这些功夫,一点都不会白费。


专注资料整理