Monday, 24 August 2026

Forecasting Airline Passengers with Triple Exponential Smoothing (Holt-Winters)

Time series forecasting is one of the most practical skills in data science. In this short post, I’ll walk you through a classic example using Triple Exponential Smoothing (also known as the Holt-Winters method) on the famous Airline Passengers dataset.

What is Triple Exponential Smoothing?Simple exponential smoothing only captures the level of a series.
Double exponential smoothing adds a trend component.
Triple exponential smoothing goes one step further — it also captures seasonality
This makes it especially useful for data that shows:
  • A clear trend (upward or downward)
  • Repeating seasonal patterns (e.g., yearly cycles)
The method works by maintaining three equations:
  • One for the level
  • One for the trend
  • One for the seasonal component
The DatasetWe used the classic International Airline Passengers dataset (monthly totals from January 1949 to December 1960). This series is perfect for demonstration because it has:
  • A strong upward trend
  • Clear yearly seasonality (more passengers in summer, fewer in winter)
  • Increasing seasonal amplitude over time
Approach
  1. Split the data
    • Training set: All data except the last 24 months
    • Test set: The final 24 months (used to evaluate performance)
  2. Fit a Holt-Winters model with:
    • Multiplicative trend
    • Multiplicative seasonality
    • Seasonal period = 12 (yearly pattern)
  3. Generate forecasts:
    • Forecast the 24-month test period
    • Extend the forecast another 5 years into the future



ResultsThe model captured both the rising trend and the seasonal peaks/troughs reasonably well. On the test set it achieved solid accuracy (typically around 5–8% MAPE depending on the exact run).When we extended the forecast five years ahead, the model continued the upward trajectory while preserving the yearly seasonal pattern — exactly what we would expect from this type of data.




Python Code by Grok

# ============================================================
# Triple Exponential Smoothing (Holt-Winters)
# Classic Airline Passengers + 5-Year Future Forecast
# ============================================================

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from statsmodels.tsa.holtwinters import ExponentialSmoothing
from sklearn.metrics import mean_absolute_error, mean_squared_error

# ------------------------------------------------------------
# 1. Load the classic Airline Passengers data
# ------------------------------------------------------------
url = "https://raw.githubusercontent.com/jbrownlee/Datasets/master/airline-passengers.csv"
data = pd.read_csv(url, parse_dates=['Month'], index_col='Month')
data.columns = ['Passengers']
data = data.asfreq('MS')   # Monthly frequency

print("Data shape:", data.shape)
print(data.head())
print(data.tail())

# ------------------------------------------------------------
# 2. Train / Test Split
# ------------------------------------------------------------
train = data.iloc[:-24]   # All data except last 24 months
test  = data.iloc[-24:]   # Last 24 months (2 years)

print(f"\nTrain size : {len(train)} months")
print(f"Test size  : {len(test)} months")

# ------------------------------------------------------------
# 3. Build the Holt-Winters model
# ------------------------------------------------------------
"""
Parameters:
- trend='mul'           → multiplicative trend
- seasonal='mul'        → multiplicative seasonality
- seasonal_periods=12   → yearly seasonality (monthly data)
"""

model = ExponentialSmoothing(
    train['Passengers'],
    trend='mul',
    seasonal='mul',
    seasonal_periods=12,
    damped_trend=False
)

# ------------------------------------------------------------
# 4. Fit the model
# ------------------------------------------------------------
fitted_model = model.fit(optimized=True)

print("\n===== Model Summary =====")
print(fitted_model.summary())

# ------------------------------------------------------------
# 5. Forecast
# ------------------------------------------------------------
# Forecast for the test period (24 months)
forecast_test = fitted_model.forecast(len(test))

# Forecast additional 5 years (60 months) into the future
future_steps = 60
forecast_future = fitted_model.forecast(len(test) + future_steps)

# Extract only the future 5-year part
forecast_5years = forecast_future[-future_steps:]

# Create datetime index for the 5-year forecast
last_date = test.index[-1]
future_index = pd.date_range(
    start=last_date + pd.offsets.MonthBegin(1),
    periods=future_steps,
    freq='MS'
)

# ------------------------------------------------------------
# 6. Evaluation (on test set only)
# ------------------------------------------------------------
mae  = mean_absolute_error(test['Passengers'], forecast_test)
rmse = np.sqrt(mean_squared_error(test['Passengers'], forecast_test))
mape = np.mean(np.abs((test['Passengers'] - forecast_test) / test['Passengers'])) * 100

print(f"\nTest MAE  : {mae:.2f}")
print(f"Test RMSE : {rmse:.2f}")
print(f"Test MAPE : {mape:.2f}%")

# ------------------------------------------------------------
# 7. Main Plot (Train + Test + Forecast on Test + 5-Year Future)
# ------------------------------------------------------------
plt.figure(figsize=(15, 6))

plt.plot(train.index, train['Passengers'], label='Train', color='steelblue')
plt.plot(test.index,  test['Passengers'],  label='Test (Actual)', color='green')

plt.plot(test.index, forecast_test, label='Forecast (Test period)',
         color='red', linestyle='--', linewidth=2)

plt.plot(future_index, forecast_5years, label='Forecast (Next 5 years)',
         color='darkorange', linestyle='--', linewidth=2)

plt.title('Triple Exponential Smoothing (Holt-Winters)\nAirline Passengers + 5-Year Future Forecast')
plt.xlabel('Date')
plt.ylabel('Passengers (thousands)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

# ------------------------------------------------------------
# 8. In-sample Fit Plot
# ------------------------------------------------------------
plt.figure(figsize=(14, 5))
plt.plot(train.index, train['Passengers'], label='Actual (Train)')
plt.plot(train.index, fitted_model.fittedvalues, label='Fitted values', alpha=0.85)
plt.title('In-sample Fit of Holt-Winters')
plt.xlabel('Date')
plt.ylabel('Passengers (thousands)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()



No comments:

Post a Comment