Time Series Forecasting with flights Dataset
This notebook demonstrates time series forecasting using three popular models: Holt-Winters, ARIMA, and Prophet. We will apply these models to the flights dataset, evaluate their performance, and then explore the effect of a log transformation on forecasting accuracy.
Initial Data Loading, Preprocessing, and Model Setup
First, we load the flights dataset, preprocess it to a suitable format for time series analysis, and then set up and train our initial forecasting models (Holt-Winters, ARIMA, and Prophet) on the entire dataset. Finally, we’ll visualize their initial forecasts.

# Import necessary libraries for data manipulation, visualization, and time series forecastingimport pandas as pdimport seaborn as snsimport matplotlib.pyplot as plt# Import Holt-Winters model for exponential smoothingfrom statsmodels.tsa.holtwinters import ExponentialSmoothing# Import ARIMA model for AutoRegressive Integrated Moving Averagefrom statsmodels.tsa.arima.model import ARIMA# Import Prophet model for forecasting, developed by Facebookfrom prophet import Prophet# -----------------------------# Load the dataset and preprocess it# -----------------------------# Load the 'flights' dataset from seaborn examplesflights = sns.load_dataset('flights')# Convert 'month' and 'year' columns into a single datetime column 'ds' (Date Stamp)flights['month'] = pd.to_datetime(flights['month'].astype(str) + flights['year'].astype(str), format='%b%Y')# Select and rename columns to 'ds' and 'y' as required by Prophet and common in time series analysisflights = flights[['month', 'passengers']].rename(columns={'month':'ds','passengers':'y'})# Set the 'ds' column as the DataFrame indexflights.set_index('ds', inplace=True)# Explicitly set the frequency of the time series to 'Month Start' to avoid warnings and ensure consistencyflights = flights.asfreq('MS')# Reset index to access 'ds' as a column and extract year and monthflights_heatmap_data = flights.reset_index().copy()flights_heatmap_data['year'] = flights_heatmap_data['ds'].dt.yearflights_heatmap_data['month'] = flights_heatmap_data['ds'].dt.strftime('%b') # Month abbreviation# Create a pivot table for the heatmap: months on y-axis, years on x-axisheatmap_data = flights_heatmap_data.pivot_table(index='month', columns='year', values='y')# Define the order of months for correct plottingmonth_order = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']heatmap_data = heatmap_data.reindex(month_order)# Plot the heatmapplt.figure(figsize=(12, 8))sns.heatmap(heatmap_data, cmap='YlGnBu', fmt=".0f", linewidths=.5, cbar_kws={'label': 'Passengers'})plt.title('Monthly Passenger Traffic (1949-1960)')plt.xlabel('Year')plt.ylabel('Month')plt.show()

# -----------------------------# Holt-Winters Model: Initial Training and Forecasting# -----------------------------# Initialize Holt-Winters Exponential Smoothing model with additive trend and seasonal components# Seasonal period is set to 12 as the data is monthly and shows yearly seasonalityhw_model = ExponentialSmoothing(flights['y'], trend='add', seasonal='add', seasonal_periods=12)# Fit the Holt-Winters model to the entire datasethw_fit = hw_model.fit()# Forecast the next 24 periods (2 years) using the fitted modelhw_forecast = hw_fit.forecast(24)# -----------------------------# ARIMA Model: Initial Training and Forecasting# -----------------------------# Initialize ARIMA model with an order of (2,1,2). These parameters (p,d,q) are chosen arbitrarily here# but would typically be determined through auto_arima or ACF/PACF analysis.arima_model = ARIMA(flights['y'], order=(2,1,2))# Fit the ARIMA model to the entire datasetarima_fit = arima_model.fit()# Forecast the next 24 periods (2 years) using the fitted modelarima_forecast = arima_fit.forecast(24)# -----------------------------# Prophet Model: Initial Training and Forecasting# -----------------------------# Reset index and rename columns to 'ds' and 'y', as Prophet expects these specific column namesprophet_df = flights.reset_index()# Initialize Prophet model, enabling yearly seasonalityprophet_model = Prophet(yearly_seasonality=True)# Fit the Prophet model to the entire datasetprophet_model.fit(prophet_df)# Create a future DataFrame for forecasting, specifying 24 periods with 'Month Start' frequencyfuture = prophet_model.make_future_dataframe(periods=24, freq='MS')# Generate forecasts using the fitted Prophet modelprophet_forecast = prophet_model.predict(future)# -----------------------------# Plotting: Comparison of Initial Forecasts# -----------------------------# Set the figure size for better visualizationplt.figure(figsize=(14,6))# Plot the original 'flights' dataplt.plot(flights.index, flights['y'], label='Original Data', color='black')# Plot the Holt-Winters forecastplt.plot(hw_forecast.index, hw_forecast, label='Holt-Winters Forecast', color='blue')# Plot the ARIMA forecastplt.plot(arima_forecast.index, arima_forecast, label='ARIMA Forecast', color='red')# Plot the Prophet forecast. 'yhat' is the predicted value from Prophetplt.plot(prophet_forecast['ds'], prophet_forecast['yhat'], label='Prophet Forecast', color='green')# Add title and legend for clarityplt.title("Comparison of Holt-Winters, ARIMA, and Prophet Forecasts")plt.legend()# Display the plotplt.show()

