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.