In this example we will show how to perform electricity load forecasting using MLForecast alongside many models. We also compare them against the prophet library.
mlforecast
. Accurate and
⚡️ fast forecasting withc lassical machine learning models.prophet
. Benchmark model
developed by Facebook.utilsforecast
. Library
with different functions for forecasting evaluation.PJM Interconnection LLC (PJM) is a regional transmission organization (RTO) in the United States. It is part of the Eastern Interconnection grid operating an electric transmission system serving all or parts of Delaware, Illinois, Indiana, Kentucky, Maryland, Michigan, New Jersey, North Carolina, Ohio, Pennsylvania, Tennessee, Virginia, West Virginia, and the District of Columbia. The hourly power consumption data comes from PJM’s website and are in megawatts (MW).Let’s take a look to the data.
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 |
32,896
observations, so it is
necessary to use very computationally efficient methods to display them
in production.
We are going to split our series in order to create a train and test
set. The model will be tested using the last 24 hours of the timeseries.
[24, 24 * 7]
as the seasonalities for the model. In order to analize
how they affect our series we are going to use the Difference
method.
MLForecast.preprocess
method to explore different
transformations. It looks like these series have a strong seasonality on
the hour of the day, so we can subtract the value from the same hour in
the previous day to remove it. This can be done with the
mlforecast.target_transforms.Differences
transformer, which we pass
through target_transforms
.
In order to analize the trends individually and combined we are going to
plot them individually and combined. Therefore, we can compare them
against the original series. We can use the next function for that.
24
hours (daily) and 24*7
(weekly) we are going to substract them from the serie using
Differences([24, 24*7])
and plot them.
PJM_Load_hourly_24
the series seem to stabilize sisnce the peaks seem
more uniform in comparison with the original series PJM_Load_hourly
.
When we extrac the 24*7 (weekly) PJM_Load_hourly_168
difference we
can see there is more periodicity in the peaks in comparison with the
original series.
Finally we can see the result from the combined result from substracting
all the differences PJM_Load_hourly_all_diff
.
For modeling we are going to use both difference for the forecasting,
therefore we are setting the argument target_transforms
from the
MLForecast
object equal to [Differences([24, 24*7])]
, if we wanted
to include a yearly difference we would need to add the term 24*365
.
unique_id | ds | y | |
---|---|---|---|
192 | PJM_Load_hourly | 1998-04-09 02:00:00 | 831.0 |
193 | PJM_Load_hourly | 1998-04-09 03:00:00 | 918.0 |
194 | PJM_Load_hourly | 1998-04-09 04:00:00 | 760.0 |
195 | PJM_Load_hourly | 1998-04-09 05:00:00 | 849.0 |
196 | PJM_Load_hourly | 1998-04-09 06:00:00 | 710.0 |
… | … | … | … |
32867 | PJM_Load_hourly | 2001-12-30 20:00:00 | 3417.0 |
32868 | PJM_Load_hourly | 2001-12-30 21:00:00 | 3596.0 |
32869 | PJM_Load_hourly | 2001-12-30 22:00:00 | 3501.0 |
32870 | PJM_Load_hourly | 2001-12-30 23:00:00 | 3939.0 |
32871 | PJM_Load_hourly | 2001-12-31 00:00:00 | 4235.0 |
cross_validation
. We can import lightgbm
and scikit-learn
models
and try different combinations of them, alongside different target
transformations (as the ones we created previously) and historical
variables.MLForecast
Cross
Validation methods
here
Naive
model that uses the electricity load
of the last hour as prediction lag1
as showed in the next cell. You
can create your own models and try them with MLForecast
using the same
structure.
scikit-learn
library: Lasso
,
LinearRegression
, Ridge
, KNN
, MLP
and Random Forest
alongside
the LightGBM
. You can add any model to the dictionary to train and
compare them by adding them to the dictionary (models
) as shown.
MLForecast
class with the models we want to
try along side target_transforms
, lags
, lag_transforms
, and
date_features
. All this features are applied to the models we
selected.
In this case we use the 1st, 12th and 24th lag, which are passed as a
list. Potentially you could pass a range
.
cross_validation
method to train and evalaute the
models. + df
: Receives the training data + h
: Forecast horizon +
n_windows
: The number of folds we want to predict
You can specify the names of the time series id, time and target
columns. + id_col
:Column that identifies each serie ( Default
unique_id ) + time_col
: Column that identifies each timestep, its
values can be timestamps or integer( Default ds ) +
target_col
:Column that contains the target ( Default y )
unique_id | ds | cutoff | y | naive | lgbm | lasso | lin_reg | ridge | knn | mlp | rf | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | PJM_Load_hourly | 2001-12-27 01:00:00 | 2001-12-27 | 28332.0 | 28837.0 | 28526.505572 | 28703.185712 | 28702.625949 | 28702.625956 | 28479.0 | 28660.021947 | 27995.17 |
1 | PJM_Load_hourly | 2001-12-27 02:00:00 | 2001-12-27 | 27329.0 | 27969.0 | 27467.860847 | 27693.502318 | 27692.395954 | 27692.395969 | 27521.6 | 27584.635434 | 27112.50 |
2 | PJM_Load_hourly | 2001-12-27 03:00:00 | 2001-12-27 | 26986.0 | 27435.0 | 26605.710615 | 26991.795124 | 26990.157567 | 26990.157589 | 26451.6 | 26809.412477 | 26529.72 |
3 | PJM_Load_hourly | 2001-12-27 04:00:00 | 2001-12-27 | 27009.0 | 27401.0 | 26284.065138 | 26789.418399 | 26787.262262 | 26787.262291 | 26388.4 | 26523.416348 | 26490.83 |
4 | PJM_Load_hourly | 2001-12-27 05:00:00 | 2001-12-27 | 27555.0 | 28169.0 | 26823.617078 | 27369.643789 | 27366.983075 | 27366.983111 | 26779.6 | 26986.355992 | 27180.69 |
naive | lgbm | lasso | lin_reg | ridge | knn | mlp | rf | |
---|---|---|---|---|---|---|---|---|
metric | ||||||||
mae | 1631.395833 | 971.536200 | 1003.796433 | 1007.998597 | 1007.998547 | 1248.145833 | 1870.547722 | 1017.957813 |
mape | 0.049759 | 0.030966 | 0.031760 | 0.031888 | 0.031888 | 0.038721 | 0.057504 | 0.032341 |
rmse | 1871.398919 | 1129.713256 | 1148.616156 | 1153.262719 | 1153.262664 | 1451.964390 | 2102.098238 | 1154.647164 |
smape | 0.024786 | 0.015886 | 0.016269 | 0.016338 | 0.016338 | 0.019549 | 0.029917 | 0.016563 |
lgbm
has top performance in most metrics
folowed by the lasso regression
. Both models perform way better than
the naive
.
PredictionIntervals
function in mlforecast.utils
.fit
method, which takes the
following arguments:
df
: Series data in long format.id_col
: Column that identifies each series. In our case,
unique_id.time_col
: Column that identifies each timestep, its values can be
timestamps or integers. In our case, ds.target_col
: Column that contains the target. In our case, y.PredictionIntervals
function is used to compute prediction
intervals for the models using Conformal
Prediction.
The function takes the following arguments: + n_windows
: represents
the number of cross-validation windows used to calibrate the intervals +
h
: the forecast horizon
predict
method so we can compare them to our test
data. Additionally, we are going to create prediction intervals at
levels
[90,95]
.
unique_id | ds | lgbm | lasso | lgbm-lo-95 | lgbm-lo-90 | lgbm-hi-90 | lgbm-hi-95 | lasso-lo-95 | lasso-lo-90 | lasso-hi-90 | lasso-hi-95 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | PJM_Load_hourly | 2001-12-31 01:00:00 | 28847.573176 | 29124.085976 | 28544.593464 | 28567.603130 | 29127.543222 | 29150.552888 | 28762.752269 | 28772.604275 | 29475.567677 | 29485.419682 |
1 | PJM_Load_hourly | 2001-12-31 02:00:00 | 27862.589195 | 28365.330749 | 27042.311414 | 27128.839888 | 28596.338503 | 28682.866977 | 27528.548959 | 27619.065224 | 29111.596275 | 29202.112539 |
2 | PJM_Load_hourly | 2001-12-31 03:00:00 | 27044.418960 | 27712.161676 | 25596.659896 | 25688.230426 | 28400.607493 | 28492.178023 | 26236.955369 | 26338.087102 | 29086.236251 | 29187.367984 |
3 | PJM_Load_hourly | 2001-12-31 04:00:00 | 26976.104125 | 27661.572733 | 25249.961527 | 25286.024722 | 28666.183529 | 28702.246724 | 25911.133521 | 25959.815715 | 29363.329750 | 29412.011944 |
4 | PJM_Load_hourly | 2001-12-31 05:00:00 | 26694.246238 | 27393.922370 | 25044.220845 | 25051.548832 | 28336.943644 | 28344.271631 | 25751.547897 | 25762.524815 | 29025.319924 | 29036.296843 |
predict
method returns a DataFrame witht the predictions for each
model (lasso
and lgbm
) along side the prediction tresholds. The
high-threshold is indicated by the keyword hi
, the low-threshold by
the keyword lo
, and the level by the number in the column names.
unique_id | ds | y | lgbm | lasso | lgbm-lo-95 | lgbm-lo-90 | lgbm-hi-90 | lgbm-hi-95 | lasso-lo-95 | lasso-lo-90 | lasso-hi-90 | lasso-hi-95 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | PJM_Load_hourly | 2001-12-31 01:00:00 | 29001.0 | 28847.573176 | 29124.085976 | 28544.593464 | 28567.603130 | 29127.543222 | 29150.552888 | 28762.752269 | 28772.604275 | 29475.567677 | 29485.419682 |
1 | PJM_Load_hourly | 2001-12-31 02:00:00 | 28138.0 | 27862.589195 | 28365.330749 | 27042.311414 | 27128.839888 | 28596.338503 | 28682.866977 | 27528.548959 | 27619.065224 | 29111.596275 | 29202.112539 |
2 | PJM_Load_hourly | 2001-12-31 03:00:00 | 27830.0 | 27044.418960 | 27712.161676 | 25596.659896 | 25688.230426 | 28400.607493 | 28492.178023 | 26236.955369 | 26338.087102 | 29086.236251 | 29187.367984 |
3 | PJM_Load_hourly | 2001-12-31 04:00:00 | 27874.0 | 26976.104125 | 27661.572733 | 25249.961527 | 25286.024722 | 28666.183529 | 28702.246724 | 25911.133521 | 25959.815715 | 29363.329750 | 29412.011944 |
4 | PJM_Load_hourly | 2001-12-31 05:00:00 | 28427.0 | 26694.246238 | 27393.922370 | 25044.220845 | 25051.548832 | 28336.943644 | 28344.271631 | 25751.547897 | 25762.524815 | 29025.319924 | 29036.296843 |
test
set.
unique_id | metric | lgbm | lasso | |
---|---|---|---|---|
0 | PJM_Load_hourly | mae | 1092.050817 | 899.979743 |
1 | PJM_Load_hourly | rmse | 1340.422762 | 1163.695525 |
2 | PJM_Load_hourly | mape | 0.033600 | 0.027688 |
3 | PJM_Load_hourly | smape | 0.017137 | 0.013812 |
lasso
regression performed slighty better than the
LightGBM
for the test set. Additonally, we can also plot the forecasts
alongside their prediction intervals. For that we can use the
plot_series
method available in utilsforecast.plotting
.
We can plot one or many models at once alongside their coinfidence
intervals.
Prophet
. This model is known for its ability to model different
seasonalities (weekly, daily yearly). We will use this model as a
benchmark to see if the lgbm
alongside MLForecast
adds value for
this time series.
unique_id | ds | Prophet | Prophet-lo-90 | Prophet-hi-90 | |
---|---|---|---|---|---|
0 | PJM_Load_hourly | 2001-12-31 01:00:00 | 25333.448442 | 20589.873559 | 30370.174820 |
1 | PJM_Load_hourly | 2001-12-31 02:00:00 | 24039.925936 | 18927.503487 | 29234.930903 |
2 | PJM_Load_hourly | 2001-12-31 03:00:00 | 23363.998793 | 18428.462513 | 28292.424622 |
3 | PJM_Load_hourly | 2001-12-31 04:00:00 | 23371.799609 | 18206.273446 | 28181.023448 |
4 | PJM_Load_hourly | 2001-12-31 05:00:00 | 24146.468610 | 19356.171497 | 29006.546759 |
unique_id | ds | lgbm | lgbm-lo-90 | lgbm-hi-90 | |
---|---|---|---|---|---|
0 | PJM_Load_hourly | 2001-12-31 01:00:00 | 28847.573176 | 28567.603130 | 29127.543222 |
1 | PJM_Load_hourly | 2001-12-31 02:00:00 | 27862.589195 | 27128.839888 | 28596.338503 |
2 | PJM_Load_hourly | 2001-12-31 03:00:00 | 27044.418960 | 25688.230426 | 28400.607493 |
3 | PJM_Load_hourly | 2001-12-31 04:00:00 | 26976.104125 | 25286.024722 | 28666.183529 |
4 | PJM_Load_hourly | 2001-12-31 05:00:00 | 26694.246238 | 25051.548832 | 28336.943644 |
unique_id | metric | Prophet | lgbm | |
---|---|---|---|---|
0 | PJM_Load_hourly | mae | 2266.561642 | 1092.050817 |
1 | PJM_Load_hourly | rmse | 2701.302779 | 1340.422762 |
2 | PJM_Load_hourly | mape | 0.073226 | 0.033600 |
3 | PJM_Load_hourly | smape | 0.038320 | 0.017137 |
lgbm
had consistently better metrics than prophet
.
improvement | |
---|---|
metric | |
mae | 2.08 |
rmse | 2.02 |
mape | 2.18 |
smape | 2.24 |
lgbm
with MLForecast
was able to provide metrics at
least twice as good as Prophet
as seen in the column improvement
above, and way faster.