Holt-Winters: Adding Seasonality to the Mix

SES models the level, while DES builds on this by incorporating a trend component. Naturally, this leads to the question of how to handle seasonality. For that reason, Triple Exponential Smoothing, better known as the Holt-Winters method—extends the DES framework by introducing a third component. Specifically, it adds a seasonal term that captures recurring patterns, updating it over time in the same way as the level and trend.

Before getting into the equations, here is where this fits in the family of methods:

MethodLevelTrendSeasonalityParameters
SESα
DES (Holt)α, β
TES (Holt-Winters)α, β, γ

The equations

Holt-Winters maintains three smoothed components. Both additive and multiplicative forms exist — shown here for reference, with the multiplicative form used throughout this post.

Additive trend & seasonal

ℓ_t  =  α · (y_t − s_{t−p})  +  (1 − α) · (ℓ_{t−1} + b_{t−1})b_t  =  β · (ℓ_t − ℓ_{t−1})  +  (1 − β) · b_{t−1}s_t  =  γ · (y_t − ℓ_t)  +  (1 − γ) · s_{t−p}ŷ_{t+m}  =  ℓ_t + m·b_t + s_{t−p+1+(m−1) mod p}

Multiplicative trend & seasonal

ℓ_t  =  α · (y_t / s_{t−p})  +  (1 − α) · (ℓ_{t−1} · b_{t−1})b_t  =  β · (ℓ_t / ℓ_{t−1})  +  (1 − β) · b_{t−1}s_t  =  γ · (y_t / ℓ_t)  +  (1 − γ) · s_{t−p}ŷ_{t+m}  =  ℓ_t · b_t^m · s_{t−p+1+(m−1) mod p}

Variables:

  • y_t — actual observed value at time t
  • ℓ_t — smoothed level at time t
  • b_t — smoothed trend (absolute change per step in additive, growth factor in multiplicative)
  • s_t — smoothed seasonal component at time t
  • s_{t−p} — seasonal estimate from the same position one full cycle ago
  • p — seasonal period (number of time steps in one full cycle)
  • m — forecast horizon (steps ahead)
  • α, β, γ — smoothing parameters for level, trend, and seasonality (each 0 to 1)

Reading the seasonal equation: in the multiplicative form, γ · (y_t / ℓ_t) is the current deviation of the observation from the smoothed level — how much bigger or smaller this period is relative to the baseline. This blends with s_{t−p}, the estimate from the same season last cycle. A high γ adapts the seasonal pattern quickly; a low γ keeps it stable.

Reading the forecast equation: s_{t−p+1+(m−1) mod p} picks the seasonal index from the right position in the most recent cycle — the seasonal factor from the last time this particular month occurred.

Setting the seasonal period

The parameter p tells the model how many time steps make up one complete seasonal cycle. For monthly data with annual seasonality, p = 12: every 12 months the pattern repeats. Getting this wrong produces forecasts that oscillate at the wrong frequency entirely.

Figure 1. Effect of seasonal_periods on forecast quality. p = 12 is correct for this data.

With p = 12 the forecast captures the annual cycle correctly (MAE 13.1). Moreover, with p = 6 the model assumes a half-yearly cycle, producing two peaks per year where there should be one. With p = 24 it assumes a two-year cycle, sending the seasonal pattern out of phase. The seasonal period is not a tuning parameter to grid-search — it’s a structural decision that has to come from understanding the data.

Additive vs multiplicative

Both trend and seasonal components can be additive or multiplicative, giving four possible combinations. The right choice depends on whether the seasonal amplitude and trend scale with the level of the series.

Figure 2. All four structural combinations with α=0.3, β=0.2, γ=0.9.

The additive seasonal models (top row) produce seasonal swings of constant height throughout the forecast — they fit the early test period but underestimate amplitude later. The multiplicative seasonal models (bottom row) grow their swings proportionally, matching what actually happens. The best result here is trend=mul, seasonal=mul at MAE 13.1, the structurally correct choice for this series.

Fitting TES and optimising

With α = β = γ = 0.3 and the fully multiplicative model:

Figure 3. TES multiplicative with default parameters. MAE = 28.3.

The seasonal pattern is captured and the trend is tracked. MAE 28.3 is already a large improvement over DES (53.4). After optimising the three parameters by grid search:

Figure 4. TES with optimised α=0.20, β=0.20, γ=0.90. MAE = 13.1.

As a result, the optimized parameters are α = 0.20, β = 0.20, γ = 0.90, with a test MAE of 13.1. Low α and β mean the level and trend update slowly, the model trusts its accumulated history. But the high γ means the seasonal component adapts aggressively, the right behaviour for a series whose seasonal amplitude keeps growing.

The full picture

Figure 5. MAE progression: Naive → SES → DES → TES.

Each step adds one structural component and roughly halves the error: Naive (107.7) → SES (90.2) → DES (53.4) → TES default (28.3) → TES optimised (13.1). The improvement from DES to TES default alone, before any optimisation, is larger than the improvement from any amount of tuning within a structurally wrong model.

A note on data leakage

When parameters are chosen by minimising test MAE, we’ve used the test set to make a modelling decision. However, in a richer dataset, the right approach is a three-way split: training data to fit the model, a validation set to tune parameters, and a test set held out entirely for final evaluation.

With 144 months total, a three-way split would leave too little data for each purpose. The two-way split used here is a practical compromise, but the reported MAE of 13.1 is slightly optimistic as a result.

Figures generated using the airline passengers dataset (monthly, January 1949–December 1960).

Leave a Reply

Create a website or blog at WordPress.com

Up ↑

Discover more from Writing my way through ideas.

Subscribe now to keep reading and get access to the full archive.

Continue reading