A quick run-through of Holt-Winters, Seasonal ARIMA and FB Prophet | by Ian Felton | Towards Data Science

github.com
The link above will take you to the notebook where the following code is sourced.
The purpose of this post is to show a quick run-through of Holt Winters, SARIMA and FB Prophet. I am skipping anything about parameter tuning as that could be multiple posts on its own.
First, lets get out imports for Holt Winters, along with the dataset.
import numpy as npimport pandas as pdimport matplotlib.pyplot as pltfrom statsmodels.tsa.holtwinters import ExponentialSmoothing%matplotlib inlineimport warningswarnings.filterwarnings("ignore")dataset = pd.read_csv('AirPassengers.csv')
The dataset can be found at the link in the beginning of this post. It’s the airline passengers dataset, which is pretty easy to find on the web.
Looking at the first two rows gives an idea of the shape.
For our analysis, we need to get the Month column into the index.
dataset['Month'] = pd.to_datetime(dataset['Month'])dataset.set_index('Month', inplace=True)
Which will get us this:
The next thing we need to do is set the index frequency and split the dataset into train and test.
dataset.index.freq = 'MS'train, test = dataset.iloc[:131, 0], dataset.iloc[131:, 0]
From here we can create and fit the model with the train data.
model = ExponentialSmoothing(train,                              trend='add',                              damped=False,                              seasonal='add',                              seasonal_periods=12).fit()
With the model ready, we can now make predictions.
holt_pred = model.predict(start=test.index[0], end=test.index[-1])
To see our predictions, we can plot it along with the train and test data.
plt.figure(figsize=(22, 10))plt.plot(train.index, train, label='Train')plt.plot(test.index, test, label='Test', color='r')plt.plot(holt_pred.index, holt_pred, label='Holt-Winters', color='k')plt.legend(loc='best', fontsize='xx-large')plt.xticks(fontsize=18)plt.yticks(fontsize=16)plt.show()
Next, let’s try out SARIMA.
import statsmodels.api as smimport itertoolsimport warningswarnings.filterwarnings("ignore")
Normally, we would be differencing and plotting ACF and PACF to determine out terms. A person should do these things, but we are going to skip this for this post. Below is a function to do a param search for SARIMA order and seasonal_order.
def find_best_sarima(train, eval_metric):        p = d = q = range(0, 2)    pdq = list(itertools.product(p, d, q))    seasonal_pdq = [(x[0], x[1], x[2], 12) for x in list(itertools.product(p, d, q))]    counter = 0    myDict = {}        for param in pdq:        for param_seasonal in seasonal_pdq:            try:                counter += 1                mod = sm.tsa.statespace.SARIMAX(train,                                                order=param,                                                seasonal_order=param_seasonal,                                                enforce_stationarity=False,                                                enforce_invertibility=False)                results = mod.fit()                myDict[counter] = [results.aic, results.bic, param, param_seasonal]            except:                continue                    dict_to_df = pd.DataFrame.from_dict(myDict,orient='index')        if eval_metric == 'aic':        best_run = dict_to_df[dict_to_df[0] == dict_to_df[0].min()].index.values        best_run = best_run[0]    elif eval_metric == 'bic':        best_run = dict_to_df[dict_to_df[1] == dict_to_df[1].min()].index.values        best_run = best_run[0]                model = sm.tsa.statespace.SARIMAX(train,                                      order=myDict[best_run][2],                                      seasonal_order=myDict[best_run][3],                                      enforce_stationarity=False,                                      enforce_invertibility=False).fit()        best_model = {'model':model,                   'aic':model.aic,                  'bic':model.bic,                  'order':myDict[best_run][2],                   'seasonal_order':myDict[best_run][3]}        return best_model
The output is a dictionary with our fitted model, along with the aic, bic, order tuple and seasonal_order tuple. We can call this function to retrieve our model.
best = find_best_sarima(train, 'aic')
With the model ready, we can now make predictions.
sarima_pred = best['model'].predict(start=test.index[0], end=test.index[-1], dynamic=True)
To see our predictions, we can plot it, along with the train and test data.
plt.figure(figsize=(22, 10))plt.plot(train.index, train, label='Train')plt.plot(test.index, test, label='Test', color='r')plt.plot(sarima_pred.index, sarima_pred, label='SARIMA', color='k')plt.legend(loc='best', fontsize='xx-large')plt.show()
Time to try out Facebook’s Prophet
from fbprophet import Prophetimport logginglogging.getLogger().setLevel(logging.ERROR)
Unlike Holt Winters and SARIMA, we need our Month column as a column, instead of an index.
df = dataset.reset_index()df.columns = ['ds', 'y']
Our prediction size will be set to the last 12 months. Our train data will be everything up until that period.
prediction_size = 12train_df = df[:-prediction_size]
Now we can create and fit our model to the train data.
m = Prophet(seasonality_mode='multiplicative')m.fit(train_df)
What we want from our prediction is a dataframe. It is worth looking into what you get out of this. It is quite a bit of useful data.
future = m.make_future_dataframe(periods=prediction_size, freq='M')forecast = m.predict(future)
Now we can plot our data.
single_plot = m.plot(forecast)
Let’s now plot the prediction with our train and test.
plt.figure(figsize=(22, 10))plt.plot(train.index, train, label='Train')plt.plot(test.index, test, label='Test', color='r')plt.plot(forecast['ds'][131:], forecast['yhat'][131:], label='Prophet', color='k')plt.legend(loc='best', fontsize='xx-large')plt.show()
Now let’s zoom in to the test period to see how they all stacked up.
plt.figure(figsize=(22, 10))plt.plot(holt_pred.index, holt_pred, label='Holt-Winters', color='b')plt.plot(sarima_pred.index, sarima_pred, label='SARIMA', color='y')plt.plot(forecast['ds'][131:], forecast['yhat'][131:], label='Prophet', color='k')plt.plot(test.index, test, label='Test', color='r')plt.legend(loc='best', fontsize='xx-large')plt.xticks(fontsize=18)plt.yticks(fontsize=16)plt.show()
So, of course, there are many different parameter tunings that could happen with any of these models. I just wanted to give a quick run-through of each against the same dataset. Thank you.