第三章:数据获取与探索——打好电价预测的第一口井
各位同学,欢迎来到第三章。前面我们聊了电价预测为什么难做,也搭建了基本的技术框架。今天咱们要干一件最接地气、但也最容易翻车的事——把数据搞到手,并且搞清楚它到底长什么样。
我个人习惯把数据获取比作「打井」。你想想看,井打歪了,后面再牛的算法也白搭。我在项目里见过太多人,模型调得飞起,最后发现是数据源出了问题——嗯,那种感觉,就像你跑完马拉松发现跑错了赛道。
3.1 数据来源:两大主流市场
电价预测领域,有两个数据源是绕不开的。一个是美国的PJM,一个是欧洲的Nord Pool。我建议初学者先从PJM入手,因为它的数据接口最友好,文档也最全。
3.1.1 PJM(宾夕法尼亚-新泽西-马里兰联合电力市场)
PJM覆盖美国13个州,是全世界最大的竞争性电力市场之一。它的数据公开、免费、实时更新,简直是做研究的宝库。
| 数据类别 | 说明 | 获取方式 |
|---|---|---|
| 实时电价(LMP) | 每5分钟一个点,分节点、区域、枢纽 | PJM API / 数据矿 |
| 日前电价(DALMP) | 提前一天发布的预测电价 | PJM Data Miner 2 |
| 负荷数据 | 实际负荷与预测负荷 | PJM OASIS |
| 发电组合 | 煤、气、风、光等出力情况 | PJM Generation |
3.1.2 Nord Pool(北欧电力市场)
Nord Pool覆盖北欧四国(挪威、瑞典、芬兰、丹麦),它的特点是水电占比高,电价受天气影响极大。如果你要做中长期预测,Nord Pool的数据更有挑战性。
Nord Pool的数据获取稍微麻烦一点,需要注册账号。但它的系统电价(System Price)和区域电价(Area Price)是很好的研究素材。
3.2 数据字段解读:别被字段名骗了
拿到数据后,第一件事不是跑模型,而是搞清楚每个字段到底代表什么。我见过有人把「预测负荷」当「实际负荷」用,结果模型精度惨不忍睹。
以PJM的实时电价数据为例,常见的字段有:
- datetime:时间戳,注意时区!PJM用的是东部时间(EST/EDT),夏令时要注意
- node_id:节点编号,不同节点电价差异很大
- lmp:节点边际电价(Locational Marginal Price),单位是$/MWh
- congestion:阻塞成本,反映输电线路拥堵情况
- loss:网损成本
- system_energy:系统能量价格
3.3 数据质量检查:脏数据是模型的天敌
数据拿到手了,字段也看懂了。接下来就是最枯燥但最重要的一步——数据质量检查。我总结了一个「三步检查法」:
第一步:缺失值检查
电力数据最常见的缺失原因有两个:通信中断和系统维护。直接删掉缺失行?别急,先看看缺失模式。
import pandas as pd
import numpy as np
# 加载数据
df = pd.read_csv('pjm_lmp_2024.csv', parse_dates=['datetime'])
# 检查缺失率
missing_rate = df.isnull().mean()
print('各字段缺失率:')
print(missing_rate)
# 看看缺失的时间分布
df['date'] = df['datetime'].dt.date
missing_by_date = df.groupby('date')['lmp'].apply(lambda x: x.isnull().mean())
print('\n每日缺失率(前10天):')
print(missing_by_date.head(10))
如果缺失率低于5%,而且分布随机,我一般用前向填充(ffill)处理。但如果某一天缺失率超过20%,那就要警惕了——可能是那天数据源出了问题。
第二步:异常值检测
电价数据里经常出现「尖刺」——比如正常50 $/MWh,突然蹦出个5000 $/MWh。这可能是真实的市场事件(比如极端天气),也可能是数据错误。
# 用IQR方法检测异常值
Q1 = df['lmp'].quantile(0.25)
Q3 = df['lmp'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 3 * IQR
upper_bound = Q3 + 3 * IQR
outliers = df[(df['lmp'] < lower_bound) | (df['lmp'] > upper_bound)]
print(f'检测到 {len(outliers)} 个异常值')
print(outliers[['datetime', 'lmp']].head())
第三步:时间连续性检查
电力数据必须是等间隔的。PJM的实时数据是5分钟一个点,如果中间有跳点,模型会出问题。
# 检查时间间隔是否均匀
df = df.sort_values('datetime')
time_diff = df['datetime'].diff().dropna()
# 统计间隔分布
print('时间间隔统计(分钟):')
print(time_diff.dt.total_seconds().div(60).describe())
# 找出间隔异常的点
abnormal_gaps = time_diff[time_diff.dt.total_seconds() > 360] # 超过6分钟
if len(abnormal_gaps) > 0:
print(f'\n发现 {len(abnormal_gaps)} 个异常间隔')
print(abnormal_gaps.head())
嗯,这里要注意:如果发现间隔不均匀,别急着插值。先搞清楚原因——是数据源本身就不连续,还是我拉数据的时候漏了。我曾经因为API限流,导致数据中间缺了2个小时,后来重跑了一遍才补上。
3.4 知识体系总览
说了这么多,咱们用一张图把本章的核心逻辑串起来:
这张图把咱们今天讲的内容串起来了。从数据源出发,经过字段解读,再到质量检查,最终得到干净可用的数据。每一步都马虎不得。
公众号:蓝海数据掘金营,微信deep3321