The Four Classical Components
Every time series can be conceptually separated into four additive (or multiplicative) components:
- Trend (<!--MATHBLOCK2-->): the long-run direction — upward, downward, or flat. Monthly global CO₂ concentrations have an unmistakable upward trend.
- Seasonality (<!--MATHBLOCK3-->): periodic fluctuations with a known, fixed period — daily, weekly, annual. Retail sales spike every December.
- Cycle (<!--MATHBLOCK4-->): quasi-periodic swings with variable duration, typically longer than a year. Business cycles and sunspot cycles are canonical examples. In practice, cycles are often lumped with the trend.
- Irregular / Noise (<!--MATHBLOCK5-->): the residual after the other components are removed.
Additive vs. Multiplicative Models
In the additive model the components simply sum:
This is appropriate when the amplitude of the seasonal swings stays roughly constant regardless of the level of the series.
In the multiplicative model the components multiply:
This is appropriate when the seasonal swings scale with the level — a common pattern in economic and demand data. Multiplicative decomposition can be converted to additive by taking logarithms: $\log(Y_t) = \log(T_t) + \log(S_t) + \ldots$
Classical Decomposition with statsmodels
Classical decomposition estimates the trend by a centered moving average and recovers the seasonal component by averaging over corresponding periods:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from statsmodels.tsa.seasonal import seasonal_decompose
# Monthly airline passengers (classic Box-Jenkins dataset)
url = "https://raw.githubusercontent.com/jbrownlee/Datasets/master/airline-passengers.csv"
df = pd.read_csv(url, parse_dates=["Month"], index_col="Month")
df.columns = ["passengers"]
result = seasonal_decompose(df["passengers"], model="multiplicative", period=12)
fig, axes = plt.subplots(4, 1, figsize=(10, 8), sharex=True)
for ax, component, label in zip(
axes,
[df["passengers"], result.trend, result.seasonal, result.resid],
["Observed", "Trend", "Seasonal", "Residual"],
):
ax.plot(component)
ax.set_ylabel(label)
plt.tight_layout()
plt.show()The multiplicative model is chosen here because the height of the seasonal spikes grows proportionally with the passenger counts — a tell-tale sign visible in the raw plot.
STL Decomposition
Classical decomposition has two weaknesses: it cannot handle every period length cleanly, and it assumes the seasonal pattern is constant over time. STL (Seasonal-Trend decomposition using Loess) removes both restrictions:
from statsmodels.tsa.seasonal import STL
stl = STL(df["passengers"], period=12, robust=True)
stl_result = stl.fit()
stl_result.plot()
plt.tight_layout()
plt.show()
print(stl_result.seasonal.head(12)) # first year of seasonal factorsThe robust=True flag uses iteratively reweighted least squares to down-weight outliers. STL is the decomposition method of choice for modern practitioners.
Interpreting Residuals
After decomposition, inspect the residual component carefully:
- Residuals should look like white noise — no obvious pattern, no heteroskedasticity.
- Remaining autocorrelation in residuals signals that the decomposition has not captured all structure (see the ACF/PACF section).
- Large isolated spikes often correspond to known events (strikes, pandemics, data entry errors).
A common pitfall is treating decomposition output as a forecast. It is not. Decomposition is a diagnostic tool that helps you understand what you are dealing with before choosing a model.