# 这是将Plotly图表渲染为HTML的操作。
# 更多信息,请参阅 https://quarto.org/docs/interactive/widgets/jupyter.html#plotly
import plotly.io as pio
= "plotly_mimetype+notebook_connected"
pio.renderers.default
import warnings
'ignore')
warnings.simplefilter(
import logging
'statsforecast').setLevel(logging.ERROR) logging.getLogger(
电力负荷预测
在这个例子中,我们将展示如何进行电力负荷预测,考虑一个能够处理多重季节性(MSTL)的模型。
介绍
一些时间序列是从非常低频的数据生成的。这些数据通常表现出多重季节性。例如,按小时的数据可能会在每小时(每24个观测值)或每天(每24 * 7,即每天的观测值)中展现重复的模式。这就是电力负荷的情况。电力负荷可能会随小时变化,例如,在晚上,人们可能会期待电力消费增加。但电力负荷也会随着周的变化而变化。也许在周末,会有电力活动的增加。
在这个例子中,我们将展示如何对时间序列的两种季节性进行建模,以在短时间内生成准确的预测。我们将使用每小时的PJM电力负荷数据。原始数据可以在这里找到。
库
在这个示例中,我们将使用以下库:
StatsForecast
。使用统计和计量经济模型进行Lightning ⚡️ 快速预测。包含用于多个季节性的MSTL模型。DatasetsForecast
。用于评估预测的性能。Prophet
。由Facebook开发的基准模型。NeuralProphet
。Prophet
的深度学习版本。用作基准。
%%capture
!pip install statsforecast
!pip install datasetsforecast
!pip install prophet
!pip install "neuralprophet[live]"
使用多重季节性进行预测
电力负荷数据
根据数据集页面,
PJM Interconnection LLC(PJM)是美国的一个区域传输组织(RTO)。它是东部互联电网的一部分,运营着一个电力传输系统,服务于德拉瓦州、伊利诺伊州、印第安纳州、肯塔基州、马里兰州、密歇根州、新泽西州、北卡罗来纳州、俄亥俄州、宾夕法尼亚州、田纳西州、弗吉尼亚州、西弗吉尼亚州以及哥伦比亚特区的全部或部分地区。每小时的电力消耗数据来自PJM的网站,单位为兆瓦(MW)。
让我们来看看数据。
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
pd.plotting.register_matplotlib_converters()"figure", figsize=(10, 8))
plt.rc("font", size=10) plt.rc(
= pd.read_csv('https://raw.githubusercontent.com/panambY/Hourly_Energy_Consumption/master/data/PJM_Load_hourly.csv')
df = ['ds', 'y']
df.columns 0, 'unique_id', 'PJM_Load_hourly')
df.insert('ds'] = pd.to_datetime(df['ds'])
df[= df.sort_values(['unique_id', 'ds']).reset_index(drop=True)
df df.tail()
unique_id | ds | y | |
---|---|---|---|
32891 | PJM_Load_hourly | 2001-12-31 20:00:00 | 36392.0 |
32892 | PJM_Load_hourly | 2001-12-31 21:00:00 | 35082.0 |
32893 | PJM_Load_hourly | 2001-12-31 22:00:00 | 33890.0 |
32894 | PJM_Load_hourly | 2001-12-31 23:00:00 | 32590.0 |
32895 | PJM_Load_hourly | 2002-01-01 00:00:00 | 31569.0 |
='ds', y='y') df.plot(x
我们清楚地观察到时间序列呈现季节性模式。此外,时间序列包含32,896
个观测值,因此有必要使用计算效率非常高的方法来在生产中显示它们。
MSTL模型
MSTL
(使用 LOESS 的多季节性趋势分解)模型最初由 Kasun Bandara、Rob J Hyndman 和 Christoph Bergmeir 开发,它使用局部多项式回归(LOESS)将时间序列分解为多个季节性。然后,它使用自定义的非季节性模型预测趋势,并使用 SeasonalNaive
模型预测每个季节性。
StatsForecast
包含了MSTL
模型的快速实现。同时,可以计算时间序列的分解。
from statsforecast import StatsForecast
from statsforecast.models import MSTL, AutoARIMA, SeasonalNaive
from statsforecast.utils import AirPassengers as ap
首先,我们必须定义模型参数。如前所述,电力负荷在每24小时(每小时)和每24 * 7小时(每日)内呈现季节性变化。因此,我们将使用 [24, 24 * 7]
作为MSTL模型接收的季节性。我们还必须指定预测趋势的方式。在这种情况下,我们将使用 AutoARIMA
模型。
= MSTL(
mstl =[24, 24 * 7], # 时间序列的季节性
season_length=AutoARIMA() # 用于预测趋势的模型
trend_forecaster )
一旦模型被实例化,我们需要实例化 StatsForecast
类来创建预测。
= StatsForecast(
sf =[mstl], # 用于拟合每个时间序列的模型
models='H', # 数据的频率
freq )
适配模型
之后,我们只需使用 fit
方法将每个模型适配到每个时间序列。
= sf.fit(df=df) sf
分解具有多个季节性的时间序列
一旦模型拟合完成,我们可以通过 StatsForecast
的 fitted_
属性访问分解结果。该属性存储了每个时间序列的拟合模型的所有相关信息。
在这种情况下,我们为单个时间序列拟合了一个模型,因此通过访问 fitted_
位置 [0, 0],我们将找到我们模型的相关信息。MSTL
类生成了一个 model_
属性,包含了序列分解的方式。
0, 0].model_ sf.fitted_[
data | trend | seasonal24 | seasonal168 | remainder | |
---|---|---|---|---|---|
0 | 22259.0 | 26183.898892 | -5215.124554 | 609.000432 | 681.225229 |
1 | 21244.0 | 26181.599305 | -6255.673234 | 603.823918 | 714.250011 |
2 | 20651.0 | 26179.294886 | -6905.329895 | 636.820423 | 740.214587 |
3 | 20421.0 | 26176.985472 | -7073.420118 | 615.825999 | 701.608647 |
4 | 20713.0 | 26174.670877 | -7062.395760 | 991.521912 | 609.202971 |
... | ... | ... | ... | ... | ... |
32891 | 36392.0 | 33123.552727 | 4387.149171 | -488.177882 | -630.524015 |
32892 | 35082.0 | 33148.242575 | 3479.852929 | -682.928737 | -863.166767 |
32893 | 33890.0 | 33172.926165 | 2307.808829 | -650.566775 | -940.168219 |
32894 | 32590.0 | 33197.603322 | 748.587723 | -555.177849 | -801.013195 |
32895 | 31569.0 | 33222.273902 | -967.124123 | -265.895357 | -420.254422 |
32896 rows × 5 columns
让我们从图形上来看一下时间序列的不同组成部分。
0, 0].model_.tail(24 * 28).plot(subplots=True, grid=True)
sf.fitted_[
plt.tight_layout() plt.show()
我们观察到有一个明显的趋势向上(橙色线)。这个成分将通过AutoARIMA
模型进行预测。我们还可以观察到每24小时和每24 * 7
小时都有一个非常明确的模式。这两个成分将分别使用SeasonalNaive
模型进行预测。
生成预测
要生成预测,我们只需使用 predict
方法并指定预测范围 (h
)。此外,为了计算与预测相关的预测区间,我们可以包含参数 level
,该参数接收一个我们希望构建的预测区间水平的列表。在这种情况下,我们将仅计算90%的预测区间 (level=[90]
)。
= sf.predict(h=24, level=[90])
forecasts forecasts.head()
ds | MSTL | MSTL-lo-90 | MSTL-hi-90 | |
---|---|---|---|---|
unique_id | ||||
PJM_Load_hourly | 2002-01-01 01:00:00 | 29956.744141 | 29585.187500 | 30328.298828 |
PJM_Load_hourly | 2002-01-01 02:00:00 | 29057.691406 | 28407.498047 | 29707.884766 |
PJM_Load_hourly | 2002-01-01 03:00:00 | 28654.699219 | 27767.101562 | 29542.298828 |
PJM_Load_hourly | 2002-01-01 04:00:00 | 28499.009766 | 27407.640625 | 29590.378906 |
PJM_Load_hourly | 2002-01-01 05:00:00 | 28821.716797 | 27552.236328 | 30091.197266 |
让我们通过图形化展示我们的预测。
= plt.subplots(1, 1, figsize = (20, 7))
_, ax = pd.concat([df, forecasts]).set_index('ds').tail(24 * 7)
df_plot 'y', 'MSTL']].plot(ax=ax, linewidth=2)
df_plot[[
ax.fill_between(df_plot.index, 'MSTL-lo-90'],
df_plot['MSTL-hi-90'],
df_plot[=.35,
alpha='orange',
color='MSTL-level-90')
label'PJM Load Hourly', fontsize=22)
ax.set_title('Electricity Load', fontsize=20)
ax.set_ylabel('Timestamp [t]', fontsize=20)
ax.set_xlabel(={'size': 15})
ax.legend(prop ax.grid()
在下一节中,我们将绘制不同的模型,因此重用之前的代码是很方便的,使用以下函数。
def plot_forecasts(y_hist, y_true, y_pred, models):
= plt.subplots(1, 1, figsize = (20, 7))
_, ax = y_true.merge(y_pred, how='left', on=['unique_id', 'ds'])
y_true = pd.concat([y_hist, y_true]).set_index('ds').tail(24 * 7)
df_plot 'y'] + models].plot(ax=ax, linewidth=2)
df_plot[[= ['orange', 'green', 'red']
colors for model, color in zip(models, colors):
ax.fill_between(df_plot.index, f'{model}-lo-90'],
df_plot[f'{model}-hi-90'],
df_plot[=.35,
alpha=color,
color=f'{model}-level-90')
label'PJM Load Hourly', fontsize=22)
ax.set_title('Electricity Load', fontsize=20)
ax.set_ylabel('Timestamp [t]', fontsize=20)
ax.set_xlabel(={'size': 15})
ax.legend(prop ax.grid()
MSTL模型的表现
拆分训练/测试集
为了验证 MSTL
模型的准确性,我们将展示其在未见数据上的表现。我们将使用一种经典的时间序列技术,将数据分为训练集和测试集。我们将最后 24 个观测值(最后一天)作为测试集。因此,模型将基于 32,872
个观测值进行训练。
= df.tail(24)
df_test = df.drop(df_test.index) df_train
MSTL模型
除了 MSTL
模型,我们还将包括 SeasonalNaive
模型作为基准,以验证 MSTL
模型的附加价值。将 StatsForecast
模型纳入其中只需将其添加到待拟合模型的列表中。
= StatsForecast(
sf =[mstl, SeasonalNaive(season_length=24)], # 将季节性朴素模型添加到列表中
models='H'
freq )
为了测量拟合时间,我们将使用time
模块。
from time import time
要获取测试集的预测结果,我们只需像之前一样进行拟合和预测。
= time()
init = sf.fit(df=df_train)
sf = sf.predict(h=len(df_test), level=[90])
forecasts_test = time()
end forecasts_test.head()
ds | MSTL | MSTL-lo-90 | MSTL-hi-90 | SeasonalNaive | SeasonalNaive-lo-90 | SeasonalNaive-hi-90 | |
---|---|---|---|---|---|---|---|
unique_id | |||||||
PJM_Load_hourly | 2001-12-31 01:00:00 | 28345.212891 | 27973.572266 | 28716.853516 | 28326.0 | 23468.693359 | 33183.304688 |
PJM_Load_hourly | 2001-12-31 02:00:00 | 27567.455078 | 26917.085938 | 28217.824219 | 27362.0 | 22504.693359 | 32219.306641 |
PJM_Load_hourly | 2001-12-31 03:00:00 | 27260.001953 | 26372.138672 | 28147.865234 | 27108.0 | 22250.693359 | 31965.306641 |
PJM_Load_hourly | 2001-12-31 04:00:00 | 27328.125000 | 26236.410156 | 28419.839844 | 26865.0 | 22007.693359 | 31722.306641 |
PJM_Load_hourly | 2001-12-31 05:00:00 | 27640.673828 | 26370.773438 | 28910.572266 | 26808.0 | 21950.693359 | 31665.306641 |
= (end - init) / 60
time_mstl print(f'MSTL Time: {time_mstl:.2f} minutes')
MSTL Time: 0.22 minutes
然后我们能够生成接下来的24小时的预测。现在让我们来看一下预测值与实际值的图形比较。
=['MSTL', 'SeasonalNaive']) plot_forecasts(df_train, df_test, forecasts_test, models
我们来看看那些仅由 MSTL
生成的内容。
=['MSTL']) plot_forecasts(df_train, df_test, forecasts_test, models
我们注意到 MSTL
产生的预测非常准确,能够跟随时间序列的行为。现在让我们数值计算模型的准确性。我们将使用以下指标:MAE
、MAPE
、MASE
、RMSE
、SMAPE
。
from datasetsforecast.losses import (
mae, mape, mase, rmse, smape )
def evaluate_performace(y_hist, y_true, y_pred, models):
= y_true.merge(y_pred, how='left', on=['unique_id', 'ds'])
y_true = {}
evaluation for model in models:
= {}
evaluation[model] for metric in [mase, mae, mape, rmse, smape]:
= metric.__name__
metric_name if metric_name == 'mase':
= metric(y_true['y'].values,
evaluation[model][metric_name]
y_true[model].values, 'y'].values, seasonality=24)
y_hist[else:
= metric(y_true['y'].values, y_true[model].values)
evaluation[model][metric_name] return pd.DataFrame(evaluation).T
=['MSTL', 'SeasonalNaive']) evaluate_performace(df_train, df_test, forecasts_test, models
mase | mae | mape | rmse | smape | |
---|---|---|---|---|---|
MSTL | 0.341926 | 709.932048 | 2.182804 | 892.888012 | 2.162832 |
SeasonalNaive | 0.894653 | 1857.541667 | 5.648190 | 2201.384101 | 5.868604 |
我们观察到,在测试集上,MSTL
相较于 SeasonalNaive
方法在 MASE
的度量上有约 60% 的改善。
与Prophet的比较
最常用的时间序列预测模型之一是 Prophet
。该模型以其能够建模不同季节性(周季节性、日季节性和年季节性)而闻名。我们将使用此模型作为基准,以查看 MSTL
是否为此时间序列增值。
from prophet import Prophet
# 创建先知模型
= Prophet(interval_width=0.9)
prophet = time()
init
prophet.fit(df_train)# 生成预测
= prophet.make_future_dataframe(periods=len(df_test), freq='H', include_history=False)
future = prophet.predict(future)
forecast_prophet = time()
end # 数据整理
= forecast_prophet[['ds', 'yhat', 'yhat_lower', 'yhat_upper']]
forecast_prophet = ['ds', 'Prophet', 'Prophet-lo-90', 'Prophet-hi-90']
forecast_prophet.columns 0, 'unique_id', 'PJM_Load_hourly')
forecast_prophet.insert( forecast_prophet.head()
23:41:40 - cmdstanpy - INFO - Chain [1] start processing
23:41:56 - cmdstanpy - INFO - Chain [1] done processing
unique_id | ds | Prophet | Prophet-lo-90 | Prophet-hi-90 | |
---|---|---|---|---|---|
0 | PJM_Load_hourly | 2001-12-31 01:00:00 | 25317.658386 | 20757.919539 | 30313.561582 |
1 | PJM_Load_hourly | 2001-12-31 02:00:00 | 24024.188077 | 19304.093939 | 28667.495805 |
2 | PJM_Load_hourly | 2001-12-31 03:00:00 | 23348.306824 | 18608.982825 | 28497.334752 |
3 | PJM_Load_hourly | 2001-12-31 04:00:00 | 23356.150113 | 18721.142270 | 28136.888630 |
4 | PJM_Load_hourly | 2001-12-31 05:00:00 | 24130.861217 | 19896.188455 | 28970.202276 |
= (end - init) / 60
time_prophet print(f'Prophet Time: {time_prophet:.2f} minutes')
Prophet Time: 0.30 minutes
= pd.DataFrame({'model': ['MSTL', 'Prophet'], 'time (mins)': [time_mstl, time_prophet]})
times times
model | time (mins) | |
---|---|---|
0 | MSTL | 0.217266 |
1 | Prophet | 0.301172 |
我们观察到,Prophet
执行拟合和预测流程所需的时间大于 MSTL
。让我们来看看 Prophet
生成的预测结果。
= forecasts_test.merge(forecast_prophet, how='left', on=['unique_id', 'ds']) forecasts_test
=['MSTL', 'SeasonalNaive', 'Prophet']) plot_forecasts(df_train, df_test, forecasts_test, models
我们注意到 Prophet
能够捕捉时间序列的整体行为。然而,在某些情况下,它的预测值远低于实际值。它也无法正确调整谷底。
=['MSTL', 'Prophet', 'SeasonalNaive']) evaluate_performace(df_train, df_test, forecasts_test, models
mase | mae | mape | rmse | smape | |
---|---|---|---|---|---|
MSTL | 0.341926 | 709.932048 | 2.182804 | 892.888012 | 2.162832 |
Prophet | 1.094768 | 2273.036373 | 7.343292 | 2709.400341 | 7.688665 |
SeasonalNaive | 0.894653 | 1857.541667 | 5.648190 | 2201.384101 | 5.868604 |
就准确性而言,Prophet
无法产生比SeasonalNaive
模型更好的预测,然而,MSTL
模型使得Prophet
的预测提高了69%(MASE
)。
与NeuralProphet的比较
NeuralProphet
是使用深度学习的 Prophet
版本。该模型还能够处理不同的季节性,因此我们也将其用作基准。
from neuralprophet import NeuralProphet
= NeuralProphet(quantiles=[0.05, 0.95])
neuralprophet = time()
init ='unique_id'))
neuralprophet.fit(df_train.drop(columns= neuralprophet.make_future_dataframe(df=df_train.drop(columns='unique_id'), periods=len(df_test))
future = neuralprophet.predict(future)
forecast_np = time()
end = forecast_np[['ds', 'yhat1', 'yhat1 5.0%', 'yhat1 95.0%']]
forecast_np = ['ds', 'NeuralProphet', 'NeuralProphet-lo-90', 'NeuralProphet-hi-90']
forecast_np.columns 0, 'unique_id', 'PJM_Load_hourly')
forecast_np.insert( forecast_np.head()
WARNING - (NP.forecaster.fit) - When Global modeling with local normalization, metrics are displayed in normalized scale.
INFO - (NP.df_utils._infer_frequency) - Major frequency H corresponds to 99.973% of the data.
INFO - (NP.df_utils._infer_frequency) - Dataframe freq automatically defined as H
INFO - (NP.config.init_data_params) - Setting normalization to global as only one dataframe provided for training.
INFO - (NP.config.set_auto_batch_epoch) - Auto-set batch_size to 64
INFO - (NP.config.set_auto_batch_epoch) - Auto-set epochs to 76
INFO - (NP.df_utils._infer_frequency) - Major frequency H corresponds to 99.973% of the data.
INFO - (NP.df_utils._infer_frequency) - Defined frequency is equal to major frequency - H
INFO - (NP.df_utils.return_df_in_original_format) - Returning df with no ID column
INFO - (NP.df_utils._infer_frequency) - Major frequency H corresponds to 95.833% of the data.
INFO - (NP.df_utils._infer_frequency) - Defined frequency is equal to major frequency - H
INFO - (NP.df_utils._infer_frequency) - Major frequency H corresponds to 95.833% of the data.
INFO - (NP.df_utils._infer_frequency) - Defined frequency is equal to major frequency - H
INFO - (NP.df_utils.return_df_in_original_format) - Returning df with no ID column
unique_id | ds | NeuralProphet | NeuralProphet-lo-90 | NeuralProphet-hi-90 | |
---|---|---|---|---|---|
0 | PJM_Load_hourly | 2001-12-31 01:00:00 | 25019.892578 | 22296.675781 | 27408.724609 |
1 | PJM_Load_hourly | 2001-12-31 02:00:00 | 24128.816406 | 21439.851562 | 26551.615234 |
2 | PJM_Load_hourly | 2001-12-31 03:00:00 | 23736.679688 | 20961.978516 | 26289.349609 |
3 | PJM_Load_hourly | 2001-12-31 04:00:00 | 23476.744141 | 20731.619141 | 26050.443359 |
4 | PJM_Load_hourly | 2001-12-31 05:00:00 | 23899.162109 | 21217.503906 | 26449.603516 |
= (end - init) / 60
time_np print(f'Prophet Time: {time_np:.2f} minutes')
Prophet Time: 2.95 minutes
= times.append({'model': 'NeuralProphet', 'time (mins)': time_np}, ignore_index=True)
times times
model | time (mins) | |
---|---|---|
0 | MSTL | 0.217266 |
1 | Prophet | 0.301172 |
2 | NeuralProphet | 2.946358 |
我们观察到NeuralProphet
所需的处理时间比Prophet
和MSTL
更长。
= forecasts_test.merge(forecast_np, how='left', on=['unique_id', 'ds']) forecasts_test
=['MSTL', 'NeuralProphet', 'Prophet']) plot_forecasts(df_train, df_test, forecasts_test, models
预测图表显示,NeuralProphet
生成的结果与 Prophet
非常相似,正如预期的那样。
=['MSTL', 'NeuralProphet', 'Prophet', 'SeasonalNaive']) evaluate_performace(df_train, df_test, forecasts_test, models
mase | mae | mape | rmse | smape | |
---|---|---|---|---|---|
MSTL | 0.341926 | 709.932048 | 2.182804 | 892.888012 | 2.162832 |
NeuralProphet | 1.084915 | 2252.578613 | 7.280202 | 2671.145730 | 7.615492 |
Prophet | 1.094768 | 2273.036373 | 7.343292 | 2709.400341 | 7.688665 |
SeasonalNaive | 0.894653 | 1857.541667 | 5.648190 | 2201.384101 | 5.868604 |
在数值评估方面,NeuralProphet
的结果相对于 Prophet
有所提升,这在预期之中。然而,MSTL
的预测结果相比于 NeuralProphet
提升了68%(MASE
)。
NeuralProphet
的性能可以通过超参数优化来提高,这会显著增加拟合时间。在这个例子中,我们展示了默认版本的性能。
结论
在这篇文章中,我们介绍了 MSTL
,这是一个由 Kasun Bandara, Rob Hyndman 和 Christoph Bergmeir 原创开发的模型,能够处理具有多重季节性的时间序列。我们还展示了对于PJM电力负荷时间序列,相比于 Prophet
和 NeuralProphet
模型,MSTL
在时间和准确率上提供了更好的性能。
参考文献
Give us a ⭐ on Github