Time Series Decomposition
Let’s decompose the original ‘flights’ time series to understand its underlying components: trend, seasonality, and residuals (random noise). This helps in understanding the structure of the data that the forecasting models are trying to capture.
from statsmodels.tsa.seasonal import seasonal_decompose# Perform additive decomposition as the seasonal fluctuations seem to be consistent over time.# The period is set to 12 for yearly seasonality in monthly data.decomposition = seasonal_decompose(flights['y'], model='additive', period=12)# Plot the decomposed components (Original, Trend, Seasonal, Residual)fig = decomposition.plot()# Set a good figure size for the plotfig.set_size_inches(10, 8)# Adjust layout to prevent labels from overlappingplt.tight_layout()# Display the plotplt.show()

Mean Absolute Error (MAE) Calculation
Now, let’s calculate the Mean Absolute Error (MAE) for each of the forecasting models (Holt-Winters, ARIMA, and Prophet) to quantify how well their in-sample predictions match the actual historical data. A lower MAE indicates a better fit.
from sklearn.metrics import mean_absolute_error# --- Holt-Winters MAE Calculation (In-Sample) ---# Calculate MAE for Holt-Winters using the fitted values against the original datahw_mae = mean_absolute_error(flights['y'], hw_fit.fittedvalues)print(f"Holt-Winters MAE: {hw_mae:.2f}")# --- ARIMA MAE Calculation (In-Sample) ---# Calculate MAE for ARIMA using the fitted values against the original dataarima_mae = mean_absolute_error(flights['y'], arima_fit.fittedvalues)print(f"ARIMA MAE: {arima_mae:.2f}")# --- Prophet MAE Calculation (In-Sample) ---# Prophet's predict method on the original dataframe gives in-sample predictions. # We use 'yhat' from the predictions DataFrame for MAE calculation.prophet_predictions = prophet_model.predict(prophet_df)prophet_mae = mean_absolute_error(prophet_df['y'], prophet_predictions['yhat'])print(f"Prophet MAE: {prophet_mae:.2f}")
Holt-Winters MAE: 9.41
ARIMA MAE: 20.85
Prophet MAE: 17.33
Forecast Accuracy with Train/Test Split
To get a more robust evaluation of each model’s forecasting ability, we will split the data into a training set and a test set. We’ll train the models on the training data and then evaluate their predictions on the unseen test data. Let’s use the last 12 months for the test set.
# Define the split point for training and testing data. Using '1960-01-01' to reserve the last 12 months for testing.split_point = '1960-01-01'# Create the training set: all data before the split_pointtrain = flights.loc[flights.index < split_point]# Create the test set: all data from the split_point onwardstest = flights.loc[flights.index >= split_point]# Print the number of data points in training and test sets for verificationprint(f"Training data points: {len(train)}")print(f"Test data points: {len(test)}")# Visualize the train/test split to ensure correctnessplt.figure(figsize=(12, 6))plt.plot(train.index, train['y'], label='Training Data', color='blue')plt.plot(test.index, test['y'], label='Test Data', color='red')plt.title('Train/Test Split of Flights Data')plt.legend()plt.show()

