Skip to content
Machine Learning
Time Series 10 min read

Time Series Components

Decompose a time series into trend, seasonality, and residual — tell additive from multiplicative patterns, and meet stationarity.

Download notebook Open Google ColabIn Colab: File → Upload notebook → pick the downloaded file.

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.

-100102030yr 0yr 1yr 2yr 3yr 4yr 5yr 6composed · trend · seasonal
slope0.80amplitude4.0noise σ1.0
Seasonal shape

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:

Python — runs in your browser

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():

Python — runs in your browser

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.observed

Try 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

5 questions · free
  1. Q1.Which property distinguishes time series data from ordinary tabular data?

  2. Q2.A sales series shows holiday spikes that get much bigger as the company grows. Which decomposition fits?

  3. Q3.Why use a window of exactly 12 for a centered moving average on monthly data?

  4. Q4.You fit an additive decomposition to a clearly multiplicative series. Where does the mistake show up?

  5. 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.