Time Series Components
Decompose a time series into trend, seasonality, and residual — tell additive from multiplicative patterns, and meet stationarity.
Every dataset so far treated rows as interchangeable — shuffle them and nothing changes. Time series data breaks that assumption: each row has a timestamp, and the order carries information. Monthly airline passengers, daily temperatures, hourly server load — to forecast them you first need to see what they're made of. This lesson teaches classical decomposition: splitting a series into trend, seasonality, and residual, and knowing when those pieces add and when they multiply.
What makes time series special
Two things separate a time series from an ordinary table. First, order matters: the value in March 1955 sits between February and April 1955, and swapping rows destroys the phenomenon you're studying. Second, observations are correlated with their own past — a property called autocorrelation. This month's passenger count looks a lot like last month's, and a lot like the same month last year. That correlation is bad news for the i.i.d. assumptions behind standard cross-validation (much more on that next lesson), but it's also the entire reason forecasting works: if the past said nothing about the future, there would be nothing to model.
The classical view says an observed series y(t) is built from a few
interpretable components:
- Trend — the long-term direction of the mean (growth, decline, or flat)
- Seasonality — a repeating pattern with a fixed, known period (12 months, 7 days, 24 hours)
- Cycles — longer up-and-down swings without a fixed period, like business cycles; harder to model, often lumped in with trend
- Residual (noise) — whatever irregular fluctuation is left over
Build one yourself. Mix a trend, a seasonal wave, and noise below, then switch to the Decompose view and watch the machine take your recipe apart:
Time series: trend + seasonality + noise
Every series is a sum of parts. Dial each one in, then hit Decompose to recover them with a centered 12-month moving average — the way classical decomposition works.
That round trip — compose, then decompose — is the core idea of the lesson. Real data arrives pre-mixed; decomposition recovers the recipe.
Additive or multiplicative?
The components can combine two ways:
- Additive:
y(t) = Trend + Seasonality + Residual— seasonal swings have roughly the same size everywhere, whether the level is high or low. - Multiplicative:
y(t) = Trend x Seasonality x Residual— seasonal swings are a percentage of the level, so they grow as the trend grows.
The diagnostic is one glance at the plot: do the seasonal peaks get taller as the series rises? Monthly births in New York wiggle by about the same amount in every decade — additive. Classic airline-passenger data shows summer bumps that balloon as air travel grows — multiplicative. Let's generate one of each and see the signature:
Same trend, same seasonal shape — but in the bottom panel the peaks fan out like a megaphone. When you see that fan, decompose multiplicatively (or take the logarithm of the series, which turns multiplication into addition and lets you use additive tools).
Extracting the trend with moving averages
The oldest trend extractor is the moving average: replace each point
with the mean of a window around it. Choose the window to match the seasonal
period — a centered 12-month window on monthly data averages over exactly
one full cycle, so the seasonal ups and downs cancel and only the trend
survives. In pandas that's one call to .rolling():
That second panel is a hand-rolled seasonal component: divide the series by its trend, then average the leftover ratio month by month. July sits about 30% above trend, February about 15% below — the recipe recovered. (For an additive series you'd subtract the trend instead of dividing, and average the differences.) Whatever remains after removing both trend and seasonality is the residual, and eyeballing it is a quality check: leftover pattern in the residual means your decomposition missed something.
Decomposition in one line: statsmodels
seasonal_decompose from statsmodels automates the whole procedure —
moving-average trend, per-period seasonal averages, residual. It doesn't run
in the browser, so drop this in the downloaded notebook or Colab (the CSV
is the classic 1949–1960 airline-passenger series):
import pandas as pd
from statsmodels.tsa.seasonal import seasonal_decompose
url = "https://raw.githubusercontent.com/jbrownlee/Datasets/master/airline-passengers.csv"
df = pd.read_csv(url, parse_dates=["Month"], index_col="Month")
result = seasonal_decompose(df["Passengers"], model="multiplicative", period=12)
fig = result.plot()
fig.set_size_inches(10, 7)
# The pieces are pandas Series you can reuse:
# result.trend, result.seasonal, result.resid, result.observedTry model="additive" on the same data and inspect result.resid: the
residual inherits a fan shape, because the additive model can't absorb the
growing swings. A residual that still shows structure is the model telling
you it's the wrong model.
Autocorrelation plots
statsmodels also provides plot_acf(df["Passengers"], lags=50) — the
autocorrelation function. A seasonal series shows spikes at lags 12, 24,
36...; white noise shows nothing beyond lag 0. It's the standard second
plot to make after the series itself.
Stationarity and differencing
A series is stationary when its statistical properties — mean, variance,
autocorrelation — don't depend on when you look: no trend, no seasonality,
just consistent fluctuation around a stable level. Many classical models
(the ARIMA family, next lesson) require it, and almost no interesting raw
series has it. The standard fix is differencing: model the changes
y(t) - y(t-1) instead of the levels — differencing removes a trend the
same way velocity removes position. Seasonal differencing, y(t) - y(t-12)
for monthly data, removes a stable seasonal pattern the same way. When
eyeballing isn't enough, the Augmented Dickey-Fuller test
(from statsmodels.tsa.stattools import adfuller) gives a p-value: below
0.05 you can treat the series as stationary; above it, difference and test
again. The airline series fails the test raw and passes after one round of
regular plus seasonal differencing — bookkeeping that ARIMA's d parameter
does for you automatically.
Check your understanding
Q1.Which property distinguishes time series data from ordinary tabular data?
Q2.A sales series shows holiday spikes that get much bigger as the company grows. Which decomposition fits?
Q3.Why use a window of exactly 12 for a centered moving average on monthly data?
Q4.You fit an additive decomposition to a clearly multiplicative series. Where does the mistake show up?
Q5.What does differencing, y(t) - y(t-1), accomplish?
Exercise: Decompose a series by hand
Generate a synthetic additive monthly series over 8 years: trend
50 + 0.5*t, a fixed 12-value seasonal pattern of your choosing, and
Gaussian noise with standard deviation 5. Recover all three components by
hand — rolling mean for the trend, monthly group-averages of the detrended
series for the seasonality, and the leftover as residual — and plot the four
panels like seasonal_decompose would. Does your residual's standard
deviation match the noise you injected?
Next up: turning the components into forecasts — baselines, lag features, Holt-Winters, and a first honest look at ARIMA.