import numpy as np
import pandas as pd
import concurrent.futures
import requests
import time
from functools import partial
from datetime import datetime
import os
import warnings
warnings.filterwarnings(action='ignore')
def toDatetime(record):
"""
Convert formatted date-time text into timestamp.
It is specifically tailored to the API.
"""
timestamp = record['timestamp']
record['timestamp'] = datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S")
return record
def getData(startTime, endTime , freq="Daily"):
"""
Convert formatted date-time text into timestamp.
This is specifically tailored to the source of data
Keyword arguments:
startTime -- the starting date of the period you wish to fetch (YYYY-MM-DD)
endTime -- the ending date of the period you wish to fetch (YYYY-MM-DD)
freq -- the frequency of data (Hourly/Daily/...)
"""
reportingGroups = ["Electricity"]
locationsLink = 'https://helsinki-openapi.nuuka.cloud/api/v1.0/Property/List'
locationsRequest = requests.get(locationsLink) # initialize api
locationsRequest.raise_for_status() # get data from api
locations = pd.DataFrame.from_dict(locationsRequest.json())
locations = locations['locationName']
data = []
dataLink = (f"https://helsinki-openapi.nuuka.cloud/api/v1.0/"
f"EnergyData/{freq}/ListByProperty")
# Helper function for concurrency
def request(group, location):
payload = {'StartTime': startTime, 'EndTime': endTime}
payload.update({'ReportingGroup': group})
payload.update({'SearchString': location.split(' ', 1)[0]})
r = requests.get(dataLink, payload)
if r.status_code == requests.codes.ok:
data_dict = r.json()
for record in data_dict:
toDatetime(record)
data.extend(data_dict)
# The final step - getting the data.
for g in reportingGroups:
with concurrent.futures.ThreadPoolExecutor() as executor:
executor.map(partial(request, g), locations)
# for each "thread", execute "request" function on each location
return pd.DataFrame(data)
# # Request the data from the API.
# # Note: You can only make 5000 API requests per hour.
# start = time.perf_counter()
# ts = getData('2017-01-01', '2020-01-01', freq='Daily')
# finish = time.perf_counter()
# print(f'Finished in {finish-start} second(s)')
# # Export the Dataframe to a .csv file
# file_name = 'hourly.csv' # change this to the respective frequency of the data
# ts.to_csv(file_name)
# necessary packages
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
The first thing to do is to load the hourly data that was pulled using the scripts in the data fetching section.
ts = pd.read_csv("hourly_dropped.csv", parse_dates=['timestamp'])
ts.head()
timestamp | locationName | value | |
---|---|---|---|
0 | 2017-01-01 00:00:00 | 1512 Malmin raitti 3 | 0.04 |
1 | 2017-01-01 01:00:00 | 1512 Malmin raitti 3 | 0.04 |
2 | 2017-01-01 02:00:00 | 1512 Malmin raitti 3 | 0.04 |
3 | 2017-01-01 03:00:00 | 1512 Malmin raitti 3 | 0.04 |
4 | 2017-01-01 04:00:00 | 1512 Malmin raitti 3 | 0.04 |
Next, we clean the data and restructure it into a more suitable format.
Group the ts
dataframe by the locations since our data contain electricity demand records from multiple locations.
For each location, we create a dataframe. The index of each of these dataframes are the timestamps of the data (from 2017-January-01 to 2020-January-01). These dataframes are stored in the list locs
.
For each dataframe, we delete the duplicated records if there are any.
groups = ts.groupby('locationName')
# divide into different dataframes for each location
locs = [groups.get_group(df).set_index('timestamp').value for df in groups.groups]
# remove duplicated rows
locs = [df[~df.index.duplicated(keep='first')] for df in locs]
Next, we further clean the data. As there may be locations whose data are not in hourly resolution, we need to reformat those dataframes so that all of them are in the hourly resolution.
# create a range of timestamps covering the time range of the data.
all_dates = pd.date_range(start='2017-01-01', end='2020-01-01', freq='1H')
for idx, loc in enumerate(locs):
if len(loc) > len(all_dates): # are there more records (timestamps) than the default dataframe
locs[idx] = locs[idx].resample('1H').sum()
Next, we combine all dataframes in locs
into 1 big dataframe df
, in which each column is a location.
df = pd.concat(locs, join='outer', axis=1, ignore_index=True)
df.head()
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ... | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
timestamp | |||||||||||||||||||||
2017-01-01 00:00:00 | 82.92 | 36.35 | 14.25 | 45.92 | 34.63 | 11.47 | 2.80 | 43.56 | 0.04 | 8.80 | ... | 22.46 | 49.56 | 43.48 | 42.4 | 21.15 | 200.0 | 108.0 | 25.91 | NaN | 69.0 |
2017-01-01 01:00:00 | 83.91 | 41.11 | 13.00 | 44.80 | 33.18 | 11.60 | 2.76 | 43.52 | 0.04 | 8.56 | ... | 22.32 | 49.56 | 43.79 | 42.4 | 21.12 | 100.0 | 112.0 | 28.63 | NaN | 43.0 |
2017-01-01 02:00:00 | 83.31 | 40.55 | 9.45 | 43.68 | 32.55 | 11.46 | 2.76 | 43.87 | 0.04 | 8.80 | ... | 22.35 | 50.04 | 43.67 | 42.0 | 21.66 | 100.0 | 112.0 | 26.50 | NaN | 42.0 |
2017-01-01 03:00:00 | 84.88 | 41.33 | 9.35 | 45.44 | 32.54 | 11.60 | 2.76 | 44.10 | 0.04 | 8.72 | ... | 22.40 | 49.32 | 44.34 | 41.6 | 21.25 | 200.0 | 112.0 | 26.89 | NaN | 42.0 |
2017-01-01 04:00:00 | 86.23 | 40.76 | 9.40 | 52.64 | 32.47 | 11.55 | 4.12 | 43.98 | 0.04 | 8.72 | ... | 22.40 | 49.68 | 43.55 | 42.0 | 20.99 | 200.0 | 120.0 | 30.83 | NaN | 47.0 |
5 rows × 581 columns
Now, we can already spot that there are NaN entries in the dataframe. In other words, for those locations at those timestamps, there was no available data. Although it may be arbitrary, we decided to only take the columns (locations) in which there are less than 100 missing entries.
df2 = df.loc[:,df.isna().sum() < 100].copy()
df2.head()
1 | 2 | 4 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | ... | 557 | 560 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 580 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
timestamp | |||||||||||||||||||||
2017-01-01 00:00:00 | 36.35 | 14.25 | 34.63 | 11.47 | 2.80 | 43.56 | 8.80 | 6.93 | 2.21 | 11.52 | ... | 43.44 | 129.45 | 22.46 | 49.56 | 43.48 | 42.4 | 21.15 | 200.0 | 108.0 | 69.0 |
2017-01-01 01:00:00 | 41.11 | 13.00 | 33.18 | 11.60 | 2.76 | 43.52 | 8.56 | 6.89 | 2.26 | 11.58 | ... | 43.44 | 129.44 | 22.32 | 49.56 | 43.79 | 42.4 | 21.12 | 100.0 | 112.0 | 43.0 |
2017-01-01 02:00:00 | 40.55 | 9.45 | 32.55 | 11.46 | 2.76 | 43.87 | 8.80 | 7.18 | 2.20 | 11.58 | ... | 43.44 | 129.45 | 22.35 | 50.04 | 43.67 | 42.0 | 21.66 | 100.0 | 112.0 | 42.0 |
2017-01-01 03:00:00 | 41.33 | 9.35 | 32.54 | 11.60 | 2.76 | 44.10 | 8.72 | 6.83 | 2.27 | 11.64 | ... | 44.04 | 129.45 | 22.40 | 49.32 | 44.34 | 41.6 | 21.25 | 200.0 | 112.0 | 42.0 |
2017-01-01 04:00:00 | 40.76 | 9.40 | 32.47 | 11.55 | 4.12 | 43.98 | 8.72 | 6.98 | 2.27 | 11.64 | ... | 44.40 | 129.44 | 22.40 | 49.68 | 43.55 | 42.0 | 20.99 | 200.0 | 120.0 | 47.0 |
5 rows × 348 columns
However, our data might still have missing timestamps, which is why we insert rows whose index are the missing timestamps.
for idx in all_dates.difference(df2.index):
df2.loc[idx] = pd.Series(dtype='float64')
As one solution to the missing entries problem, we decided to interpolate the data linearly, that is, for each column (location), the missing entries are filled with values that follow a linear trend to other entries. There are other interpolation methods as well, but we decided to go with this method. This interpolation should not affect the inference much as for each column there are only at most 100 missing entries, and the dataframe has more than 25000 rows.
After the interpolation step, we sum over all columns (locations) as we only wish to predict the hourly "total" electricity demand.
df3 = df2.sort_index().interpolate().sum(axis=1).rename('kWh')
df3.head()
timestamp 2017-01-01 00:00:00 7985.77 2017-01-01 01:00:00 7780.45 2017-01-01 02:00:00 7676.35 2017-01-01 03:00:00 7722.80 2017-01-01 04:00:00 7716.57 Name: kWh, dtype: float64
As a final step in this section, we save the aggregated data.
file_name = 'hourly_total.csv'
df3.to_csv(file_name)
import pandas as pd
import numpy as np
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
from statsmodels.tsa.seasonal import STL
from matplotlib import pyplot as plt, rc_context, rc
plt.style.use('seaborn-deep')
We start by reading in the data.
ts = pd.read_csv("hourly_total.csv", parse_dates=['timestamp'], index_col=['timestamp'])
ts
kWh | |
---|---|
timestamp | |
2017-01-01 00:00:00 | 7985.77 |
2017-01-01 01:00:00 | 7780.45 |
2017-01-01 02:00:00 | 7676.35 |
2017-01-01 03:00:00 | 7722.80 |
2017-01-01 04:00:00 | 7716.57 |
... | ... |
2019-12-31 20:00:00 | 11171.96 |
2019-12-31 21:00:00 | 10570.42 |
2019-12-31 22:00:00 | 10077.17 |
2019-12-31 23:00:00 | 9530.08 |
2020-01-01 00:00:00 | 9254.37 |
26281 rows × 1 columns
First, we look at some descriptive statistics about the data.
ts.describe()
kWh | |
---|---|
count | 26281.000000 |
mean | 12064.554405 |
std | 4915.637292 |
min | 5801.520000 |
25% | 8453.570000 |
50% | 10111.080000 |
75% | 14381.880000 |
max | 26206.430000 |
Note that this data is on the hourly resolution; therefore, the max value is much lower than the average (or even the minimum) value of the daily data. Next, we try to visualize the time series to gain some insights.
ts2017,ts2018,ts2019 = ts['2017-01-01':'2018-01-01'],ts['2018-01-01':'2019-01-01'],ts['2019-01-01':'2020-01-01']
fig,ax = plt.subplots(3,figsize=(10,15))
ax[0].plot(ts2017,'o')
ax[0].set_title('Energy Consumption 2017')
ax[1].plot(ts2018,'o')
ax[1].set_title('Energy Consumption 2018')
ax[2].plot(ts2019,'o')
ax[2].set_title('Energy Consumption 2019')
plt.show()
We could already identify some general patterns from the data above. For example, we notice that the demand peaks in winter-spring then bottoms out during summer. We should zoom in on different parts of the series.
def plot(data, start, end):
data.loc[start:end].plot(figsize=(12,5), style='o-')
plt.show()
plot(ts, '2017-12-12', '2018-01-31')
It looks like that during the holidays (winter - new year), there is low electricity demand. In addition, during the weekend, the demand is lower than in the weekdays. Next, we investigate the summer months.
plot(ts, '2018-05-01', '2018-09-30')
Compared to the plot above, it is clear that the demand in the summer is lower than the other seasons. There is a decreasing trend in demand in the first half of the year, and there is an increasing trend in the second half of the year. This situation should be the same for 2017:
plot(ts, '2017-05-01', '2017-09-30')
Indeed it is. Next, we zoom in on a random week.
plot(ts, '2017-06-25', '2017-07-08')
plot(ts, '2018-01-14', '2018-01-27')
The plot above further emphasizes the weekly trend as well as reveals the daily trends: the demand tend to peak locally at around noon (Finnish time). The trends and seasonalities above (except for the daily seasonality) are also explained in the Exploratory Data Analysis for daily data section.
Since this is a time series, we should look at its autocorrelation function plot to identify any patterns that could potentially help us build common models such as ARIMA.
with rc_context():
rc("figure", figsize=(10,5))
plot_acf(ts['kWh'], lags=720) # 720 hours, or 30 days
plt.show()
with rc_context(): # takes quite some time to run
rc("figure", figsize=(10,5))
plot_pacf(ts['kWh'], lags=720)
plt.show()
As is identified above and further confirmed with these two plots, this time series has multiple seasonality and complex seasonality patterns. For this kind of time series, ARIMA (a common time series model) will not work very well.
Here, we also try to decompose the time series with the STL
method. The period is $24\times 7$ hours because from most of the plots above (and from the previous EDA section on daily data), there is a weekly cycle, and there are $24\times 7$ hours in a week. Of course, there are other seasonalities in this data, but the period mentioned above should demonstrate well this decomposition.
plt.rcParams.update({'figure.figsize':(10,9)})
stl = STL(ts2017,period=24*7,robust=True)
res = stl.fit()
fig = res.plot()
Now, we are pretty confident that the hourly electricity demand time series consists of three main components: A (yearly) trend that reaches bottom in July, a complex seasonality component (with multiple other seasonality sub-components), and holiday effects.
import pandas as pd
import numpy as np
# Facebook Prophet
from prophet import Prophet
from prophet.diagnostics import cross_validation, performance_metrics
from prophet.plot import add_changepoints_to_plot, plot_cross_validation_metric, plot_seasonality
import prophet.plot
# Plotting
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
import matplotlib.pyplot as plt
from sklearn.metrics import mean_squared_error, mean_absolute_percentage_error
Importing plotly failed. Interactive plots will not work.
import logging
logging.getLogger().setLevel(logging.ERROR)
logging.getLogger("pystan").setLevel(logging.ERROR)
logging.getLogger("prophet").setLevel(logging.ERROR)
First, we load in the hourly data created in the wrangling step.
df = pd.read_csv("hourly_total.csv", parse_dates=['timestamp'], index_col=['timestamp'])
df
kWh | |
---|---|
timestamp | |
2017-01-01 00:00:00 | 7985.77 |
2017-01-01 01:00:00 | 7780.45 |
2017-01-01 02:00:00 | 7676.35 |
2017-01-01 03:00:00 | 7722.80 |
2017-01-01 04:00:00 | 7716.57 |
... | ... |
2019-12-31 20:00:00 | 11171.96 |
2019-12-31 21:00:00 | 10570.42 |
2019-12-31 22:00:00 | 10077.17 |
2019-12-31 23:00:00 | 9530.08 |
2020-01-01 00:00:00 | 9254.37 |
26281 rows × 1 columns
Next, we rename the columns for use in the models below. The models below expect the data to have a ds
column (representing the timestamps) and a y
column (representing the values of the time series).
df.index.names = ['ds']
df.rename(columns={'kWh':'y'}, inplace=True)
We divide the data into three sets: train, validation, and test sets. The test set starts from the timestamp 2019-12-01. In the remaing portion of the data, the first 80% of it is used for training, and the remaining 20% is used for validation. However, we may choose not to generate the validation set if it's not necessary.
def train_val_test_split(df, validation=True):
train_end = '2019-12-01 00:00:00'
train_val = df.loc[:train_end, :]
test_df = df.loc[train_end:, :]
# Let the ds indexing column be its own column as is required by the models below.
for df in [train_val, test_df]:
df.reset_index(inplace=True)
if not validation:
return train_val, test_df
n = len(train_val)
cutoff = int(n*0.8)
train_df = train_val[:cutoff]
val_df = train_val[cutoff:]
return train_df, val_df, test_df
train_df, val_df, test_df = train_val_test_split(df)
Prophet is a open-source project from Facebook that aims to model time series data and make reliable forecasts. Essentially, a Prophet model is an additive model with several components: a seasonal component with multiple levels (e.g., yearly, weekly, daily, and user-defined seasonalities), a trend component which can be highly non-linear, and holiday effects. Prophet fits models with Stan, a probabilistic programming framework. Prophet estimates the parameters with maximum a posteriori estimates by default.
The model can typically be expressed with the following formula:
$$ \begin{aligned} y_t=g(t)+s(t)+h(t) \end{aligned} $$where:
$y_t$ is the time series
$g(t)$ is a (possibly smooth and nonlinear) function of the trend
$s(t)$ represents the seasonal, periodic changes (e.g., yearly and weekly seasonalities)
$h(t)$ models the effects of holidays, whose schedule may be irregular.
For more information, please see the article (Taylor et al., 2018).
# Helper functions for Prophet model fitting and plotting.
def to_exp(df, colnames):
res = df.copy()
for colname in colnames:
res[colname] = res[colname].apply(np.exp)
return res
def to_log(df, colnames):
res = df.copy()
for colname in colnames:
res[colname] = res[colname].apply(np.log)
return res
def plot(forecast, train, x1,x2):
"""
Plots two time series 'forecast' and 'train' in one plot mainly to compare them.
Also plots the first time series against the second time series to determine
how similar they are. x1 is the starting time point and x2 is the ending time point.
"""
yhat = forecast.set_index('ds').loc[x1:x2, 'yhat']
y = train.set_index('ds').loc[x1:x2, 'y']
plt.figure(figsize=(15,7))
plt.plot(yhat, 'o-', label='Estimated')
plt.plot(y, 'o', label='True')
lower = forecast.set_index('ds').loc[x1:x2, 'yhat_lower']
upper = forecast.set_index('ds').loc[x1:x2, 'yhat_upper']
plt.fill_between(yhat.index, lower, upper, alpha=0.2)
plt.legend()
plt.show()
plt.figure(figsize=(10,5))
plt.scatter(x=y, y=yhat)
min_x = int(np.floor(np.min([yhat.min(), y.min()])))
max_x = int(np.ceil(np.max([yhat.max(), y.max()])))
r = range(min_x,max_x)
plt.plot(r, r, 'r', label='y=x')
plt.title('True (x-axis) against estimated (y-axis)')
plt.legend()
plt.show()
def acf_res(forecast, train):
"""
Plots a autocorrelation plot of the residuals (y-true minus y-predicted) for training data.
"""
date_range = train['ds']
res = train.set_index('ds')['y'] - forecast.set_index('ds').loc[date_range, 'yhat']
plot_acf(res)
def is_spring(ds):
return ds.month in [3,4,5]
def is_summer(ds):
"""
Determines whethere a timestamp ds is in the summer or not.
"""
return ds.month in [6,7,8]
def is_winter(ds):
"""
Determines whether a timestamp ds is in the winter.
"""
return ds.month in [12,1,2]
def is_autumn(ds):
return ds.month in [9,10,11]
def is_weekend(ds):
"""
Determines whether a timestamp ds is in the weekend.
"""
return ds.day_name() in ['Saturday', 'Sunday']
def is_weekday(ds):
"""
Determines whether a timestamp ds is a weekday.
"""
return not is_weekend(ds)
def is_winter_weekday(ds):
"""
Determines whether a timestamp ds is a winter weekday.
"""
return is_winter(ds) and is_weekday(ds)
def is_winter_weekend(ds):
"""
Determines whether a timestamp ds is a winter weekend.
"""
return is_winter(ds) and is_weekend(ds)
def is_summer_weekday(ds):
"""
Determines whether a timestamp ds is a summer weekday.
"""
return is_summer(ds) and is_weekday(ds)
def is_summer_weekend(ds):
"""
Determines whether a timestamp ds is a summer weekend.
"""
return is_summer(ds) and is_weekend(ds)
def is_first_half(ds):
"""
Determines whether a timestamp is in the first half of a year (spring-summer)
"""
return (ds.month >= 2 and ds.month <= 7)
def is_second_half(ds):
"""
Determines whether a timestamp is in the second half of a year (autumn-summer)
"""
return not is_first_half(ds)
def is_spring_weekday(ds):
return is_spring(ds) and is_weekday(ds)
def is_spring_weekend(ds):
return is_spring(ds) and is_weekend(ds)
def is_autumn_weekday(ds):
return is_autumn(ds) and is_weekday(ds)
def is_autumn_weekend(ds):
return is_autumn(ds) and is_weekend(ds)
def add_custom_seasonality(df):
"""
Augments the dataframe df with several columns determining
whether the corresponding datapoints belong to different seasoonalities.
"""
df = df.assign(
spring_weekday = df['ds'].apply(is_spring_weekday),
spring_weekend = df['ds'].apply(is_spring_weekend),
summer_weekday = df['ds'].apply(is_summer_weekday),
summer_weekend = df['ds'].apply(is_summer_weekend),
autumn_weekday = df['ds'].apply(is_autumn_weekday),
autumn_weekend = df['ds'].apply(is_autumn_weekend),
winter_weekday = df['ds'].apply(is_winter_weekday),
winter_weekend = df['ds'].apply(is_winter_weekend),
summer = df['ds'].apply(is_summer),
winter = df['ds'].apply(is_winter),
weekend = df['ds'].apply(is_weekend),
weekday = df['ds'].apply(is_weekday)
)
return df
def mape(y_true, y_pred):
"""
calculates the mean absolute percentage error, for use below
"""
return mean_absolute_percentage_error(y_true, y_pred)
First, we try the default Prophet model, that is, we do not specify any further options and use the default arguments for its fit
method. In a default Prophet model for hourly time series, there is a yearly seasonality, weekly seasonality, and daily seasonality, The growth is linear and the components are additive.
model_default = Prophet(interval_width=0.95) # for each estimate, return a 95% interval
model_default.fit(train_df)
None
Initial log joint probability = -635.268 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 99 38735.3 0.0011358 219.705 0.7006 0.7006 133 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 199 38738.1 0.000455496 513.434 0.7367 0.7367 250 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 299 38739.3 0.00185707 192.922 1 1 382 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 359 38739.6 4.27005e-05 121.165 1.377e-07 0.001 516 LS failed, Hessian reset 399 38739.7 2.482e-06 63.5939 0.2749 0.2749 565 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 421 38739.7 2.20626e-06 65.7519 0.1712 1 595 Optimization terminated normally: Convergence detected: relative gradient magnitude is below tolerance
After fitting the model_default
on the train dataset, we use it to make forecasts on the validation dataset.
future = model_default.make_future_dataframe(periods=len(val_df), freq='H')
forecast = model_default.predict(future)
The plot below shows the fitted values (blue curve) against the true data (black dots). The blue bands are 95% uncertainty intervals.
fig1 = model_default.plot(forecast, figsize=(15,7))
As we can see, the fit is not satisfactory as the model underestimates and overestimates the data at regular intervals (the blue line is consistently lower and higher than the black "curve" (dots), even if the 95% intervals cover the black dots.
We should zoom in on a time period to investigate the fits.
plot(forecast, train_df, '2017-07', '2017-09')
Additionally, we investigate the autocorrelation plot of the residuals, i.e., true value minus predicted value.
acf_res(forecast, train_df)
In a satisfactory (ARIMA) model, the autocorrelations should be very low (they should lie within the blue 95% confidence band). However, in the plot above, not only are the autocorrelations high, they also lie outside the band. Since we did not include any autoregressive terms, this bad fit is to be expected.
From all plots above, we can conclude that the model regularly underestimates as well as overestimates the data, and that the model is not satisfactory.
Next, we plot some components of the models: the trend and the seasonalities.
fig2 = model_default.plot_components(forecast)
Remarks: the default model correctly identifies that during a day, the electricity demand typically reaches its daily peak around noon (the time zone of the data is in UTC, but the origin of the data is in UTC+2 or UTC+3 time zone). It also correctly identifies that in a year, the demand typically reaches its lowest in the summer. In contrast, the demand is typically very high in the winter. In addition, in a week, the model correctly identifies that the demand is lower in weekends and higher in weekdays.
After a careful look at the data, we observe that in the summer, the within-day variation in electricity demand seems to be smaller than that in the winter. This suggests that this variation has a multiplicative effect. In addition, we notice that the demand is always lower in the weekend than during the weekdays. In addition, within each year, we see that there is a decreasing trend in the first half of the year and an increasing trend in the second half of the year.
In light of the observations above, we add our custom seasonalities. First of all, we add seasonalities for weekends and weekdays for each season (winter, spring, summer, autumn). However, we transform the data by logarithmizing the values because we wish the seasonalities to have a multiplicative effect on each other as log of a product is a sum:
$$ \begin{aligned} y_t&=g(t)\times s(t)\times h(t)\\ \Rightarrow \log{y_t}&=\log{g(t)}+\log{s(t)}+\log{h(t)} \end{aligned} $$train_df, val_df, test_df = train_val_test_split(to_log(df, ['y']))
train_df = add_custom_seasonality(train_df)
val_df = add_custom_seasonality(val_df)
test_df = add_custom_seasonality(test_df)
future = add_custom_seasonality(future)
a = pd.date_range(start='2017-01-01', end='2017-01-08', freq='1D').to_series()
b = pd.date_range(start='2017-12-20', end='2018-01-08', freq='1D').to_series()
c = pd.date_range(start='2018-12-20', end='2019-01-08', freq='1D').to_series()
d = pd.date_range(start='2019-12-20', end='2020-01-01', freq='1D').to_series()
# In Finland, usually Christmas and New year holidays last acoording to the date ranges above
christmas_newyear = pd.DataFrame({
'holiday': 'christmas_newyear',
'ds': pd.concat([a, b, c, d], axis=0).values
})
def better_prophet():
model_improved = Prophet(
changepoint_prior_scale=0.5,
daily_seasonality=False,
holidays = christmas_newyear,
interval_width=0.95)
# The higher the fourier_order, the more wiggly the seasonality component becomes
model_improved.add_seasonality(name='spring_weekday', period=1, fourier_order=12, condition_name='spring_weekday')
model_improved.add_seasonality(name='spring_weekend', period=1, fourier_order=12, condition_name='spring_weekend')
model_improved.add_seasonality(name='summer_weekday', period=1, fourier_order=12, condition_name='summer_weekday')
model_improved.add_seasonality(name='summer_weekend', period=1, fourier_order=12, condition_name='summer_weekend')
model_improved.add_seasonality(name='autumn_weekday', period=1, fourier_order=12, condition_name='autumn_weekday')
model_improved.add_seasonality(name='autumn_weekend', period=1, fourier_order=12, condition_name='autumn_weekend')
model_improved.add_seasonality(name='winter_weekday', period=1, fourier_order=12, condition_name='winter_weekday')
model_improved.add_seasonality(name='winter_weekend', period=1, fourier_order=12, condition_name='winter_weekend')
model_improved.add_country_holidays(country_name='Finland')
return model_improved
model_improved = better_prophet()
model_improved.fit(train_df)
None
Initial log joint probability = -27.4921 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 99 86957.1 0.000503586 16715.3 1 1 123 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 199 87118 0.000447545 7700.55 1 1 228 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 299 87185.4 0.000511386 22496.1 0.422 1 343 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 399 87258.7 0.00216175 10073.4 0.5063 1 450 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 499 87286.2 0.000819403 6115.44 1 1 560 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 599 87290.8 0.000793825 3761.81 1 1 673 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 699 87299.7 0.00055602 2291.27 1 1 783 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 799 87303.7 0.000295679 2947.96 1 1 890 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 899 87313.2 0.000481969 4116.12 1 1 999 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 999 87317.3 2.38646e-05 3281.23 0.4569 0.4569 1110 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1099 87318.6 0.000578451 1536.12 1 1 1220 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1199 87320.8 0.000169228 2712.87 1 1 1328 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1299 87321.8 0.000285385 923.804 0.5261 0.5261 1435 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1399 87322.7 8.67929e-06 524.985 0.6095 0.6095 1540 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1499 87323.5 3.36629e-05 1592.98 0.5599 0.5599 1649 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1599 87327 0.000953593 5337.35 0.6597 0.6597 1760 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1699 87330 0.00121819 4786.04 1 1 1868 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1799 87331.2 0.00120256 4005.18 0.1667 1 1977 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1899 87332.4 3.28651e-05 542.251 1 1 2091 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1999 87333.3 0.000382552 728.796 1 1 2202 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2099 87333.9 9.33465e-05 386.372 1 1 2311 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2199 87334.7 0.00040299 1524.04 1 1 2421 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2299 87335.1 4.38056e-05 1106.28 0.4638 0.4638 2533 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2399 87335.5 1.26346e-05 444.462 1 1 2642 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2499 87335.8 2.7574e-05 414.474 1 1 2750 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2585 87335.9 5.30719e-06 208.856 1 1 2843 Optimization terminated normally: Convergence detected: relative gradient magnitude is below tolerance
forecast_improved = model_improved.predict(future)
fig1 = model_improved.plot(forecast_improved, figsize=(15,7))
fig2 = model_improved.plot_components(forecast_improved)
plot(to_exp(forecast_improved, ['yhat', 'yhat_lower', 'yhat_upper']), to_exp(train_df, ['y']), '2017-01-01', '2018-12-01')
plot(to_exp(forecast_improved, ['yhat', 'yhat_lower', 'yhat_upper']), to_exp(train_df, ['y']), '2018-01-02', '2018-01-20')
acf_res(forecast_improved, train_df)
Although the model consistently overpredicts in the summer, the forecasts are quite satisfactory in other seasons. Judging from the last plot above, the residual autocorrelations are much lower than those of the previous model.
Cross-validation on time series data is different from that on common non time-series data because there is a time structure which must not be broken with random partitioning used in normal cross-validation methods. This is why Prophet has its own cross-validation methods.
The cross-validation procedure is as follows. First, the model is trained on the first 8766 hours of the data (initial). Then, it forecasts the next 720 hours of data (horizon). After that, the model is trained on the first 8766 hours plus the next 1440 hours (period by default) and make predictions on the next 720 hours after that 360 hours. This procedure continues (training data has 1440-hour increments and forecasts are made on the next 720 hours) until there is not enough data left to do this.
df_cv = cross_validation(model_improved, initial='8766 hours', period='1440 hours', horizon='720 hours', parallel="processes")
Initial log joint probability = -11.471 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes Initial log joint probability = -15.0729 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes Initial log joint probability = -21.8625 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes Initial log joint probability = -18.9933 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes Initial log joint probability = -20.1531 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes Initial log joint probability = -19.253 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes Initial log joint probability = -19.0252 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes Initial log joint probability = -22.6015 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 99 41345.2 0.000548757 10030.2 0.4261 0.4261 119 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 99 47501.6 0.00124999 6114.58 1 1 119 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 99 53797.4 0.000340203 7025.14 1 1 116 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 99 60014 0.00247326 13043.8 1 1 126 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 99 66224.4 0.00281839 42765.1 1 1 119 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 199 41359.8 0.000867063 2136.31 1 1 231 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 99 72447.2 0.00226106 20365.2 1 1 122 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 199 47517.9 0.000828279 3714.26 1 1 223 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 99 77942.2 0.00662896 32803.6 1 1 123 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 99 84111.4 0.000566965 29121.6 1 1 129 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 199 53817.1 3.69101e-05 1995.43 0.6424 0.6424 225 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 299 41364.2 7.78714e-05 1956.24 1 1 342 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 199 60099.9 0.000311177 4583.29 0.3155 0.7912 240 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 299 47520.9 0.000274104 1184.66 0.8919 0.8919 331 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 199 66369.3 0.00191307 16043.7 0.8364 0.8364 229 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 199 72573.5 0.00494617 27268.8 1 1 238 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 299 53826.5 0.000103462 2421.7 1 1 331 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 399 41368.2 0.000283529 1045.54 1 1 451 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 199 78121.2 0.00421494 20990.1 1 1 235 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 399 47523 2.26934e-05 326.105 1 1 442 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 299 60104.5 0.00036969 1404.7 1 1 347 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 199 84241.1 0.00167999 30779.6 0.3875 1 240 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 499 41381 0.00602525 13626.5 0.2223 1 559 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 299 66416.1 0.0065362 17929.9 1 1 342 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 399 53832.9 0.0026013 8604.63 1 1 443 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 299 72679.7 0.000414487 12703.6 1 1 347 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 499 47530.1 0.000167382 2942.57 0.7096 0.7096 548 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 599 41438 0.00212997 2801.36 0.9248 0.9248 670 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 399 60106.1 0.00151644 3556.89 1 1 457 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 299 78192.1 0.000637548 2554.02 1 1 341 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 499 53838.5 2.88873e-05 2554.79 1 1 549 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 599 47537.2 8.70027e-05 1051.42 0.2917 0.2917 654 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 299 84316.1 0.000845964 17368.9 0.916 0.916 351 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 399 66429.5 0.00157703 6610.99 1 1 454 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 699 41445.8 0.000229243 1950.94 1 1 777 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 399 72688.5 0.000271559 2332.17 1 1 458 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 499 60110.4 0.0015447 4051.26 1 1 571 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 599 53840 2.86286e-05 626.399 1 1 661 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 699 47542.2 0.00121233 3075.49 0.6576 0.6576 763 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 799 41449.4 0.000245195 1282.88 1 1 881 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 399 78219.8 0.000841334 7798.7 0.2485 0.9275 453 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 499 66440.9 0.00451046 6125.72 1 1 559 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 399 84362.7 0.000456424 3754.47 1 1 462 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 899 41451.3 1.051e-05 424.004 1 1 986 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 799 47547.2 0.000771144 2067.99 0.6415 0.6415 871 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 599 60117.9 6.99185e-05 1003.01 1 1 682 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 499 72699.8 0.000917421 1624.29 1 1 568 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 699 53843.6 2.85927e-05 723.791 1 1 772 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 999 41451.8 0.000201802 938.635 1 1 1093 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 499 78246.1 0.00226166 5713.16 1 1 560 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 599 66462 0.000193756 4114.35 1 1 670 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 899 47564.3 0.000144644 10650.7 0.5879 0.5879 983 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 799 53852.1 0.0105768 3092.92 1 1 882 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 699 60119.9 9.23497e-05 1346.44 1 1 792 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1099 41453.4 0.00316637 5343.8 1 1 1195 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 599 72702.5 0.000124803 4294.86 1 1 678 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 499 84377.2 5.5346e-05 906.847 1 1 571 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 999 47565.6 0.000121412 894.946 1 1 1086 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 699 66467.4 0.000206435 5845.27 0.7871 0.7871 782 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 899 53873.8 0.000410417 1845.8 1 1 985 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1199 41457.2 0.000367509 716.679 1 1 1304 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 599 78250.3 9.47931e-05 3647.48 1 1 673 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 799 60123 0.00109661 7919.67 1 1 902 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1099 47566.4 6.6826e-05 681.549 0.883 0.883 1199 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 699 72706.5 0.000556605 1445.63 1 1 782 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1299 41462.3 0.000383115 2198.19 1 1 1414 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 999 53887.9 1.53533e-05 1048.52 0.8852 0.8852 1094 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 599 84378.6 0.000199879 1620.96 1 1 684 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 799 66477 9.30081e-05 2420.22 1 1 887 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 899 60124.8 3.83369e-05 503.714 1 1 1008 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1199 47567.3 0.000506507 1301.43 0.424 1 1307 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1399 41462.9 0.000254605 380.322 1 1 1529 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 699 78252.7 5.40412e-05 1073.63 3.66 0.366 785 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1099 53894.3 0.000215813 1936.26 1 1 1203 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 799 72711.5 0.00113528 5148.55 1 1 891 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1299 47568.7 5.24836e-05 1680.76 1 1 1417 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 899 66481.4 0.000216632 2562.63 1 1 998 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1499 41467.1 0.0198218 8796.28 0.914 0.914 1635 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 999 60125.3 1.29679e-05 518.791 1 1 1121 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 699 84380.2 0.00185151 5386.42 1 1 795 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1199 53903.1 0.000110112 1241.79 1 1 1313 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 799 78258.2 0.000649553 1627.06 1 1 892 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1599 41492.3 0.0129236 3457.67 1 1 1740 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1399 47569.8 3.81675e-05 479.554 1 1 1527 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 899 72720.7 1.85135e-05 1816.35 0.9882 0.9882 1003 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1099 60130.6 0.000737338 4191.67 1 1 1230 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 999 66485.2 0.000341908 981.896 1 1 1108 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1699 41496.7 0.000732734 1702.64 0.8465 0.8465 1851 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1299 53906.8 9.34763e-05 923.656 1 1 1423 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1499 47570 1.23877e-05 418.363 0.75 0.75 1637 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 799 84385 0.000167549 7123.61 1 1 901 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 899 78262.7 0.000148608 7720.31 0.8779 0.8779 1005 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 999 72724 0.000234693 1993.98 3.138 0.3138 1113 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1199 60133.4 2.10611e-05 536.262 1 1 1342 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1799 41497.1 1.20854e-05 225.227 1 1 1966 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1099 66489.7 0.000399955 3167.66 1 1 1219 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1599 47571 4.53588e-05 408.88 0.4273 0.4273 1745 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1399 53907.4 5.21927e-05 594.244 1 1 1536 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1899 41497.7 0.000193107 307.674 1 1 2068 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 899 84388.1 2.53464e-05 525.415 0.6953 0.6953 1009 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1699 47572.1 3.63113e-05 696.231 1 1 1855 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1299 60134.5 5.21587e-05 1268.15 1 1 1453 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1499 53911.8 0.000724399 1277.7 1 1 1645 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1199 66495.3 0.000329665 1014.23 1 1 1328 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1099 72726.3 0.000345695 1794.17 1 1 1229 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 999 78265.6 6.30629e-05 612.142 1 1 1119 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1999 41498.1 6.99852e-05 354.583 1 1 2180 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1799 47572.8 7.89867e-05 1045.33 1 1 1959 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2099 41499 0.000822629 1621.17 0.3331 1 2288 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1399 60135.2 7.87712e-05 903.827 1 1 1565 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1599 53921.9 0.000154492 868.604 1 1 1761 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 999 84390.3 0.000333561 3542.34 1 1 1118 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1299 66497.6 0.000476151 5268.45 0.8645 0.8645 1436 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1199 72727.1 9.59143e-05 858.173 1 1 1338 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1899 47573.4 6.53276e-06 1166.3 0.1466 0.1466 2072 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1099 78268.2 9.29847e-05 3275.86 0.3925 0.3925 1232 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2199 41501.3 0.000861092 581.647 1 1 2394 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1699 53922.8 0.000117747 1091.97 1 1 1873 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1499 60141.2 0.00094956 1735.98 1 1 1677 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1999 47573.8 5.46012e-05 1042.86 1 1 2180 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2299 41502.1 7.62397e-06 345.845 1 1 2499 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1399 66500.2 0.00107395 2293.17 1 1 1546 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1299 72729.1 0.00111897 2295.82 1 1 1445 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1099 84397.5 0.0125044 10066.1 1 1 1232 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1799 53925.5 0.00212424 5233.13 1 1 1983 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1199 78269.4 6.39776e-05 1573.68 0.5615 0.5615 1344 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2399 41502.9 0.000106348 1614.16 0.5356 0.5356 2602 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1599 60146.4 0.00249471 2125.45 1 1 1782 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2099 47574.4 0.000221413 808.189 1 1 2285 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1499 66502.3 0.000406553 4733.29 1 1 1652 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2499 41504.4 7.68116e-05 354.974 0.9176 0.9176 2713 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1399 72731.4 0.000125088 936.378 0.3054 0.3054 1562 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1899 53932.4 0.00015774 1240.19 1 1 2092 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2199 47574.8 1.23406e-05 407.339 1 1 2397 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1699 60150.6 0.000412104 3147.7 0.3848 1 1894 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1199 84402.8 0.000204564 897.213 1 1 1340 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1299 78270.6 1.94315e-05 741.553 1 1 1457 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2599 41505 0.000149838 854.529 1 1 2818 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1599 66504.6 0.000821947 1550.07 1 1 1758 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2299 47575.4 1.38724e-05 316.807 0.9534 0.9534 2501 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1999 53934.3 0.000338198 1162.77 1 1 2201 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2699 41505.8 0.000659141 3097.78 1 1 2923 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1499 72733.4 7.44385e-05 1032.37 1 1 1670 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1799 60155.4 3.4464e-05 3469.77 0.6183 0.6183 2011 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2399 47575.8 6.20414e-05 795.88 0.9938 0.9938 2607 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1299 84404.2 9.94953e-05 2145.01 1 1 1456 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2099 53936.8 7.197e-05 1189.44 0.4296 1 2316 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2799 41506.1 0.000323183 1447.83 1 1 3029 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1399 78271.6 3.3548e-05 1111.65 0.5085 0.5085 1567 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1699 66507.1 0.000223535 2209.97 0.8677 0.8677 1875 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2826 41506.1 1.96893e-06 102.362 0.7307 0.7307 3057 Optimization terminated normally: Convergence detected: relative gradient magnitude is below tolerance 1899 60159.8 0.00343779 5464.14 1 1 2121 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2499 47576.4 3.33629e-05 331.547 1 1 2716 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1599 72735.3 0.000635863 1695.1 0.7759 0.7759 1780 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2199 53938.5 0.000221328 1951.74 0.6478 0.6478 2418 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2581 47576.5 2.9795e-06 144.751 1 1 2802 Optimization terminated normally: Convergence detected: relative gradient magnitude is below tolerance 1799 66508.8 4.78462e-05 930.553 0.9233 0.9233 1991 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1499 78272.9 0.000160243 887.373 1 1 1678 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1399 84406 0.00022777 892.993 1 1 1566 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1999 60161.9 6.67325e-05 490 1 1 2227 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2299 53939.3 7.88521e-06 571.325 0.7332 0.7332 2529 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1699 72736.8 3.7606e-06 863.764 0.8479 0.8479 1892 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1899 66509.3 8.1168e-05 905.145 0.9767 0.9767 2099 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2099 60163 0.000368868 926.623 1 1 2339 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1599 78273.8 4.10489e-05 1264.33 1 1 1785 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2399 53940.2 6.73056e-06 312.922 1 1 2644 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1499 84408 0.00071265 2073.99 1 1 1672 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1799 72737.1 6.86341e-06 408.629 1 1 2003 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2199 60163.4 7.35085e-06 722.505 0.5814 0.5814 2448 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1999 66509.7 7.52211e-05 2637.94 1 1 2212 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2499 53940.8 7.68893e-05 538.184 1 1 2753 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1699 78274.8 1.33752e-05 706.534 1 1 1898 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1899 72737.8 0.000109785 1173.02 0.5444 0.5444 2109 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1599 84409.1 0.000129321 880.133 1 1 1783 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2299 60163.6 8.45234e-05 496.054 1 1 2563 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2599 53941.9 0.000414645 1678.8 1 1 2866 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2099 66510.9 8.05021e-05 4425.99 0.5738 0.5738 2319 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1799 78275.4 1.35912e-05 452.554 1 1 2003 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1999 72738 4.08543e-05 432.836 1 1 2219 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2399 60164 3.36426e-05 320.256 1 1 2671 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2699 53943.5 1.4733e-05 315.708 1 1 2978 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2199 66512.7 1.53414e-05 806.308 0.404 0.404 2427 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1699 84409.3 7.83438e-05 1036.2 0.5966 0.5966 1896 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2799 53944.2 0.000237724 637.962 0.8885 0.8885 3088 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2499 60164.8 9.45755e-05 883.862 1 1 2780 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2099 72739.1 2.42214e-05 516.038 0.733 0.733 2326 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2834 53944.3 2.61709e-05 142.14 1 1 3127 Optimization terminated normally: Convergence detected: relative gradient magnitude is below tolerance 1899 78276.3 0.00158966 1048.32 1 1 2115 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2299 66513.5 0.000122705 2241.32 0.1765 1 2539 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1799 84410.1 0.000283063 2609.15 1 1 2004 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2599 60166.4 0.000200074 526.733 0.9856 0.9856 2891 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2199 72739.5 2.69038e-06 377.89 0.8271 0.8271 2433 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2205 72739.6 2.49135e-06 275.011 1 1 2439 Optimization terminated normally: Convergence detected: relative gradient magnitude is below tolerance 2399 66514.1 1.77227e-05 467.837 1 1 2652 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1999 78277.3 2.16952e-05 812.663 1 1 2229 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2699 60167.4 7.03249e-05 589.403 0.8018 0.8018 3003 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1899 84411.2 3.82642e-05 813.322 0.9069 0.9069 2115 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2499 66514.6 0.000349851 2087.18 1 1 2766 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1930 84411.3 3.76356e-06 175.989 1 1 2148 Optimization terminated normally: Convergence detected: relative gradient magnitude is below tolerance 2799 60168.5 0.00286027 2640.53 0.9726 0.9726 3112 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2099 78277.7 7.47754e-05 675.482 1 1 2337 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2599 66517.5 0.00491565 5687.02 1 1 2873 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2899 60170.7 0.00134426 2640.66 1 1 3227 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2199 78278.3 2.12138e-05 856.021 0.9282 0.9282 2448 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2699 66519.8 7.34272e-05 4038.38 0.7036 0.7036 2986 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2999 60174.3 0.000280378 1375.97 0.5095 0.5095 3339 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2299 78280.9 0.00656932 3268.62 1 1 2556 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 3099 60178 6.13993e-05 889.865 1 1 3447 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2799 66521.3 4.16981e-05 1206.39 0.8898 0.8898 3101 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 3199 60178.5 1.47253e-05 351.073 1 1 3553 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2399 78284.3 0.000158738 1015.08 1 1 2668 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2899 66521.7 0.00037649 2208.89 1 1 3208 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 3299 60178.9 0.000125052 888.653 0.2372 1 3660 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2999 66522.2 0.00318011 1912.28 1 1 3312 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2499 78286.2 3.84676e-05 480.406 1 1 2773 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 3399 60179.6 0.000323127 842.27 1 1 3774 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 3099 66523.4 3.9844e-05 926.081 1 1 3422 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2599 78286.5 0.000238348 1702.65 1 1 2881 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 3499 60180.1 2.86336e-05 272.216 1 1 3886 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 3199 66524 0.000575066 682.493 1 1 3534 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 3599 60180.9 0.000146687 396.881 1 1 3995 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2699 78286.9 8.66778e-06 272.61 1 1 2985 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 3699 60181.3 1.64493e-05 984.184 0.5172 0.5172 4111 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 3299 66524.8 5.55098e-05 547.049 0.7655 0.7655 3647 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2799 78287.3 5.36611e-05 396.942 1 1 3094 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 3799 60181.7 4.29126e-05 771.335 0.2541 1 4227 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 3399 66525.6 0.000789692 1644.68 1 1 3753 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2899 78287.8 0.000227705 525.281 1 1 3207 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 3899 60182.1 7.12764e-05 735.833 1 1 4344 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 3499 66528.2 0.00117038 2638.82 0.7423 0.7423 3865 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 2956 78288 5.70242e-06 213.812 0.9748 0.9748 3267 Optimization terminated normally: Convergence detected: relative gradient magnitude is below tolerance 3999 60182.4 0.00011037 446.408 1 1 4449 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 3599 66546.3 0.0180619 8648.95 1 1 3973 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 4099 60182.9 5.36311e-05 587.47 0.8909 0.8909 4560 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 3699 66571.9 0.000180393 2175.1 1 1 4082 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 4199 60183.2 0.000117103 495.398 1 1 4669 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 3799 66575.8 0.000630098 2798.63 1 1 4195 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 4299 60183.6 6.00607e-05 572.268 1 1 4780 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 3899 66577.4 9.44358e-05 1050.11 1 1 4305 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 4399 60183.8 4.55638e-06 212.018 0.8545 0.8545 4889 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 3999 66578.3 7.0352e-05 822.036 1 1 4420 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 4499 60183.9 6.95939e-06 217.456 1 1 4997 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 4099 66578.9 0.000239794 815.779 0.9167 0.9167 4529 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 4599 60184.1 7.71241e-06 160.082 0.353 0.353 5106 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 4699 60185.2 0.00170767 2208.65 1 1 5213 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 4199 66579.3 0.000118538 748.295 1 1 4636 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 4799 60189.1 0.00122345 2431.03 0.8251 0.8251 5322 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 4299 66579.5 1.22162e-05 690.323 0.6773 0.6773 4748 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 4323 66579.6 3.87376e-06 148.564 1 1 4772 Optimization terminated normally: Convergence detected: relative gradient magnitude is below tolerance 4899 60193.3 0.00237099 3532.41 1 1 5432 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 4999 60195 0.000128872 695.723 1 1 5546 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 5084 60195.4 4.2153e-05 147.661 1 1 5641 Optimization terminated normally: Convergence detected: relative gradient magnitude is below tolerance
df_p = performance_metrics(df_cv)
df_p
horizon | mse | rmse | mae | mape | mdape | smape | coverage | |
---|---|---|---|---|---|---|---|---|
0 | 3 days 00:00:00 | 0.004003 | 0.063267 | 0.049847 | 0.005384 | 0.004173 | 0.005380 | 0.986111 |
1 | 3 days 01:00:00 | 0.004006 | 0.063297 | 0.049836 | 0.005383 | 0.004160 | 0.005379 | 0.986111 |
2 | 3 days 02:00:00 | 0.004136 | 0.064312 | 0.050037 | 0.005405 | 0.004137 | 0.005401 | 0.984375 |
3 | 3 days 03:00:00 | 0.004651 | 0.068198 | 0.050931 | 0.005503 | 0.004137 | 0.005495 | 0.980903 |
4 | 3 days 04:00:00 | 0.005495 | 0.074128 | 0.052231 | 0.005644 | 0.004121 | 0.005631 | 0.977431 |
... | ... | ... | ... | ... | ... | ... | ... | ... |
644 | 29 days 20:00:00 | 0.023983 | 0.154864 | 0.117670 | 0.012609 | 0.009267 | 0.012619 | 0.925347 |
645 | 29 days 21:00:00 | 0.023880 | 0.154532 | 0.117301 | 0.012568 | 0.009139 | 0.012577 | 0.925347 |
646 | 29 days 22:00:00 | 0.023819 | 0.154335 | 0.117123 | 0.012548 | 0.009054 | 0.012557 | 0.925347 |
647 | 29 days 23:00:00 | 0.023717 | 0.154004 | 0.116742 | 0.012505 | 0.009054 | 0.012514 | 0.925347 |
648 | 30 days 00:00:00 | 0.023641 | 0.153756 | 0.116405 | 0.012467 | 0.009016 | 0.012476 | 0.925347 |
649 rows × 8 columns
We focus on the mean absolute percentage error (MAPE). The absolute percentage error is the absolute value of the proportion of the difference between the true value and the predicted value to the true value. MAPE is a mean of those errors:
$$ \begin{aligned} \text{MAPE}=\frac{1}{n}\sum_{t=1}^n\left|\frac{y-\hat{y}}{y}\right| \end{aligned} $$This error function can be measured in percentages and is thus easy to understand. The lower this error is, the better the prediction.
fig_cv = plot_cross_validation_metric(df_cv, metric='mape')
We could finally evaluate the better model's performance on the test set by retraining it on the aggregation of the train set and the validation set.
train, test = train_val_test_split(to_log(add_custom_seasonality(df.reset_index()), ['y']).set_index('ds'), validation=False)
model_final = better_prophet()
model_final.fit(train)
None
Initial log joint probability = -27.3283 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 99 109218 0.00197411 57025 1 1 124 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 199 109404 0.0006731 13901.9 1 1 232 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 299 109480 0.000404305 26704.6 1 1 344 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 399 109518 2.5434e-05 4222.74 0.8493 0.8493 453 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 499 109597 0.00184531 9185.27 1 1 571 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 599 109631 0.00234386 5970.27 1 1 683 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 699 109636 0.000552864 3013.85 1 1 790 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 799 109639 6.57427e-05 2679.32 1 1 899 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 899 109644 0.00023892 5312.45 0.7973 0.7973 1010 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 999 109652 0.000240615 1213.84 1 1 1118 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1099 109655 0.000647908 6339.71 1 1 1229 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1199 109659 4.26967e-05 1933.04 1 1 1344 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1299 109663 2.75591e-05 1134.48 1 1 1455 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1399 109665 0.000154011 2307 1 1 1563 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1499 109666 2.43307e-05 2543.48 0.1785 0.1785 1680 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1599 109678 4.31381e-05 9498.96 0.2018 0.2018 1790 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1699 109687 0.00129297 1894.16 1 1 1896 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1799 109688 1.20719e-05 744.375 1 1 2004 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 1827 109688 5.49889e-06 257.562 1 1 2033 Optimization terminated normally: Convergence detected: relative gradient magnitude is below tolerance
future = model_final.make_future_dataframe(periods=len(test), freq='H')
future = add_custom_seasonality(future)
forecast = model_final.predict(future)
fig1 = model_final.plot(forecast, figsize=(15,7))
Note that for Prophet models, the wider the blue bands (relative to the predictions), the more likely the model is overfit. Here, we see that this is not the case. Now, we evaluate the model on the test set:
plot(to_exp(forecast, ['yhat', 'yhat_lower', 'yhat_upper']), to_exp(test, ['y']), '2019-12-01 00:00:00', '2020-01-01 00:00:00')
Visually, it seems like the model is a good fit although it overpredicts on Christmas holidays. Now we need to check the error of the model on the test set.
from sklearn.metrics import mean_squared_error
ytrue = to_exp(test, ['y'])['y']
ypred = to_exp(forecast, ['yhat']).set_index('ds').loc['2019-12-01 00:00:00':'2020-01-01 00:00:00'].reset_index()['yhat']
rmse = np.sqrt(mean_squared_error(ytrue, ypred))
print(f"The RMSE of the better model is: {rmse:.2f}")
print(f"The MAPE of the better model is: {mape(ytrue, ypred):.2f}")
The RMSE of the better model is: 1572.73 The MAPE of the better model is: 0.09
We could also repeat the same procedure for the default model above.
train, test = train_val_test_split(df, validation=False)
m = Prophet(interval_width=0.95)
m.fit(train)
future = m.make_future_dataframe(periods=len(test), freq='H')
forecast = m.predict(future)
None
Initial log joint probability = -670.657 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 99 48961.9 0.000882737 253.515 0.6919 0.6919 128 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 199 48965.1 0.000924023 219.26 1 1 257 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 299 48966.1 0.000153786 107.778 1 1 385 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 377 48966.6 2.74294e-05 142.61 1.574e-07 0.001 541 LS failed, Hessian reset 399 48966.8 0.000678339 128.448 9.676 0.9676 565 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 442 48966.9 4.87531e-05 196.73 5.05e-07 0.001 653 LS failed, Hessian reset 499 48967 5.57503e-05 98.4578 1 1 725 Iter log prob ||dx|| ||grad|| alpha alpha0 # evals Notes 519 48967 2.36949e-05 68.7138 3.389e-07 0.001 785 LS failed, Hessian reset 530 48967 1.59626e-06 66.2776 0.04548 1 800 Optimization terminated normally: Convergence detected: relative gradient magnitude is below tolerance
ypred2 = forecast.set_index('ds').loc['2019-12-01 00:00:00':'2020-01-01 00:00:00'].reset_index()['yhat']
rmse2 = np.sqrt(mean_squared_error(test['y'], ypred2))
print(f"The RMSE of the default model is: {rmse2:.2f}")
print(f"The MAPE of the default model is: {mape(test['y'], ypred2):.2f}")
The RMSE of the default model is: 2676.30 The MAPE of the default model is: 0.18
The MAPE of the better model is half that of the default model. The RMSE is also much lower. We successfully improved on the default model.
The purpose of the validation set was to help tune the hyperparameters of the Prophet models. This is not of great importance to this project, but we could try doing it in the future.