Seasonality & Decomposition
STL, additive vs multiplicative, X-13-ARIMA-SEATS, Fourier terms, irregular seasonality
A time series with both trend and seasonality is hard to model directly: the trend makes the series non-stationary, and the seasonal component repeats at a fixed period but with amplitude that may scale with the trend level. Modelling the combined series forces a single model to represent structure operating at two different timescales simultaneously. Decomposition separates these into three components — trend, seasonal, residual — so each can be modelled, forecast, and analysed independently. Choosing additive versus multiplicative decomposition is not aesthetic: it determines whether the seasonal component is a fixed absolute number or a fraction of the current level. Get it wrong and every downstream forecast inherits systematic bias. STL dominates classical decomposition for most ML work because it lets the seasonal component evolve and is robust to outliers. But STL's biggest limitation is also its most common production failure: it encodes seasonality at a fixed calendar period and cannot handle events that shift across the calendar, like Black Friday.
Key points
- Additive decomposition: Y_t = T_t + S_t + R_t. The seasonal component is a fixed absolute number regardless of the trend level — a store that always sells exactly 200 extra units in December. Multiplicative: Y_t = T_t × S_t × R_t. The seasonal component is a fraction of the current level — 20% higher December sales, so the absolute lift grows as the business grows. Diagnosing which applies: if seasonal amplitude stays constant as the trend grows, use additive; if it scales with the level, use multiplicative or log-transform first.
- Classical decomposition extracts trend via centred moving average, computes seasonal indices as per-period averages, and residualises. Two fatal flaws: the seasonal component is forced identical across all years (it cannot evolve), and trend estimates are missing at the endpoints (the first and last s/2 observations). For business time series where seasonal patterns shift over years, classical decomposition produces a systematically biased seasonal estimate for recent data — the period you most care about.
- STL (Seasonal and Trend decomposition using Loess): an inner loop that estimates both the seasonal and trend components via LOESS, wrapped in an outer robustness loop that downweights outlier-influenced points using remainder-based weights (0 iterations unless robust=True). Key parameters are period (s), trend window (t.window), and seasonal window (s.window). s.window="periodic" forces the seasonal component identical across all years — correct only when the seasonal driver is physically fixed (daylight hours). A finite s.window allows the seasonal component to evolve over time. The robust option downweights outliers in LOESS fitting so anomalies don't corrupt the seasonal estimate.
- Multiple seasonalities require iterative STL (MSTL), processing periods shortest-first. Hourly data with both daily and weekly patterns: decompose at the daily period first, then decompose the remainder at the weekly period — ascending order avoids the shorter cycle being absorbed into the longer one. Single STL cannot handle two simultaneous seasonal frequencies. This is a common failure mode for high-frequency data (hourly electricity, real-time traffic) where practitioners apply a single STL and wonder why the residuals still have structure.
- Fourier terms replace seasonal dummy variables with K pairs of sin/cos: sin(2πkt/s) and cos(2πkt/s) for k=1,...,K. The advantage: works for non-integer periods like annual seasonality in daily data (period = 365.25 days — no integer period exists for dummies) and very long periods where dummies are impractical. K controls seasonal shape complexity — K=s/2 is equivalent to dummies. Fourier terms are fitted as OLS regression coefficients, making them computationally cheap and easy to include in any regression model.
- Calendar-shifting events are where STL breaks down completely. Black Friday falls on the fourth Thursday of November — it shifts by up to six days from year to year. In STL with a fixed 52-week period, that spike smears across 3-4 weeks in the seasonal component and the peak estimate is attenuated by a factor of ~3-6. Explicit holiday calendars (Prophet, X-13) or event features (days_to_black_friday, is_black_friday_week) are the correct tool. STL applied to series with calendar-shifting events will systematically underforecast the peak.
- X-13-ARIMA-SEATS is what national statistics agencies use for official economic data. It runs ARIMA before decomposition to handle outliers, trading day effects (months have different numbers of weekdays each year), and holiday effects explicitly. More rigorous than STL; appropriate when you need auditable, reproducible decompositions on economic data. For most ML applications, STL with explicit holiday features is simpler and sufficient.
- A common pipeline: decompose → model trend, seasonal, and residual components separately → recompose. The residual after removing trend and seasonality is close to stationary, making it much easier to model with ARIMA or a neural forecaster. Forecasting on the residual alone often outperforms forecasting on the raw series because the model no longer has to simultaneously capture multiple structure types at different timescales.
The practical insight that separates strong candidates is knowing when STL fails and what to do instead. STL handles smoothly evolving seasonality at a fixed calendar period but is blind to calendar-shifting events (Black Friday, Ramadan, Easter) because it assumes the seasonal spike falls at the same calendar week each year. The diagnostic — "seasonal component smeared across 3-4 weeks, peak attenuated" — identifies STL failure, and the fix is explicit holiday features or event indicators rather than encoding the event in the seasonal component.
Recap
- Additive `Y=T+S+R` (fixed absolute season) vs multiplicative `Y=T×S×R` (season scales with level).
- Diagnose: amplitude constant → additive; amplitude grows with trend → multiplicative or log first.
- Classical decomposition: season forced identical across years + missing endpoints → biased recent estimate.
- STL: LOESS smoother; `s.window="periodic"` fixes season (correct only if driver physically fixed, e.g. daylight).
- Multiple seasonalities need MSTL (daily then weekly — shortest period first); single STL leaves residual structure.
- Fourier terms (K sin/cos pairs) handle non-integer periods like 365.25 and long periods cheaply.
- STL breaks on calendar-shifting events (Black Friday smears across 3-4 weeks) → explicit holiday features.
Check your understanding
Q1. Weekly website traffic shows seasonal amplitude doubling over 3 years while trend also doubles. Which TWO statements about additive vs multiplicative decomposition are correct?
- A) If seasonal amplitude grows in proportion to the trend level, multiplicative decomposition (or an additive fit on log(Y_t)) is the appropriate choice, not a fixed-amplitude additive model applied directly.
- B) Additive decomposition is always the more interpretable choice regardless of how the seasonal amplitude behaves relative to the trend, so it should be the default model in every single case encountered.
- C) When seasonal amplitude stays constant in absolute terms as the trend grows, additive decomposition correctly captures the seasonal component without requiring any log transformation beforehand.
- D) Classical moving-average decomposition explicitly and correctly handles multiplicative seasonality without any log transform, which is why it is generally preferred over STL for proportional seasonal patterns.
Q2. You are building a real-time anomaly detector for server error rates, which show strong daily and weekly patterns. STL fails because STL requires the series to be longer than two periods and the seasonal window must be odd. For 10-minute data with both daily (144 points/period) and weekly (1008 points/period) seasonality, what is your approach?
- A) Apply a single STL at the dominant (weekly) period and ignore the daily seasonality; residual daily patterns will average out and not materially affect anomaly detection.
- B) Use MSTL: decompose at daily period (144) first, then at weekly period (1008) — shortest period first, so the short cycle isn't absorbed into the long one. Alternatively, use Fourier terms in an online regression updated on a rolling 4-week window; use the residual as the anomaly signal.
- C) Aggregate the 10-minute data to hourly before applying STL; the coarser granularity eliminates the dual-seasonality problem and makes standard STL applicable.
- D) Apply two independent STL models — one at the daily period and one at the weekly period — then subtract both seasonal components from the raw series before thresholding.
Q3. A stakeholder asks why your Black Friday sales forecast is consistently off by 20%. Your model uses STL decomposition with a fixed 52-week seasonal pattern. What is the root cause?
- A) Black Friday shifts by up to ±3 business days yearly. STL with a fixed 52-week period smears the spike across 3-4 weeks, attenuating the peak. Fix: explicit holiday features or Prophet/X-13 holiday effects.
- B) The 20% error is due to insufficient training data alone; STL needs at least 5 full years of history to correctly identify the Black Friday spike within a fixed 52-week seasonal period.
- C) The root issue is the additive-versus-multiplicative choice; switching to multiplicative STL will correctly capture the Black Friday peak since holiday sales scale proportionally with overall revenue level.
- D) The fixed 52-week seasonal pattern is correctly specified as-is; the forecast error is caused instead by year-over-year revenue trend growth — add a separate linear trend regression on top of the decomposition.
Q4. What does it mean to set s.window="periodic" in STL, when is it correct to do so, and what is the risk of always using it?
- A) s.window="periodic" increases the number of LOESS smoothing iterations in the inner loop, making the seasonal estimate more statistically robust; the only risk of overuse is longer computation time.
- B) s.window="periodic" forces the seasonal component to evolve rapidly year over year; the risk is that it overfits recent seasonal patterns and performs poorly in the subsequent trend extrapolation step.
- C) s.window="periodic" forces the seasonal component identical across years, correct only when the driver is physically fixed (daylight hours). Risk: evolving patterns leave systematic residuals in recent years.
- D) s.window="periodic" disables the outer robustness loop in STL entirely, making the decomposition run faster but leaving it far more sensitive to outliers corrupting the resulting seasonal estimate.
Try it interactively
ML Systems Lab is a free interview-prep platform for ML engineers — work through the full interactive module, quizzes, and drills.
Open ML Systems Lab →