Seasonal Decomposition — STL and Classical
Statistics
Separating Trend, Seasonality, and Residual Components
Seasonal decomposition breaks a time series into interpretable components — underlying trend, repeating seasonal patterns, and random noise. STL and classical methods each offer advantages for different data characteristics.
-
Retail Planning — Isolate holiday shopping trends from underlying growth
-
Energy Forecasting — Separate daily and weekly cycles from long-term demand shifts
-
Healthcare — Detect flu season patterns from baseline hospital admission trends
Understanding the parts makes the whole pattern clear.
Seasonal decomposition separates a time series into trend, seasonal, and residual components, making patterns easier to identify and model.
DfAdditive Decomposition
A time series can be decomposed as:
Additive Model
Here,
- =Observed value at time t
- =Trend component
- =Seasonal component
- =Residual (remainder) component
DfMultiplicative Decomposition
When seasonal variation changes with the level of the series:
Multiplicative Model
Here,
- =Trend component
- =Seasonal component (proportion)
- =Residual component
Choosing Model Type
Use the additive model when seasonal fluctuations are constant. Use the multiplicative model when seasonal fluctuations increase with the level of the series. The log transform converts multiplicative to additive.
Classical Decomposition
Moving Average Method
For additive decomposition with period :
Centered Moving Average
Here,
- =Seasonal period (e.g., 12 for monthly data)
Steps:
-
Compute the centered moving average to estimate
-
Detrend: (additive) or (multiplicative)
-
Average detrended values by season to get
-
Residual:
Classical Method Limitations
Classical decomposition assumes the seasonal pattern is fixed and does not adapt to changes. It also loses endpoints (first and last observations). For modern data, prefer STL.
STL Decomposition
Seasonal and Trend decomposition using Loess (Cleveland et al., 1990) is a more robust and flexible method.
DfSTL Algorithm
STL iteratively applies Loess smoothing (local regression) to estimate trend and seasonal components. It handles any seasonal period and is robust to outliers.
STL Advantages
| Feature | Classical | STL |
|---------|-----------|-----|
| Handles any period | No (fixed s) | Yes |
| Robust to outliers | No | Yes |
| Seasonal pattern adapts | No | Yes |
| Handles missing data | Limited | Yes |
| Computational speed | Fast | Moderate |
Strength of Seasonal Component
The strength of the seasonal component measures how much of the variation is due to seasonality.
Seasonal Strength
Here,
- =Seasonal strength (0 to 1)
- =Residual component
- =Seasonal component
| Value | Interpretation |
|-------|---------------|
| 0.6 - 1.0 | Strong seasonality |
| 0.3 - 0.6 | Moderate seasonality |
| 0.0 - 0.3 | Weak seasonality |
Python Implementation
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from statsmodels.tsa.seasonal import seasonal_decompose, STL
# Simulate monthly data with trend and seasonality
np.random.seed(42)
n = 240
t = np.arange(n)
trend = 100 + 0.5 * t
seasonal = 10 * np.sin(2 * np.pi * t / 12)
noise = np.random.randn(n) * 2
y = trend + seasonal + noise
dates = pd.date_range('2005', periods=n, freq='M')
ts = pd.Series(y, index=dates)
# Classical decomposition
result_classical = seasonal_decompose(ts, model='additive', period=12)
# STL decomposition
stl = STL(ts, period=12, robust=True)
result_stl = stl.fit()
# Plot comparison
fig, axes = plt.subplots(4, 2, figsize=(14, 10))
for i, (comp, label) in enumerate(zip(['observed','trend','seasonal','resid'],
['Observed','Trend','Seasonal','Residual'])):
axes[i, 0].plot(result_classical.observed if comp=='observed'
else getattr(result_classical, comp))
axes[i, 0].set_title(f'Classical - {label}')
axes[i, 1].plot(result_stl.observed if comp=='observed'
else getattr(result_stl, comp))
axes[i, 1].set_title(f'STL - {label}')
plt.tight_layout()
plt.show()
# Seasonal strength
var_resid = np.var(result_stl.resid)
var_season_resid = np.var(result_stl.seasonal + result_stl.resid)
F_s = 1 - var_resid / var_season_resid
print(f"Seasonal Strength: {F_s:.3f}")
Worked Example
Example: Airline Passengers
Monthly airline passenger data (1949-1960) shows:
-
Increasing trend from 100 to 500+ passengers
-
Multiplicative seasonality — amplitude grows with level
-
Period: 12 months
Steps:
-
Apply log transform: to convert multiplicative to additive
-
Run STL with period = 12
-
Trend: Smooth upward curve from 4.6 to 6.2 (log scale)
-
Seasonal: Regular pattern oscillating around 0
-
Residual: Random noise, no remaining patterns
-
Seasonal strength: -> strong seasonality
Key Takeaways
Summary: Seasonal Decomposition
-
Decompose series into trend , seasonal , and residual
-
Additive model: (constant seasonal variation)
-
Multiplicative model: (growing variation)
-
STL is preferred: handles any period, robust to outliers, adaptive
-
Seasonal strength quantifies how seasonal the data is
-
Use decomposition to check if residuals look like white noise
Related Topics
-
See Stationarity for testing stationarity of components
-
See ARIMA Models for forecasting after decomposition
-
See Exponential Smoothing for Holt-Winters with seasonality