Holt-Winters Model Evaluation
# Retrain Holt-Winters model specifically on the training datahw_model_train = ExponentialSmoothing(train['y'], trend='add', seasonal='add', seasonal_periods=12)hw_fit_train = hw_model_train.fit()# Forecast for the length of the test period using the retrained modelhw_forecast_test = hw_fit_train.forecast(len(test))# Calculate Mean Absolute Error (MAE) comparing the forecast with the actual test datahw_mae_test = mean_absolute_error(test['y'], hw_forecast_test)print(f"Holt-Winters MAE on Test Set: {hw_mae_test:.2f}")# Plot the training data, actual test data, and the Holt-Winters forecastplt.figure(figsize=(12, 6))plt.plot(train.index, train['y'], label='Training Data', color='blue')plt.plot(test.index, test['y'], label='Actual Test Data', color='red')plt.plot(hw_forecast_test.index, hw_forecast_test, label='Holt-Winters Forecast', color='green', linestyle='--')plt.title('Holt-Winters Forecast vs Actual (Test Set)')plt.legend()plt.show()
Holt-Winters MAE on Test Set: 13.38

ARIMA Model Evaluation
# Retrain ARIMA model on the training data. Using the same (p,d,q) order as before.arima_model_train = ARIMA(train['y'], order=(2,1,2))arima_fit_train = arima_model_train.fit()# Forecast for the length of the test period using the retrained modelarima_forecast_test = arima_fit_train.forecast(len(test))# Calculate Mean Absolute Error (MAE) comparing the forecast with the actual test dataarima_mae_test = mean_absolute_error(test['y'], arima_forecast_test)print(f"ARIMA MAE on Test Set: {arima_mae_test:.2f}")# Plot the training data, actual test data, and the ARIMA forecastplt.figure(figsize=(12, 6))plt.plot(train.index, train['y'], label='Training Data', color='blue')plt.plot(test.index, test['y'], label='Actual Test Data', color='red')plt.plot(arima_forecast_test.index, arima_forecast_test, label='ARIMA Forecast', color='green', linestyle='--')plt.title('ARIMA Forecast vs Actual (Test Set)')plt.legend()plt.show()
ARIMA MAE on Test Set: 41.83

Prophet Model Evaluation
# Prophet requires a DataFrame with 'ds' and 'y' columns. Prepare training and testing DataFrames accordingly.prophet_train_df = train.reset_index().rename(columns={'ds':'ds', 'y':'y'})prophet_test_df = test.reset_index().rename(columns={'ds':'ds', 'y':'y'})# Retrain Prophet model on the training data, enabling yearly seasonalityprophet_model_train = Prophet(yearly_seasonality=True)prophet_model_train.fit(prophet_train_df)# Create a future DataFrame specifically for the test period to generate forecastsfuture_prophet_test = prophet_model_train.make_future_dataframe(periods=len(test), freq='MS', include_history=False)# Generate forecasts for the test period using the retrained Prophet modelprophet_forecast_test = prophet_model_train.predict(future_prophet_test)# Calculate Mean Absolute Error (MAE) comparing Prophet's 'yhat' (prediction) with the actual test dataprophet_mae_test = mean_absolute_error(prophet_test_df['y'], prophet_forecast_test['yhat'])print(f"Prophet MAE on Test Set: {prophet_mae_test:.2f}")# Plot the training data, actual test data, and the Prophet forecastplt.figure(figsize=(12, 6))plt.plot(train.index, train['y'], label='Training Data', color='blue')plt.plot(test.index, test['y'], label='Actual Test Data', color='red')plt.plot(prophet_forecast_test['ds'], prophet_forecast_test['yhat'], label='Prophet Forecast', color='green', linestyle='--')plt.title('Prophet Forecast vs Actual (Test Set)')plt.legend()plt.show()
INFO:prophet:Disabling weekly seasonality. Run prophet with weekly_seasonality=True to override this. INFO:prophet:Disabling daily seasonality. Run prophet with daily_seasonality=True to override this.
Prophet MAE on Test Set: 33.43

Summary of Test Set MAE
# Print the calculated MAE for each model on the test set for easy comparisonprint(f"Holt-Winters MAE on Test Set: {hw_mae_test:.2f}")print(f"ARIMA MAE on Test Set: {arima_mae_test:.2f}")print(f"Prophet MAE on Test Set: {prophet_mae_test:.2f}")
Holt-Winters MAE on Test Set: 13.38
ARIMA MAE on Test Set: 41.83
Prophet MAE on Test Set: 33.43
You must be logged in to post a comment.