第二章:时间序列基础与可视化
各位好,我是老张。在电力系统干了十几年,每天打交道最多的就是负荷数据。说实话,刚入行那会儿,面对一堆时间戳和数字,我头都是大的。后来慢慢摸出门道——时间序列处理,说白了就是跟「时间」这个维度谈恋爱,你得懂它的脾气。
这一章,我们聊聊Pandas的时间序列索引、重采样、滚动统计,还有怎么用Matplotlib和Plotly把负荷曲线画得漂漂亮亮。嗯,这些都是基本功,但也是后面所有预测模型的基石。
核心知识点速览:
- Pandas时间序列索引:让数据带上时间标签
- Resample重采样:把数据从高频变低频,或反过来
- 滚动统计:滑动窗口里的平均值、最大值
- Matplotlib/Plotly可视化:静态图与交互图的取舍
- 季节性分解:把负荷拆成趋势、季节、残差三部分
2.1 Pandas时间序列索引:给数据贴上时间标签
我个人习惯,拿到负荷数据第一件事,就是把时间列转成Pandas的DatetimeIndex。为什么?因为只有索引是时间类型,你才能用切片、重采样这些神器。
import pandas as pd
import numpy as np
# 模拟负荷数据:2024年1月,每15分钟一个点
date_rng = pd.date_range(start='2024-01-01', end='2024-01-31 23:45:00', freq='15T')
load_data = np.random.normal(loc=500, scale=50, size=len(date_rng)) + 100*np.sin(np.arange(len(date_rng))*2*np.pi/(96*7))
df = pd.DataFrame(load_data, index=date_rng, columns=['load'])
print(df.head())
你看,索引变成了DatetimeIndex,这时候想取某一天的数据,直接写 df['2024-01-15'] 就行。我在项目中遇到过,有人用字符串匹配去筛选时间,效率低不说,还容易出错。用时间索引切片,又快又准。
小技巧: 如果你的原始数据时间列是字符串,用 pd.to_datetime() 转换,然后 set_index() 设置索引。我一般这么写:df.index = pd.to_datetime(df['time_column'])
2.2 Resample重采样:数据频率的魔术师
实际工作中,负荷数据来源五花八门。SCADA系统可能是15分钟一个点,电表可能是每小时一个点,气象数据可能是每天一个点。你想把它们放一起分析?必须统一频率。
重采样分两种:降采样和升采样。
| 操作 | 说明 | 常用方法 | 示例 |
|---|---|---|---|
| 降采样 | 高频→低频(如15分钟→1小时) | mean(), sum(), max() | df.resample('1H').mean() |
| 升采样 | 低频→高频(如1小时→15分钟) | ffill(), bfill(), interpolate() | df.resample('15T').ffill() |
# 降采样:15分钟数据聚合成小时级
hourly_load = df.resample('1H').mean()
print(hourly_load.head())
# 升采样:小时级数据插值到15分钟
# 先模拟小时数据
hourly_data = df.resample('1H').mean()
# 再升采样
ffill_data = hourly_data.resample('15T').ffill() # 向前填充
interp_data = hourly_data.resample('15T').interpolate() # 线性插值
嗯,这里要注意:降采样时,聚合函数的选择很关键。算平均负荷用mean,算峰值负荷用max,算总用电量用sum。我曾经犯过一个错,用mean去算日最大负荷,结果偏小很多,后来被领导骂了一顿。
避坑指南: 升采样时,ffill(向前填充)会引入滞后,interpolate(线性插值)更平滑但可能产生不合理值。我个人建议,如果数据变化平缓用interpolate,如果数据有突变(比如负荷跳变)用ffill更安全。
2.3 滚动统计:滑动窗口里的秘密
滚动统计,说白了就是在一个滑动窗口里算平均值、最大值、标准差。这玩意儿在负荷预测里太常用了——比如用过去7天的平均负荷来预测今天的负荷。
# 7天滚动平均(每天96个15分钟点,7天=672个点)
df['load_7d_avg'] = df['load'].rolling(window=672).mean()
df['load_7d_std'] = df['load'].rolling(window=672).std()
# 查看结果
print(df[['load', 'load_7d_avg', 'load_7d_std']].head(700))
你想想看,滚动窗口的大小怎么选?太小了噪声大,太大了反应迟钝。我一般先画个图看看,选一个能让曲线既平滑又不失主要特征的窗口。比如负荷数据,7天窗口能消除日周期性,30天窗口能消除周周期性。
经验之谈: 滚动统计的边界处理要注意。默认情况下,前N-1个数据点是NaN。可以用 min_periods=1 让前面也有值,但精度会差一些。我在做实时预测时,通常用 min_periods=int(window*0.7),既保证数据量,又不让起始段空太多。
2.4 Matplotlib/Plotly绘制负荷曲线
画图是检验数据质量的照妖镜。我拿到新数据,第一件事就是画个全貌图。Matplotlib适合做静态报告,Plotly适合做交互式探索。
2.4.1 Matplotlib:经典静态图
import matplotlib.pyplot as plt
plt.figure(figsize=(12, 6))
plt.plot(df.index[:1000], df['load'][:1000], label='原始负荷', color='#3498db', linewidth=0.8)
plt.plot(df.index[:1000], df['load_7d_avg'][:1000], label='7天滚动平均', color='#e74c3c', linewidth=1.5)
plt.title('负荷曲线与滚动平均(前1000个点)', fontsize=14)
plt.xlabel('时间')
plt.ylabel('负荷 (MW)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
Matplotlib的好处是出图稳定,适合写报告。但缺点是不能交互,想看某个点的具体数值得自己猜。
2.4.2 Plotly:交互式探索
import plotly.graph_objects as go
fig = go.Figure()
fig.add_trace(go.Scatter(x=df.index[:1000], y=df['load'][:1000],
mode='lines', name='原始负荷', line=dict(color='#3498db', width=1)))
fig.add_trace(go.Scatter(x=df.index[:1000], y=df['load_7d_avg'][:1000],
mode='lines', name='7天滚动平均', line=dict(color='#e74c3c', width=2)))
fig.update_layout(title='负荷曲线交互图', xaxis_title='时间', yaxis_title='负荷 (MW)')
fig.show()
Plotly的交互功能太强了——鼠标悬停显示数值、缩放、平移、下载图片。我在做项目汇报时,经常用Plotly生成HTML文件,领导可以自己拖拽看细节。
我的习惯: 探索阶段用Plotly,出报告用Matplotlib。Plotly的图可以保存为HTML,直接发给客户,他们打开就能交互,不用装Python。
2.5 季节性分解:把负荷拆开看
负荷数据有三个成分:趋势(长期变化)、季节性(日/周/年周期)、残差(随机波动)。季节性分解就是把这仨拆开,方便我们分别建模。
from statsmodels.tsa.seasonal import seasonal_decompose
# 先降采样到小时级,减少计算量
hourly = df['load'].resample('1H').mean()
# 填充缺失值
hourly = hourly.fillna(method='ffill')
# 季节性分解:周期设为24小时(一天)
result = seasonal_decompose(hourly, model='additive', period=24)
# 提取成分
trend = result.trend
seasonal = result.seasonal
residual = result.resid
# 画图
plt.figure(figsize=(12, 10))
plt.subplot(4, 1, 1)
plt.plot(hourly.index, hourly, label='原始', color='#3498db')
plt.legend()
plt.subplot(4, 1, 2)
plt.plot(trend.index, trend, label='趋势', color='#e67e22')
plt.legend()
plt.subplot(4, 1, 3)
plt.plot(seasonal.index, seasonal, label='季节性', color='#2ecc71')
plt.legend()
plt.subplot(4, 1, 4)
plt.plot(residual.index, residual, label='残差', color='#e74c3c')
plt.legend()
plt.tight_layout()
plt.show()
为什么用加法模型?因为负荷的季节性幅度基本不随趋势变化。如果季节性幅度随趋势变大(比如夏天用电高峰时波动也大),那就用乘法模型。我一般先试加法,如果残差有明显模式再换乘法。
注意: 季节性分解要求数据没有缺失值。如果数据有空洞,先用ffill或interpolate补上。另外,周期参数要设对——日周期是24(小时级),周周期是168(24*7)。设错了,分解出来的季节性就是一团乱麻。
好了,这一章的内容就这些。时间序列索引是基础,重采样是工具,滚动统计是平滑利器,可视化是检验手段,季节性分解是深入理解数据的钥匙。把这些玩熟了,后面做预测模型就顺手多了。
公众号:蓝海资料掘金营,微信deep3321