Home Chinland InfoTech Academy
Ready
All Courses

Time Series — Interactive Lab

A hands-on companion on the analysis and forecasting of data indexed by time — decomposition, stationarity, autocorrelation, smoothing, ARIMA, and forecasting evaluation. Click, drag, and tune each idea to see how it works. A sibling to the ML and Deep-Learning labs. Press Esc anytime for this menu.

Deep dives — guided, end-to-end
Concept cards — focused & interactive
Deep dive · the shape of time

Anatomy of a Time Series

A time series is just data with a clock attached — values observed in order, where when a point arrives is as meaningful as its value. That ordering changes everything: the rows are no longer interchangeable, neighbouring points are correlated, and the goal shifts from "explain this row" to "understand the past and forecast the future." Before any model, the first move is always the same — pull the series apart into the handful of patterns hiding inside it: a slow trend, a repeating seasonal swing, and the noise left over. This dive builds a series from those pieces, then reverses the process to recover them from real data.

1 · The idea

Data with an order

In ordinary tabular learning the rows are exchangeable — shuffle them and nothing is lost. A time series is the opposite: shuffle it and you destroy the very structure you care about. Each observation is tied to a timestamp, consecutive observations are correlated, and the questions are temporal — what's the underlying direction, does it repeat, is today's spike unusual, what comes next? Answering them starts with a simple decomposition.

Order is the signal. A time series is values indexed by time, and almost every technique begins by separating the patterns that ordering reveals.
2 · The components

Trend, season, noise

The classical model says any series is a sum of a few interpretable pieces:

yt = Tt + St + Rt

Trend (T)

The slow, long-run direction — growth, decline, or drift. Not necessarily a straight line.

Seasonality (S)

A pattern that repeats on a fixed period — daily, weekly, yearly. Same shape, same spacing, every cycle.

Cycles

Longer swings with no fixed length (business cycles). Often folded into the trend when the period is unknown.

Residual (R)

What's left after trend and season — ideally unstructured noise. Structure here means you've missed something.

3 · Build a series

Add the pieces up

Run the model forwards: dial a trend slope, a seasonal amplitude and period, and a dose of noise, and watch the three components stack into a single series. This is exactly how the synthetic data underneath every forecasting demo is made — and seeing it assembled makes the inverse problem (pulling it back apart) far less mysterious:

trend · season amp · period 12 · noise

Turn the seasonal amplitude to zero and the repeating ripples vanish, leaving trend plus noise; kill the noise and the series becomes a clean deterministic curve. Real series are these same ingredients in unknown proportions — and decomposition is the act of estimating them.

4 · Decomposition

Pull a real series apart

Here is the inverse, on a classic dataset: monthly international airline passengers, 1949–1960 — 144 points with an unmistakable upward trend and a yearly summer peak. Classical decomposition estimates the trend with a centered 12-month moving average, finds the seasonal shape by averaging each calendar month after detrending, and calls whatever remains the residual. The four stacked panels are the whole story of the series:

observed = trend + seasonal + residual · the residual should look like structureless noise — watch whether it does

The same computation, laid out like a spreadsheet — each column derived from the ones before it, so you can trace a single month from raw count to residual (rows 6–13, mid-1949 to early-1950):

ABCDE
t (mon)ytobservedTt=12-mo MAdt=A-BSt=avg(d) by monthRt=A-B-D
6 Jul148126.79+21.21+63.8-42.6
7 Aug148127.25+20.75+62.8-42.1
8 Sep136127.96+8.04+16.5-8.5
9 Oct119128.58-9.58-20.6+11.1
10 Nov104129.00-25.00-53.6+28.6
11 Dec118129.75-11.75-28.6+16.9
12 Jan115131.25-16.25-24.7+8.5
13 Feb126133.08-7.08-36.2+29.1
Column by column: the observed count A is the only input; the trend B is the centered 12-month moving average; detrended C = A−B strips the trend; the seasonal index D is the average of C across all same-named months (every July shares one value); and the residual E = A−B−D is what neither trend nor season explains. Row 6: 148 − 126.79 − 63.8 = −42.6 — that July sat below its own seasonal norm.

The trend panel shows steady growth; the seasonal panel shows the same yearly shape repeated twelve times; and the residual — crucially — is not flat. Its swings grow as the series rises, a tell that this series is multiplicative, not additive.

5 · Additive vs multiplicative

Do the swings grow?

The additive model y = T + S + R assumes the seasonal swing is a fixed number of passengers, the same in 1949 as in 1960. But airline seasonality is proportional — a 20% summer bump on a bigger base is a bigger absolute jump. That's the multiplicative model:

yt = Tt × St × Rt

The fix is a trick you've seen in the feature-engineering world: take logs. Since log(T × S × R) = log T + log S + log R, a multiplicative series becomes additive on the log scale, with a flat-variance residual — which is why airline-passenger forecasts are almost always done in logs. The signature to watch for: if the seasonal swings fan out as the level rises, go multiplicative (or log-transform); if they stay a constant width, additive is fine.

See it directly. Toggle between the raw passenger counts and their logarithm, with each year's seasonal high and low traced as an envelope. On the raw scale the envelope fans open as traffic grows; on the log scale the two edges run parallel — the swing has become a constant proportion:

seasonal swing, first year vs last
6 · Trend

The slow direction

The trend is the low-frequency backbone. The simplest estimate is a moving average — slide a window and average — which smooths away the season and noise to leave the drift; a window equal to the seasonal period cancels the season exactly. Trends needn't be straight: they bend, saturate, and break. Fitting a line assumes constant growth; a moving average or local regression lets the trend curve with the data, which is why decomposition prefers the smoother. The trend is what you extrapolate when you forecast the long run.

7 · Seasonality

The part that repeats

Seasonality is structure on a known, fixed period: 12 months in airline data, 7 days in web traffic, 24 hours in electricity demand — and real series often carry several at once (hour-of-day and day-of-week). You detect a candidate period from domain knowledge, from peaks in the autocorrelation, or from the calendar; you estimate its shape by averaging every point that shares a season-position after removing the trend. Subtracting the seasonal component gives the seasonally adjusted series — how economists report unemployment, so a January dip isn't mistaken for a real decline.

8 · The residual

What's left over

After trend and season are removed, the residual is the part the decomposition can't explain — and ideally it's pure noise: no trend, no repeating pattern, constant spread, centred on zero (or one, for multiplicative). If structure remains — drift, leftover seasonality, fanning variance — the decomposition has missed something, or the model is the wrong type. The residual is your diagnostic: a flat, patternless residual means trend and season captured everything systematic, and what's left is genuine randomness. Checking it for leftover autocorrelation is the bridge to the next dive.

9 · Why decompose

What it buys you

Forecasting

Extrapolate the trend, repeat the season, and you already have a respectable baseline forecast.

Seasonal adjustment

Strip the season to see the true underlying movement — the basis of official economic statistics.

Anomaly detection

A point is unusual only relative to its trend and season; a big residual is a real anomaly.

Understanding

Separating the pieces tells you what's actually driving the series — growth, calendar, or chance.

10 · In code

From scratch, then statsmodels

Classical decomposition is nothing but a moving average plus averaging-by-month — worth writing once in plain NumPy before reaching for a library:

# --- classical additive decomposition, from scratch ---
def decompose(y, P=12):
    n, h = len(y), P // 2
    trend = np.full(n, np.nan)
    for i in range(h, n - h):                         # centered 2xP moving average
        window = np.r_[0.5*y[i-h], y[i-h+1:i+h], 0.5*y[i+h]]   # half-weight the two ends
        trend[i] = window.sum() / P
    detrended = y - trend
    seasonal_idx = np.array([np.nanmean(detrended[m::P]) for m in range(P)])
    seasonal_idx -= seasonal_idx.mean()               # center so the season sums to zero
    seasonal = seasonal_idx[np.arange(n) % P]
    resid = y - trend - seasonal
    return trend, seasonal, resid

The first computable trend point averages months 1–13 with the two ends half-weighted: (½·112 + 118 + … + 104 + ½·115) / 12 = 126.79 passengers. statsmodels packages this exact recipe — plus the multiplicative and STL variants — and agrees with the hand-rolled version to machine precision:

from statsmodels.tsa.seasonal import seasonal_decompose, STL

res   = seasonal_decompose(y, model="additive", period=12)        # classical, additive
res_m = seasonal_decompose(y, model="multiplicative", period=12)  # or log-then-additive
stl   = STL(y, period=12, robust=True).fit()                      # lets the season drift over time
from-scratch vs statsmodels — identical
max|trend    - res.trend|    = 1.1e-13
max|seasonal - res.seasonal| = 2.8e-14
trend     climbs 127 -> 475 ;  seasonal peaks +64 (Jul), troughs -54 (Nov)
resid     swings GROW with the level -> prefer multiplicative / log
11 · Recap

The shape of every series

Every time series is, to first approximation, a trend plus a repeating season plus noise — and the first thing to do with one is pull those apart. Decomposition gives you a baseline forecast, a seasonally adjusted view, an anomaly detector, and a residual to diagnose. Watch the residual's variance to choose additive vs multiplicative, smooth to estimate the trend, average-by-season to estimate the pattern, and treat any leftover structure as a clue you're not done. Everything later — smoothing, ARIMA, forecasting, anomaly detection — builds on this anatomy.

Order matters

Rows aren't exchangeable; neighbouring points are correlated.

Three pieces

Trend + seasonal + residual — estimate each, inspect each.

Watch the residual

Fanning variance → multiplicative; leftover pattern → unfinished.

Decompose first

It's the baseline forecast and the lens for everything after.

A time series is values in order; its anatomy is trend, season, and residual. Pull them apart, read the residual to pick additive vs multiplicative, and you have both a first forecast and the foundation for every method that follows.
Deep dive · the memory of a series

Autocorrelation — ACF & PACF

A time series remembers. Today's value is rarely independent of yesterday's — temperature, prices, and traffic all drag their recent past forward. Autocorrelation measures exactly that memory: how strongly a series correlates with a lagged copy of itself. It is the single most useful diagnostic in the field — from one plot you can read trend, periodicity, and the kind of model the series wants. This dive defines it, builds the intuition as a plain scatter, separates the direct memory (the partial autocorrelation) from the echoes, and reads the tell-tale signatures of AR and MA processes — all from scratch, then confirmed against statsmodels.

1 · The idea

Correlation with your own past

Ordinary correlation measures how two different variables move together. Autocorrelation asks the same question of one series against a time-shifted version of itself: do high values tend to follow high values? The answer, computed at every lag, is a fingerprint. A slowly fading fingerprint means a trend; a fingerprint that spikes every twelve steps means a yearly season; a fingerprint that vanishes after one step means each value depends only on the one before. Reading that fingerprint is how you choose what to do next.

Autocorrelation is correlation of a series with a lagged copy of itself. Its shape across lags reveals trend, seasonality, and model structure at a glance.
2 · Defined

The autocorrelation function

For lag k, the sample autocorrelation is the lag-k covariance divided by the variance — correlation, restricted to pairs k steps apart:

rk = Σt=k+1n (yt)(yt−k)Σt=1n (yt)2

Note r0 = 1 always (a series is perfectly correlated with itself), and the denominator is fixed — every lag is normalised by the same total variance, so the rk shrink naturally as k grows and fewer pairs overlap.

By hand, on a five-point series x = [2, 4, 6, 5, 3] with mean = 4, the deviations are [−2, 0, 2, 1, −1]. The lag-1 numerator pairs each deviation with the next: (−2)(0) + (0)(2) + (2)(1) + (1)(−1) = 1. The denominator is the sum of squared deviations: 4 + 0 + 4 + 1 + 1 = 10. So r1 = 1 / 10 = 0.10 — almost no lag-1 memory.

Here is that arithmetic as a spreadsheet — the row number is t, and each column to the right is computed from the one before:

ABCD
txtinputdt=A-4dt·dt-1=B·B↑dt2=B^2
12-24
24000
36+204
45+1+21
53-1-11
Σ110
Column by column: subtract the mean (A−4) to get deviations B; multiply each deviation by the one above it for the lag-1 cross-products C; square each deviation for D. The autocorrelation is just the ratio of the two column sums: r1 = ΣC / ΣD = 1 / 10 = 0.10. Every rk is the same table with the product column shifted k rows.
3 · See it

It's just a lagged scatter

Strip away the formula and autocorrelation is the most ordinary thing imaginable: shift the series by k, plot each value against its k-step-ago self, and measure the correlation of that scatter. Below is a series with real memory; drag the lag and watch the cloud tighten into a diagonal at small lags and dissolve into a shapeless blob as the memory fades — the slope of the cloud is rk:

lag k 1 · rk =

At lag 1 the points line up tightly — this series strongly resembles its previous value. Push the lag out and the correlation decays toward zero: the influence of the past leaks away. That decay curve, lag by lag, is the autocorrelation function.

4 · The correlogram

Every lag at once

Collect rk for k = 1, 2, 3, … and draw them as a stem plot: the correlogram. It's the workhorse diagnostic. Around zero sit dashed confidence bands at ±1.96/√n — under pure noise, 95% of the bars fall inside, so a stem poking past the band is a statistically real correlation. Three shapes recur and each means something definite: a slow linear decay signals a trend (every nearby point is similar); a spike at the seasonal period (lag 12 for monthly data) signals seasonality; and bars that stay inside the bands everywhere mean there's no linear structure left to exploit.

5 · AR vs MA

Two processes, two fingerprints

Two simple models generate most of the textbook intuition. An autoregressive AR(1) process feeds its own past forward, yt = φ yt−1 + εt; the influence compounds, so its ACF decays geometrically, rk = φk — never quite reaching zero. A moving-average MA(1) process carries only one shock forward, yt = εt + θ εt−1; values two or more steps apart share no shock, so its ACF cuts off sharply after lag 1, r1 = θ/(1+θ2), rk>1 = 0. Decay versus cut-off is the first thing to read off a correlogram.

6 · Partial autocorrelation

Direct memory, echoes removed

The ACF has a blind spot. If yt correlates with yt−1, and yt−1 with yt−2, then yt and yt−2 will correlate too — purely by transitivity, even with no direct link. The partial autocorrelation (PACF) removes that echo: it is the correlation between yt and yt−k after regressing out everything in between. It answers "how much new information does lag k add?" The PACF is computed straight from the ACF by the Durbin–Levinson recursion, which solves the lag-k linear-prediction problem one order at a time and reads off the last coefficient.

7 · The signatures

Read the pair, name the model

Together the ACF and PACF identify a process — this is the heart of Box–Jenkins model selection. Switch between pure noise, an AR(1), and an MA(1), and drag the coefficient; watch the two plots swap roles. The rule is exact and worth memorising:

φ 0.70
processACFPACF
AR(p)tails off (decays)cuts off after lag p
MA(q)cuts off after lag qtails off (decays)
white noiseall zeroall zero

So an ACF that decays with a PACF that snaps to zero after one spike says "AR(1)"; the mirror image says "MA(1)". The lag where the cut-off happens is the order. That single reading turns a mystery series into a model.

8 · Real data

The airline series, decoded

Here is the ACF of the airline-passenger series — and it tells the whole story at a glance. Raw, the bars decay agonisingly slowly (the trend: every month resembles its neighbours) with a clear bump at lag 12 (the yearly season). After first differencing — subtracting each month from the next to kill the trend — the slow decay collapses, but a tall spike survives at lag 12: the seasonality is still there, now unmistakable. Below the ACF sits the PACF — the direct lags with the echoes stripped out. Toggle and watch the trend's smear give way to the season's spike:

lag-12

This is exactly how an analyst diagnoses what a series needs: the raw ACF screams "non-stationary — difference me," and the differenced ACF's surviving lag-12 spike says "and take a seasonal difference too." The correlogram doesn't just describe the series; it prescribes the next step.

9 · White noise

The goal is no memory left

Decomposition and modelling aim at one target: residuals that are white noise — zero autocorrelation at every lag, every bar inside the bands. If structure remains, the model has left signal on the table. Eyeballing the correlogram is the quick check; the formal one is the Ljung–Box portmanteau test, which bundles the first h lags into a single statistic

Q = n(n+2) Σk=1h rk2nk  ~  χ2h

A large Q (small p-value) rejects "the residuals are white" — there's leftover autocorrelation to model. A small Q means you're done: what remains is genuine noise, and a forecast can do no better than its mean.

See the test decide. Toggle between pure white noise and the AR(1) series from earlier: white noise keeps almost every bar inside the bands and Q below the χ2 threshold; the AR series breaks both, again and again:

Ljung–Box Q(10) vs χ2 crit 18.31 →
10 · In code

From scratch, then statsmodels

The ACF is two dot products; the PACF is the Durbin–Levinson recursion on top. Both are short enough to write plainly before trusting a library:

# --- ACF and PACF from scratch ---
def acf(x, K):
    x = x - x.mean()
    c0 = (x * x).sum()                                # total variance (the denominator)
    return np.array([ (x[k:] * x[:len(x)-k]).sum() / c0 for k in range(K+1) ])

def pacf(acf):                                        # Durbin-Levinson recursion from the ACF
    K = len(acf) - 1
    phi = np.zeros((K+1, K+1)); pac = np.zeros(K+1); pac[0] = 1
    phi[1, 1] = acf[1]; pac[1] = acf[1]
    for k in range(2, K+1):
        num = acf[k] - sum(phi[k-1, j] * acf[k-j] for j in range(1, k))
        den = 1      - sum(phi[k-1, j] * acf[j]   for j in range(1, k))
        phi[k, k] = num / den; pac[k] = phi[k, k]
        for j in range(1, k):
            phi[k, j] = phi[k-1, j] - phi[k, k] * phi[k-1, k-j]
    return pac

statsmodels does both in one call each — and agrees with the hand-rolled versions to machine precision on an AR(1) series (φ = 0.75):

from statsmodels.tsa.stattools import acf as sm_acf, pacf as sm_pacf
sm_acf(y, nlags=12, fft=False)              # sample ACF
sm_pacf(y, nlags=12, method="ywm")          # sample PACF
from-scratch vs statsmodels — identical
ACF  lag1 = 0.760   max|diff vs statsmodels| = 2.2e-16
PACF lag1 = 0.760 , lag2 = -0.090   (AR(1): PACF cuts off after lag 1)
11 · Recap

One plot, the whole diagnosis

Autocorrelation is the memory of a series — correlation with its own lagged copy. The ACF across lags reveals trend (slow decay), seasonality (periodic spikes), and model order; the PACF strips out the transitive echoes to expose direct dependence. Read as a pair, they name the process: ACF decays + PACF cuts → AR; ACF cuts + PACF decays → MA. The bands tell you what's real, the Ljung–Box test confirms when nothing's left, and the airline ACF shows the workflow in miniature — diagnose, difference, repeat. Every model in the dives ahead is chosen by reading these two plots.

ACF = lagged correlation

Slope of the yt-vs-yt−k scatter, for every k.

PACF = direct memory

Lag-k correlation with the in-between lags regressed out.

Decay vs cut-off

AR tails off in ACF; MA cuts off in ACF — and the PACF mirrors it.

Bands & Ljung–Box

Inside the bands = no structure; white residuals = done.

The ACF and PACF are the diagnostic core of time-series analysis: they measure a series' memory, expose its trend and season, and — read together against the confidence bands — name the model that fits. Everything from ARIMA onward starts here.
Deep dive · the assumption underneath everything

Stationarity & Differencing

Almost every classical forecasting method rests on one assumption the airline series flagrantly breaks: stationarity. A series is stationary when its statistical character doesn't drift over time — same mean, same variance, same autocorrelation structure whether you look at 1949 or 1960. Stationary series are stably predictable; non-stationary ones keep moving the goalposts. The craft of classical time-series work is to transform a wild series into a stationary one — usually by differencing and a log — model the tame version, then undo the transform. This dive defines stationarity, spots it with rolling statistics, removes trend and season by differencing, and tests for it with the augmented Dickey–Fuller test — from scratch, then matched against statsmodels.

1 · The idea

Statistics that don't drift

Take two windows of a series — one early, one late — and compute their mean, spread, and lag-1 correlation. If those summaries match, the series is stationary; the process generating it is the same in both eras, so what you learn from the past transfers to the future. The airline series fails immediately: its later years have a higher mean (the trend) and bigger swings (the growing season). A model fit to it learns numbers that won't hold next year. Stationarity is the property that makes "the past predicts the future" actually true.

A stationary series has time-invariant statistics. It's the precondition that lets a model trained on history generalise forward.
2 · Why it matters

Three reasons to insist on it

Stable estimates

A mean or variance only means something if it's constant. On a trending series, "the average" depends on when you stop looking.

No spurious regression

Two unrelated trending series look strongly correlated — both just go up. Differencing to stationarity kills the illusion.

ARIMA needs it

The workhorse model ahead is ARIMA — the "I" is the differencing that makes the series stationary before AR and MA do their work.

3 · Defined

Weak (covariance) stationarity

The usable definition asks for three things to hold for all time t:

E[yt] = μ    Var[yt] = σ2    Cov[yt, yt+k] = γk

The mean is constant, the variance is constant, and the autocovariance between two points depends only on the gap k between them, never on when they occur. (This is weak stationarity; strict stationarity demands the entire distribution be time-invariant, which is stronger than anyone needs in practice.) Note the third condition is exactly what made the ACF well-defined in the previous dive — the autocorrelation depends on lag alone, which is only true for a stationary series.

4 · Spotting it

Roll a window through

The quickest diagnostic is visual: slide a window along the series and plot the rolling mean and a rolling ±1σ band. On a stationary series both sit flat; on a non-stationary one the mean wanders and the band breathes. Toggle the raw airline series against its twice-differenced log — the difference is stark:

rolling mean drift

Raw, the rolling mean climbs from ~130 to ~470 and the band fans open — both the mean and the variance are functions of time, the textbook fingerprint of non-stationarity. After differencing, the rolling mean pins to zero and the band holds a near-constant width: the statistics have stopped drifting. The other tell you already know — a correlogram whose bars decay agonisingly slowly (last dive's raw airline ACF) is the autocorrelation signature of the same problem.

5 · Differencing

Subtract the recent past

The fix for a drifting mean is differencing — replace each value with its change since the step before:

Δyt = ytyt−1

A constant linear trend has a constant slope, so its first difference is a constant — the trend flattens to a level. The number of differences needed to reach stationarity is the order of integration d; most economic series need d = 1, occasionally 2, rarely more. (Over-differencing is a real mistake: difference a stationary series and you inject artificial negative autocorrelation, so stop as soon as the ADF test passes.) By hand, the first six first-differences of the airline series are 118−112, 132−118, 129−132, … = 6, 14, −3, −8, 14, 13 — the trend is gone, only the wiggles remain.

Here is differencing as a spreadsheet — each new column is a subtraction of a shifted copy of the input (rows 13–18):

ABC
tytinputΔyt=A-A↑Δ12yt=A-A↑12
13126+11+8
14141+15+9
15135-6+6
16125-10+4
17149+24+14
18170+21+22
Column by column: the ordinary first difference B = A − A one row up removes the month-to-month trend; the seasonal difference C = A − A twelve rows up removes the yearly pattern by subtracting the same month last year. Row 13 (Jan 1950): Δy = 126 − 115 = +11, and Δ12y = 126 − 118 = +8 versus Jan 1949.
6 · Seasonal differencing

Subtract last year

A first difference flattens a trend but leaves a fixed seasonal pattern untouched — every December still towers over its November. The cure is a seasonal difference: subtract the value from one full season ago,

Δsyt = ytyt−s

with s = 12 for monthly data. Comparing each month to the same month last year cancels a stable yearly shape. Series with both trend and seasonality — like the airline data — typically need both a regular and a seasonal difference, ΔΔ12y, applied in either order. That double-differenced log is exactly the stationary series the rolling-statistics panel above settled flat.

7 · Transformations

Logs for the variance

Differencing fixes a drifting mean; it does nothing for a growing variance. That's the multiplicative fanning from the anatomy dive, and the fix is the same: take logs (or a Box–Cox transform) first, to stabilise the spread, then difference to flatten the level. The standard recipe for a series like this is therefore log → seasonal difference → first difference. Each tool targets one defect: the log tames the variance, the differences tame the mean and the season.

8 · The ADF test

A formal stationarity check

Eyeballing rolling statistics is good; a hypothesis test is better. The augmented Dickey–Fuller test fits the regression

Δyt = α + γ yt−1 + Σi=1p φi Δyt−i + εt

and tests the null H₀: γ = 0, a unit root — meaning the series does not pull back toward any level, the hallmark of non-stationarity. If γ is reliably negative, shocks decay and the series is mean-reverting (stationary). The test statistic is the t-ratio γ̂/se, but it is not compared to the normal distribution — under a unit root it follows a special Dickey–Fuller distribution, so you compare against MacKinnon critical values (around −2.9 at 5%). A statistic more negative than the critical value, or equivalently p < 0.05, rejects the unit root: the series is stationary. The lagged Δy terms are the "augmented" part — they soak up autocorrelation so the test on γ stays valid.

What does γ actually control? Exactly how hard the series is yanked back toward its level. Write the same idea as an autoregression, yt = φ·yt−1 + εt with φ = 1 + γ, and drag φ through the three regimes the test distinguishes — same random shocks throughout, only the pull-back strength changes:

φ 0.90 (γ = −0.10) ·

Below 1, every shock decays and the series oscillates tightly around zero — stationary, the case the ADF test wants to confirm. At exactly φ = 1 (γ = 0) the pull-back vanishes: shocks accumulate forever and the series wanders off as a random walk — the unit root, the null hypothesis. Above 1 the feedback compounds and the series explodes. The whole ADF test is just a careful way of asking which side of φ = 1 your series sits on.

9 · The workflow

Climb the ladder to stationarity

Put it together on the airline series and watch the ADF verdict flip. Step through the transform ladder — raw, then log, then a first difference, a seasonal difference, and finally both — and read the statistic and p-value at each rung:

ADF statistic · p-value ·

The ladder tells the whole story: raw, p = 0.99 (hopelessly non-stationary); logging alone barely moves it; a single difference or a single seasonal difference each gets close but stalls around p ≈ 0.07; only with both differences applied does the statistic plunge to −4.4 and p to 0.000 — stationary at last. That ΔΔ12 log series is what an ARIMA model would actually be fit on.

10 · In code

From scratch, then statsmodels

Differencing is one NumPy call; the Dickey–Fuller statistic is one small regression. Both are short enough to write before trusting the library:

import numpy as np

# --- transforms from scratch ---
ly   = np.log(y)
d1   = np.diff(ly)                       # first difference  (kills trend)
d12  = ly[12:] - ly[:-12]                # seasonal difference (kills season)
dd   = np.diff(d12)                       # both -> stationary

def df_tstat(s):                          # Dickey-Fuller: regress dY on Y_{t-1}
    Y, dY = s[:-1], np.diff(s)
    X = np.column_stack([np.ones(len(Y)), Y])
    b = np.linalg.lstsq(X, dY, rcond=None)[0]
    resid = dY - X @ b
    s2  = resid @ resid / (len(dY) - 2)
    se  = np.sqrt(s2 * np.linalg.inv(X.T @ X)[1, 1])
    return b[1] / se                      # t-ratio on gamma

statsmodels' adfuller wraps the same regression, adds the augmenting lags automatically, and supplies the MacKinnon p-value — agreeing with the hand-rolled core to machine precision:

from statsmodels.tsa.stattools import adfuller
stat, p, *_ = adfuller(dd)               # augmented, MacKinnon p-value
from-scratch vs statsmodels — final ΔΔ₁₂ log series
from-scratch DF t-stat   = -16.1911
statsmodels (maxlag=0)   = -16.1911      max|diff| = 1e-13
adfuller(autolag="AIC")  ADF = -4.44 , p = 0.000   ->  STATIONARY
11 · Recap

Tame it before you model it

Stationarity — constant mean, variance, and lag-only autocovariance — is the assumption that makes the past a guide to the future, and most real series lack it. Spot non-stationarity with a wandering rolling mean, a breathing variance band, or a slowly-decaying ACF; remove a drifting mean with a first difference, a fixed season with a seasonal difference, and a growing variance with a log; and confirm the result with the augmented Dickey–Fuller test, reading p < 0.05 as "stationary." For the airline series the full recipe is log, then both differences — and the once-wild curve becomes the flat, modellable series that ARIMA is built to forecast.

Stationary = stable stats

Mean, variance, and autocovariance that don't depend on time.

Difference the mean

Δ for trend, Δ₁₂ for season; the order of integration is how many you need.

Log the variance

Multiplicative fanning calls for a log before differencing.

Confirm with ADF

p < 0.05 rejects the unit root — stop differencing once it does.

Stationarity is the contract every classical forecaster signs: stabilise the variance with a log, flatten the mean with differences, and verify with the ADF test. The tamed series — for the airline data, ΔΔ₁₂ of the log — is exactly what the ARIMA dive ahead will model.
Deep dive · the workhorse

ARIMA

Everything in this lab has been building to one model. ARIMA(p, d, q) stitches together the three ideas you've already met: AR regresses a series on its own recent values, I is the integration order — how many differences make it stationary — and MA models the series as a weighted memory of recent random shocks. Difference the series d times to make it stationary, fit p autoregressive and q moving-average terms to what's left, and you have the default forecasting model for a single series. This dive builds each piece, reads its order off the ACF/PACF, fits the autoregression from scratch, and ends with a real multi-year forecast of the airline data — confidence band and all.

1 · The idea

Three letters, three jobs

ARIMA is a recipe with three dials. The I comes first in practice: difference the series d times until it's stationary (the previous dive's job). On that stationary series, AR(p) says today is a linear function of the last p values, and MA(q) says today is a linear function of the last q forecast errors. Add a constant, fit the coefficients to history, and iterate the equation forward to forecast. Almost every classical single-series forecast — and the baseline every fancier method must beat — is some ARIMA.

ARIMA(p, d, q): difference d times for stationarity, then explain the result with p lags of the series (AR) and q lags of the shocks (MA).
2 · AR(p)

Regress on your own past

An autoregression predicts each value from a weighted sum of the previous ones:

yt = c + φ1yt−1 + φ2yt−2 + … + φpyt−p + εt

AR(1) with 0 < φ < 1 decays smoothly back to its mean (the mean-reverting case from the last dive). AR(2) is richer: with the right coefficients its roots turn complex and the series oscillates, tracing pseudo-cycles no AR(1) can. Drag the two coefficients and watch the behaviour change — and watch it blow up when you leave the stationary region:

φ1 0.60 · φ2 0.20 ·

The key fact for fitting: because each yt is a linear function of earlier values plus noise, fitting AR is nothing more than ordinary least squares — regress the series on lagged copies of itself. That makes the AR part trivially solvable, which is half of why ARIMA endures.

3 · MA(q)

A memory of shocks

A moving-average term models today as a weighted sum of recent random shocks — not of past values:

yt = μ + εt + θ1εt−1 + θ2εt−2 + … + θqεt−q

This is a different beast from the moving-average smoother of the anatomy dive — here the ε are unobserved white-noise innovations, and an MA(q) process has memory exactly q steps long: a shock today influences the next q values, then is gone forever. That's why an MA(q)'s ACF cuts off sharply after lag q (the signature from the autocorrelation dive). Because the shocks are unobserved, MA terms can't be fit by plain least squares — they need iterative maximum likelihood, which is the other half of what a library like statsmodels does for you.

4 · The I

Integration: difference, then undo

AR and MA both assume stationarity, so ARIMA sandwiches them between differencing and its inverse. The I(d) step differences the series d times to remove trend; the model is fit on that stationary version; then forecasts are integrated back — cumulatively summed — to return to the original scale. "Integrated" is just the undo of "differenced." For the airline series we already found d = 1 (plus a seasonal difference) does the job, so it's an ARIMA with d = 1 at its core.

5 · Choosing p, d, q

Read the orders off the plots

The three orders aren't guessed — they're read from the diagnostics you already built. The d comes from the stationarity work: difference until the ADF test passes. Then p and q come from the ACF and PACF of the differenced series, using the exact signatures from the autocorrelation dive:

look attells yourule
ADF testdfewest differences to stationarity
PACF cuts off at lag pp (AR)AR order = last significant PACF spike
ACF cuts off at lag qq (MA)MA order = last significant ACF spike

An ACF that decays with a PACF that cuts at lag 1 says ARIMA(1, d, 0); the mirror says ARIMA(0, d, 1). In practice you shortlist a few candidates this way and let an information criterion (AIC) pick between them — but the plots get you to the right neighbourhood.

6 · Fitting AR

It's just regression on lags

Fitting the AR part means stacking the series against shifted copies of itself and solving least squares. Laid out as a spreadsheet, column B is simply column A shifted down one row — yesterday's value lined up next to today's (a stationary AR(1) series, mean ≈ 0):

AB
tyttargetyt-1=A↑ (predictor)
2+2.12+0.33
3+2.11+2.12
4+1.76+2.11
5-0.46+1.76
6-0.54-0.46
7-0.68-0.54
Fit: regress the target A on the predictor B by ordinary least squares and the slope is φ̂. Over the full series that slope comes out φ̂ = 0.760 — and notice it equals the lag-1 autocorrelation from the autocorrelation dive exactly. For a mean-zero AR(1), the least-squares estimate of φ and the lag-1 ACF are the same number.
7 · Forecasting

Iterate the equation forward

To forecast, you just run the fitted equation forward, feeding each prediction back in as the next step's input. For a stationary AR, this means forecasts decay smoothly toward the mean — an AR(1) forecast h steps out is φh times today's deviation, fading to the long-run average as the past loses its grip. MA terms contribute only for the first q steps (after that all the relevant shocks are forecast as zero). Finally, every forecast made on the differenced scale is integrated back by cumulative summing, returning it to the units you started in. Uncertainty grows with the horizon, which is why forecast bands fan out.

Both behaviours are visible at once on a plain stationary AR(1). Drag φ and watch the forecast (orange) decay from the last value back to the mean, while the band fans out — not forever, but toward a fixed width set by the series' own variance. Higher φ means a longer memory: slower decay, wider final band:

φ 0.70 · band settles at

This is the signature of every stationary forecast: the point prediction reverts to the mean at rate φh, and the uncertainty grows but saturates — once you're far enough out, the best guess is simply the average and the band is the full spread of the series. (A non-stationary forecast, by contrast, never settles; that's exactly why we difference first.)

8 · Seasonal ARIMA

SARIMA for repeating patterns

Plain ARIMA has no notion of "the same month last year." SARIMA(p, d, q)(P, D, Q)s bolts on a second set of terms operating at the seasonal lag s: a seasonal difference (D), seasonal AR terms (P) linking yt to yt−s, and seasonal MA terms (Q). The famous "airline model" — SARIMA(0,1,1)(0,1,1)12 on the log series — uses one ordinary and one seasonal difference, plus one ordinary and one seasonal MA term, and fits the airline data beautifully with just a handful of parameters. That's the model behind the forecast below.

9 · A real forecast

Two years into the future

Here is the airline model fit to all 144 months and forecast 24 months ahead, on the original scale. The trend continues, the seasonal peaks repeat, and the shaded band shows the 80% interval widening with the horizon — exactly the behaviour the pieces above predict:

144 months observed (cyan) → 24-month SARIMA(0,1,1)(0,1,1)₁₂ forecast (amber) with 80% band · peak forecast

The forecast isn't magic — it's the trend extrapolated, the seasonal shape repeated, and the recent shocks faded out, all read from the same decomposition and autocorrelation structure this lab has been pulling apart. And the widening band is honest: two years out, the model knows much less than it does next month.

10 · In code

From scratch, then statsmodels

The AR part is pure least squares — write it once to see there's no magic — and it matches statsmodels' estimator to machine precision:

# --- fit AR(p) from scratch: regress y_t on its p lags ---
def ar_fit(x, p):
    n = len(x)
    Y = x[p:]                                          # targets
    X = np.column_stack([np.ones(n-p)] +
                        [x[p-i-1 : n-i-1] for i in range(p)])   # const + lagged columns
    beta = np.linalg.lstsq(X, Y, rcond=None)[0]
    return beta                                        # [c, phi_1, ..., phi_p]

The full model — including the MA terms that need iterative maximum likelihood, and the seasonal structure — is one call to statsmodels:

from statsmodels.tsa.statespace.sarimax import SARIMAX
res = SARIMAX(np.log(y), order=(0,1,1),
              seasonal_order=(0,1,1,12)).fit()
forecast = np.exp(res.get_forecast(24).predicted_mean)   # back to original scale
from-scratch AR vs statsmodels AutoReg
AR(1):  phi_1 = 0.7604   (== lag-1 ACF)   max|diff vs statsmodels| = 1e-16
AR(2):  phi_1 = 0.8221 , phi_2 = -0.0842                max|diff| = 2e-16
SARIMA(0,1,1)(0,1,1)12 on log airline:  AIC = -483.4 ,  24-mo forecast peaks ~738
11 · Recap

The model the whole lab was for

ARIMA is the assembly of everything prior: difference to stationarity (the I, from the stationarity dive), regress on lagged values (the AR, fit by least squares), and model the shock memory (the MA, whose order you read from the ACF). Choose d with the ADF test, p and q from the PACF and ACF, let AIC settle ties, add seasonal terms for repeating patterns, and forecast by iterating forward and integrating back. It's not the newest tool, but it's the one every forecaster should reach for first and the baseline every neural method is measured against.

AR = lags of the series

Least-squares regression on the previous p values.

MA = lags of the shocks

A q-step memory of past forecast errors, fit by MLE.

I = differencing

The d that makes it stationary; integrated back to forecast.

Orders from the plots

d from ADF, p from PACF, q from ACF, ties broken by AIC.

ARIMA(p,d,q) is the convergence point of this whole lab: stationarity supplies the I, autocorrelation supplies p and q, and least squares plus MLE fit the rest. Difference, identify, fit, forecast, integrate back — and you have the workhorse of classical forecasting.
Deep dive · grading a forecast

Forecasting Evaluation

A model that fits history perfectly can still forecast terribly — the only test that matters is error on data the model has never seen. But time series add a twist the rest of machine learning doesn't face: you cannot shuffle. The test set must be the future, the training set the past, or you've let the model peek at answers it could never have in production. This dive sets up the time-ordered split, defines the error metrics (MAE, RMSE, MAPE) from scratch, insists every model beat a naive baseline, runs a walk-forward backtest, and grades ARIMA against its rivals on the airline holdout.

1 · The idea

In-sample fit lies

Any flexible model can be made to hug its training data — add enough AR and MA terms and the residuals vanish. That tells you nothing about forecasting, which is prediction of the unseen. The honest question is always: held out the last stretch of history, how close were the forecasts to what actually happened? Every number in this dive is computed on data the model never touched during fitting.

Forecast quality is out-of-sample error, never in-sample fit. A model is only as good as its predictions on data it was never shown.
2 · Time changes the rules

You cannot shuffle

In ordinary supervised learning you split data randomly. Do that to a time series and you commit a subtle but fatal sin: the model trains on June while testing on May, learning from the future to predict the past. That leakage inflates every score and collapses the moment you deploy. The only valid split respects the arrow of time — train on an initial segment, test on what comes strictly after. Any preprocessing (scaling, differencing parameters, feature statistics) must likewise be fit on the training portion alone.

3 · The split

Hold out the future

The simplest honest evaluation is a single time-ordered split: train on the first stretch, forecast the held-out tail, and compare to what really happened. For the airline data we train on the first 120 months (ten years) and forecast the final 24 — a genuine two-year-ahead test. The held-out portion is sacred: it's looked at exactly once, to score the final model, never to choose it.

4 · The metrics

Three ways to measure error

Given actuals yt and forecasts ft over a test set of size n, three metrics dominate:

MAE = 1n Σ |ytft|     RMSE = √ 1n Σ (ytft)2      MAPE = 100n Σ |ytftyt|

MAE is the average error in the original units — interpretable and robust. RMSE squares first, so it punishes a few large misses far more than many small ones; it's the right choice when big errors are disproportionately costly. MAPE reports error as a percentage, making it scale-free and comparable across series — but it explodes when actuals near zero and penalises over- and under-prediction asymmetrically. Here is the per-month computation laid out as a spreadsheet (first 6 of the 24 test months, SARIMA forecasts):

ABCDE
tytactualftforecastet=A-Bet2=C^2|e/y|=|C/A|
1360348.6+11.41303.2%
2342331.6+10.41083.0%
3406383.2+22.85205.6%
4396372.6+23.45485.9%
5420382.9+37.113768.8%
6472453.1+18.93574.0%
Aggregate (over all 24 months): MAE is the mean of column |C| = 39.5; RMSE is the square root of the mean of column D = 43.2 (larger than MAE because squaring inflates the big misses); MAPE is the mean of column E = 8.5%. Row 5's 8.8% error is the worst of these six and contributes 1376 to the RMSE sum — far more than row 1's 130.
5 · Beat the baseline

Earn your complexity

A metric in isolation is meaningless — RMSE of 43 is only "good" relative to something. That something is a naive baseline. Two are standard: the naive forecast predicts the last observed value for everything ahead (a flat line), and the seasonal naive predicts the value from one full season ago (last January for this January). On a trending, seasonal series the seasonal naive is a strong opponent — it gets the shape right for free. Any model that can't beat it isn't earning its complexity. This is the time-series cousin of the dummy-classifier baseline from the ML lab.

6 · Walk-forward backtesting

Refit as you go

A single split uses the test set once; a walk-forward backtest wrings far more signal from it. Start at the split, forecast one step, then reveal the true value, slide the window forward to include it, refit, and forecast the next step — marching through the entire test period the way the model would actually be used in production. With an expanding window the training set grows each step; with a sliding window it stays a fixed length (better when the process drifts). Walk-forward gives many test forecasts instead of one batch, a more stable error estimate, and an honest picture of one-step-ahead performance — at the cost of refitting repeatedly.

Step through it. Drag the backtest forward: the training window (shaded) grows month by month, and at each step the model forecasts just one step ahead (orange) and is graded against the truth (cyan) before sliding on. Because every forecast is only a month out — the easiest horizon — the errors are tiny:

backtest step 8 / 24 · trained on months · one-step RMSE so far

Note the punchline: this walk-forward one-step RMSE settles near 15 — a third of the 43 from the two-year batch forecast above. They're not contradictory; they answer different questions. "How well can I predict next month, refitting as I go?" is a far easier task than "how well can I predict the next two years from today?" Always report which horizon a number describes.

7 · The comparison

Grade them on the holdout

Now the payoff: forecast the 24-month holdout three ways and look. Toggle the model and watch the forecast track (or miss) the actuals, with each month's error drawn as a vertical drop:

MAE · RMSE · MAPE

And the same three models as a leaderboard — switch the metric and the ranking holds: the naive flat line is hopeless, the seasonal naive is a genuinely strong baseline, and SARIMA edges past it by learning the trend on top of the season:

lower is better · winner highlighted

SARIMA wins on every metric (MAE 39.5, RMSE 43.2, MAPE 8.5%), but notice how much it beats the seasonal naive (MAE 47.6) — modestly. That margin is the real value the model adds over a free baseline, and it's the honest story to report: not "8.5% error," but "8.5% versus the 10.5% you'd get for nothing."

8 · Read the errors

Where, not just how much

A single number hides structure. Plot the forecast errors over time and interrogate them: do they drift (the model is biased, systematically high or low)? Do they swell toward the end (accuracy decays with horizon, as it must)? Are they autocorrelated — a big miss followed by another? Leftover autocorrelation in the errors means the model missed exploitable structure, which is exactly the Ljung–Box check from the autocorrelation dive applied to forecast residuals. Good forecast errors look like white noise centred on zero; anything else is a clue for the next iteration.

9 · Pitfalls

Ways to fool yourself

Tuning on the test set

Pick the model by its holdout score and the score is no longer honest. Use a validation split or cross-validation for selection; touch the test set once.

Ignoring the horizon

One-step-ahead error is tiny next to 24-steps-ahead. Always report the horizon a metric was computed at.

MAPE near zero

Dividing by small actuals makes MAPE blow up and punishes under-forecasts unevenly. Prefer MAE or scaled errors then.

One split, one season

A holdout covering a single unusual year misleads. Walk-forward over many windows for a stable estimate.

10 · In code

From scratch, then the loop

The metrics are three one-liners; the walk-forward backtest is a short loop that refits as it marches forward:

# --- metrics from scratch ---
mae  = np.mean(np.abs(y - f))
rmse = np.sqrt(np.mean((y - f)**2))
mape = np.mean(np.abs((y - f) / y)) * 100

# --- walk-forward backtest (expanding window, one-step-ahead) ---
errors = []
for t in range(split, len(series)):
    model = SARIMAX(series[:t], order=(0,1,1),
                    seasonal_order=(0,1,1,12)).fit(disp=False)   # train on the past only
    f = model.forecast(1)[0]                                     # predict the next step
    errors.append(series[t] - f)                                 # then reveal the truth and move on
rmse_wf = np.sqrt(np.mean(np.square(errors)))
airline holdout — train 120, test 24
naive (last value)   MAE 115.2   RMSE 137.3   MAPE 23.6%
seasonal naive       MAE  47.6   RMSE  50.0   MAPE 10.5%   <- strong baseline
SARIMA               MAE  39.5   RMSE  43.2   MAPE  8.5%   <- wins on every metric
11 · Recap

Honest grading, start to finish

Evaluating a forecast is mostly about not cheating. Split on time so the test set is the future; fit every transform on the training portion; measure with MAE, RMSE, and MAPE while knowing what each rewards; always compare to a naive and seasonal-naive baseline; backtest walk-forward for a stable, deployment-realistic estimate; and inspect the errors for bias, horizon decay, and leftover autocorrelation. Report the margin over the baseline, not the raw metric. Do all that and the number you publish is one you can actually stand behind.

Split on time

Test = future, train = past. Never shuffle a time series.

Know your metric

MAE robust, RMSE punishes big misses, MAPE scale-free but fragile near zero.

Beat the baseline

Report the margin over naive and seasonal-naive, not the raw error.

Walk forward

Refit and forecast one step at a time for a realistic estimate.

A trustworthy forecast evaluation respects time (no shuffling), measures out-of-sample error with the right metric, beats a naive baseline, and backtests walk-forward. The deliverable isn't a model that fits the past — it's an honest number for how it does on the future.
Deep dive · the other classical family

Exponential Smoothing

ARIMA explains a series through its autocorrelation; exponential smoothing takes the opposite route — it builds a forecast directly from interpretable components (level, trend, season), each updated as an exponentially-weighted average that trusts recent data more than old. It's the second great classical family, often labelled ETS (Error, Trend, Season), and on many real series — including the airline data — it matches or beats ARIMA with less fuss. This dive builds it up one component at a time: simple smoothing, then Holt's trend, then the full Holt–Winters seasonal model — from the recursion by hand to a real forecast.

1 · The idea

Recent data matters more

If you had to forecast tomorrow from a noisy history, you'd weight yesterday more than a year ago. Exponential smoothing makes that instinct precise: every forecast is a weighted average of all past values where the weights decay geometrically into the past. One smoothing parameter controls how fast. Layer one such average for the level, another for the trend, another for the season, and you've reconstructed the whole anatomy of a series — but as a live, updating forecaster rather than a static decomposition.

Exponential smoothing forecasts from components (level, trend, season), each an exponentially-weighted average that fades the past at a tunable rate.
2 · Simple exponential smoothing

One level, updated

The simplest case — no trend, no season — tracks a single levelt, nudging it toward each new observation:

t = α yt + (1 − α) ℓt−1

Each level is a blend of the newest value and the previous level; the forecast for every future step is just the last level (a flat line). By hand with α = 0.3 on the first airline values, starting ℓ1 = 112: ℓ2 = 0.3·118 + 0.7·112 = 113.8, then ℓ3 = 0.3·132 + 0.7·113.8 = 119.26, and so on. Laid out as a spreadsheet, each level cell reads from the value beside it and the level above it:

AB
tytobservedt=0.3·A + 0.7·B↑
1112112.00
2118113.80
3132119.26
4129122.18
5121121.83
6135125.78
Column by column: column B is recursive — each level is 30% of today's observation A plus 70% of yesterday's level (the cell directly above). The level chases the data but lags it, smoothing out noise. Row 3: 0.3·132 + 0.7·113.80 = 119.26. The forecast beyond the data is simply the final level, held flat.
3 · The smoothing parameter

How fast to forget

Unrolling the recursion shows what α really does: the weight on the value k steps back is α(1−α)k — a geometric decay. Large α forgets quickly and hugs the latest point (responsive but jumpy); small α forgets slowly and approaches the long-run average (smooth but sluggish). Drag α and watch the smoothed level chase a noisy, stepping series, with the weight-decay shown alongside:

α 0.30 · weight on latest point 30%

There's no universally right α — it's fit from the data by minimising forecast error. Notice the tension: crank α up and the level reacts instantly to the step change but jitters on the noise; turn it down and the noise vanishes but the level takes many steps to catch a real shift. That trade-off between responsiveness and smoothness is the whole game.

4 · Holt's linear trend

Add a slope

Simple smoothing forecasts a flat line, so it lags any trending series badly (try it on the airline data and it trails the climb forever). Holt's method fixes this by smoothing a second component — the trend bt — with its own parameter β:

t = α yt + (1−α)(ℓt−1 + bt−1)    bt = β(ℓt − ℓt−1) + (1−β) bt−1

The level now updates around the previous level plus trend, and the trend is itself an exponentially-smoothed estimate of how much the level changes each step. The forecast is no longer flat but a straight line: ŷT+h = ℓT + h·bT. That single addition turns a lagging tracker into something that extrapolates growth.

See the difference on a trending series. Simple smoothing (amber) forecasts a flat line from its last level — hopeless against a climb. Holt (orchid) carries the slope forward. Drag β to control how quickly the trend itself adapts:

β 0.30 · at h=12: SES vs Holt

The flat SES forecast falls further behind every step, while Holt's sloped forecast stays on the trend line. A higher β lets the slope chase recent changes (useful if growth is accelerating) at the cost of more sensitivity to noise — the same responsiveness-versus-smoothness trade-off as α, now for the trend.

5 · Holt–Winters seasonal

Add the season

One more component and one more parameter captures the yearly pattern. Holt–Winters smooths a seasonal term st with rate γ, recycled every m = 12 months, and combines all three for the forecast. Seasonality comes in two flavours:

Additive

The seasonal swing is a fixed number of units: ŷ = ℓ + h·b + s. Use when the season's size is roughly constant.

Multiplicative

The swing scales with the level: ŷ = (ℓ + h·b)·s. Use when the season grows with the series — exactly the airline's fanning pattern.

This is the same additive-vs-multiplicative choice from the anatomy dive, now baked into the forecaster. For the airline series the multiplicative form is the right call, because the summer peaks grow in proportion to the rising baseline.

6 · The three equations

Three smoothers, working together

The full multiplicative Holt–Winters is just three coupled exponential averages plus a forecast rule:

t = α ytst−m + (1−α)(ℓt−1+bt−1)
bt = β(ℓt−ℓt−1) + (1−β)bt−1     st = γ ytt + (1−γ)st−m
ŷt+h = (ℓt + h·bt) · st−m+h

Each line is the same pattern — a fraction of the newest evidence plus the remainder of the old estimate — applied to the level, the per-step change, and the seasonal factor respectively. The level equation deseasonalises yt by dividing out the season before updating; the season equation re-estimates the factor from how far yt sits above or below the level.

7 · Fitting

Pick the smoothing rates

The parameters α, β, γ (each in [0,1]) and the initial level, trend, and seasonal states are all chosen at once to minimise the sum of squared one-step errors over the history — a numerical optimisation, just as ARIMA's coefficients are fit by maximum likelihood. On the airline series the fit lands near α = 0.32, β ≈ 0 (the trend is steady, so it barely updates), and γ = 0.60 (the seasonal shape drifts moderately). A near-zero β is informative: it says the growth rate is essentially constant across the decade.

8 · ETS vs ARIMA

Two families, similar power

ETS and ARIMA are the two pillars of classical forecasting, and they overlap more than they differ: many exponential-smoothing models have an exact ARIMA equivalent (simple smoothing is an ARIMA(0,1,1), for instance). The mental split is in framing — ETS thinks in explicit, interpretable components you can read off; ARIMA thinks in autocorrelation and differencing. In practice you try both and let out-of-sample error decide. On the airline holdout from the evaluation dive, Holt–Winters actually edges ahead:

modelholdout MAEholdout RMSE
seasonal naive (baseline)47.650.0
SARIMA(0,1,1)(0,1,1)₁₂39.543.2
Holt–Winters (mult.)29.032.5

Neither family is universally best — the lesson from the evaluation dive stands: don't assume, measure.

9 · A real forecast

Holt–Winters on the airline

Here is the multiplicative Holt–Winters model fit to all 144 months and forecast 24 ahead, with the SARIMA forecast from the ARIMA dive overlaid for comparison. The two classical families trace nearly the same future — trend continued, seasonal peaks growing — from completely different machinery:

144 months observed (cyan) → Holt–Winters forecast (orchid) vs SARIMA (amber) · HW peak

That the two methods agree so closely is reassuring, not redundant: when models built on different principles converge on the same forecast, you can trust it more. Where they diverge is where the forecast is genuinely uncertain.

10 · In code

From scratch, then statsmodels

Simple smoothing is a three-line loop; the level it produces matches statsmodels exactly:

# --- simple exponential smoothing from scratch ---
def ses(y, alpha):
    level = y[0]
    out = [level]
    for t in range(1, len(y)):
        level = alpha * y[t] + (1 - alpha) * level    # blend new obs with old level
        out.append(level)
    return out                                         # forecast = out[-1], held flat

The full Holt–Winters model — all three components, additive or multiplicative, fit by optimisation — is one statsmodels call:

from statsmodels.tsa.holtwinters import ExponentialSmoothing
hw = ExponentialSmoothing(y, trend='add', seasonal='mul',
                          seasonal_periods=12).fit()
forecast = hw.forecast(24)
from-scratch SES vs statsmodels (alpha=0.3)
final level = 138.388   (matches SimpleExpSmoothing exactly)
Holt-Winters fit:  alpha=0.319 , beta=0.000 , gamma=0.601
24-month forecast peaks ~705   ·   holdout MAE 29.0 (beats SARIMA's 39.5)
11 · Recap

The components, made live

Exponential smoothing forecasts a series by maintaining a handful of exponentially-weighted components and projecting them forward. Start with a single smoothed level (simple exponential smoothing); add a smoothed trend for a sloped forecast (Holt); add a smoothed seasonal factor, additive or multiplicative, for repeating patterns (Holt–Winters). The smoothing rates α, β, γ set how fast each component forgets, and they're fit by minimising error. It's the interpretable cousin of ARIMA — different machinery, comparable accuracy — and on the airline data it wins. Two families, one lesson: build the components you can see, then let the held-out error pick the winner.

SES: one level

ℓ updated toward each new value; flat forecast. The α weights decay geometrically.

Holt: + trend

A second smoother for the slope turns the forecast into a line.

Holt–Winters: + season

A third smoother, additive or multiplicative, captures the yearly shape.

ETS vs ARIMA

Two classical families, often equivalent; measure to choose.

Exponential smoothing is forecasting by components: a level, a trend, and a season, each an exponentially-weighted average fading the past at its own rate. Simple, interpretable, and on the airline data a match for ARIMA — the second tool every forecaster should know.
Deep dive · the bridge to machine learning

Machine-Learning Forecasting

ARIMA and exponential smoothing are purpose-built for time series. But there's a second way in: reframe forecasting as ordinary supervised regression. Slide a window along the series and every position becomes a training row — the recent past as features, the next value as the target — and suddenly the entire machine-learning toolbox applies, from linear regression to gradient-boosted trees. This dive builds that transformation by hand, fits a model to the airline data, and weighs the ML approach honestly against the classical methods — including the trap that catches tree models on a trend.

1 · The idea

A series is a regression in disguise

Supervised learning needs a table: rows of features, each with a target. A time series doesn't look like that — until you build the table yourself. Take each month, use the few months before it as the input features and the month itself as the target, and you've converted one long sequence into hundreds of ordinary (x, y) rows. Fit any regressor to that table and you have a forecaster. This is how the same gradient boosting and random forests from the ML lab end up winning forecasting competitions.

Forecasting becomes supervised regression once you turn the series into a feature table: recent values in, next value out.
2 · Lag features

The sliding window

The core trick is the lag feature: to predict yt, use yt−1, yt−2, …, yt−k as inputs. Slide a window of width k along the series and read off one row at each stop. Drag the window below and watch the design matrix assemble itself — each position contributes the highlighted lags as features and the next point as the target:

window position · row:

Stacking every window gives the feature matrix X and target vector y — a perfectly ordinary regression dataset (here with three lags on the airline series):

ABC
rowyt-1lag 1yt-2lag 2yt-3lag 3yttarget
t=4132118112129
t=5129132118121
t=6121129132135
t=7135121129148
t=8148135121148
t=9148148135136
Row by row: each row is one slide of the window. The features A, B, C are the series shifted by 1, 2, and 3 steps; the target is the value that follows. Notice the staircase — column A of one row becomes the target of the row above it, because consecutive windows overlap. Feed this table to any regressor and the lag coefficients it learns are an autoregression.

3 · Richer features

Beyond just lags

This is where the ML framing pulls decisively ahead of univariate ARIMA: once the series is a feature table, you can bolt on any column. Feature engineering becomes the main lever, and four families do most of the work:

Lag features

yt-1, yt-2, …, and the seasonal lag yt-12 — the raw memory of the series.

Rolling statistics

Moving mean, std, min/max over recent windows — smoothed summaries of where the series has been.

Calendar & Fourier

Month, day-of-week, holiday flags, and sine/cosine terms that encode seasonality as smooth continuous columns.

Exogenous drivers

Price, weather, promotions — external variables that plain ARIMA needs special extensions to use, but a regression takes for free.

Which features actually carry the forecast? Fit a model and read its feature importances. With lags [1, 2, 3, 12, 13] on the airline series, the answer is emphatic — and it depends on the model. Toggle between a random forest and a linear model:

share of importance

The random forest pours 97% of its importance into the seasonal lag yt−12 — for monthly data, last year's same month is overwhelmingly the best single predictor, and a tree happily keys on it alone. The linear model spreads its weight across the recent lag, the seasonal lag, and their interaction yt−13, because it must combine them additively. Same data, same features, two very different stories about how the forecast is made — a reminder that importance is a property of the model, not just the series.

4 · Recursive vs direct

Forecasting more than one step

A lag model naturally predicts one step. For a longer horizon there are two strategies. Recursive: forecast one step, then feed that prediction back in as a lag to forecast the next, and so on — simple, but errors compound. Direct: train a separate model for each horizon (one for "+1 month", another for "+2 months", …) — no compounding, but more models and no coherence between them. Recursive is the common default and the one used for the forecast later in this dive.

5 · Which regressor

From linear to boosted trees

Any regressor fits the table, and the choice echoes the ML lab. Linear regression on lag features is special: it's exactly an AR model — the same least-squares-on-lags fit from the ARIMA dive, just assembled by hand. Tree ensembles (random forests, gradient boosting) earn their keep when the dynamics are nonlinear or when many heterogeneous features interact — they capture thresholds and regime changes a linear model can't, which is why gradient boosting dominates tabular forecasting competitions with many related series.

That last point is the modern twist: the global model. Instead of one model per series, train a single regressor on the stacked feature tables of thousands of related series at once — every product in a catalogue, every sensor in a fleet. The model learns patterns that recur across series and lets data-rich series lend strength to sparse ones, something no per-series ARIMA can do. This is how gradient-boosted trees (LightGBM in particular) won the M5 forecasting competition, and it's ML forecasting's genuine structural edge: classical methods fit each series alone, while one global model exploits all of them together.

6 · The extrapolation trap

Why trees flatline on a trend

There's a catch that bites beginners. A tree can only predict averages of values it saw in training, so it cannot extrapolate beyond the range of the data. Point a raw gradient-boosting model at a rising series and its forecast flattens at the highest value it ever saw — useless for a trend. On the airline holdout this is stark: linear-on-lags scores RMSE 37.7, but the same-featured gradient booster scores a worse 54.8, precisely because it can't follow the climb. The fix is the lesson from the stationarity dive: difference or detrend first so the model predicts changes (which stay in range), or include an explicit trend feature and a model that can use it. Classical methods extrapolate trends for free; ML models must be set up to.

Watch it happen. The same lag features feed a linear model and a gradient booster, each forecasting 24 months past the end of the airline series. The linear forecast sails upward with the trend; the booster's forecast bends over and flatlines, unable to print a value above the dashed ceiling it saw in training:

linear reaches · gradient boosting caps at · training max 622

The booster isn't broken — it's doing exactly what trees do, averaging training targets, none of which exceeded 622. Difference the series first and the problem vanishes: the trees then predict changes, which stay comfortably inside the range they trained on, and the cumulative sum rebuilds the trend. It's the stationarity dive's lesson with real teeth.

7 · Validating in time

The same rules apply

Everything from the evaluation dive carries over, with teeth. You still must not shuffle: use a time-ordered split and, for tuning, rolling-origin cross-validation (expanding-window backtests) rather than random k-fold. Feature engineering is a new leakage hazard — a rolling mean that peeks at the target month, or scaling fit on the whole series, quietly leaks the future. Every feature for row t must be computable using only data available strictly before t. The flexibility of ML buys power and a dozen new ways to fool yourself.

8 · A worked forecast

Lag-features vs SARIMA

Here is a linear model on lag features [1, 2, 3, 12, 13] of the log series, forecast recursively 24 months and laid over the SARIMA forecast. Built from generic regression machinery — no time-series model at all — it lands almost on top of SARIMA:

144 months observed (cyan) → lag-feature regression (coral) vs SARIMA (amber) · ML peak

On the holdout this lag-feature regression actually scores MAE 33.9 — a touch better than SARIMA's 39.5 — because with the seasonal lag yt−12 in the feature set, the linear model is essentially a seasonal autoregression. It's a reminder that the boundary between "classical" and "ML" forecasting is blurry: a regression on the right lags is both.

9 · ML vs classical

When to reach for which

ML wins when…

You have many related series, rich exogenous features, nonlinear dynamics, and plenty of history. One model can learn across thousands of series at once.

Classical wins when…

You have a single, shortish series, want interpretability and calibrated uncertainty out of the box, and need trends extrapolated without fuss.

ML costs

More data and tuning, careful leakage control, no built-in prediction intervals, and the extrapolation trap on trends.

Classical costs

Awkward with many exogenous drivers, assumes a fixed structure, and one model per series.

10 · In code

Build the table, fit any model

The entire ML approach is two steps: window the series into a matrix, then hand it to a regressor:

# --- turn a series into a supervised table ---
def make_Xy(s, lags):
    X, y = [], []
    for t in range(max(lags), len(s)):
        X.append([s[t - L] for L in lags])    # lag features for row t
        y.append(s[t])                         # the value to predict
    return np.array(X), np.array(y)

X, y = make_Xy(np.log(series), lags=[1, 2, 3, 12, 13])
from sklearn.linear_model import LinearRegression
model = LinearRegression().fit(X, y)           # linear-on-lags == an AR model

# --- recursive multi-step forecast ---
hist = list(np.log(series))
for _ in range(24):
    x = [[hist[-L] for L in lags]]
    hist.append(model.predict(x)[0])           # feed prediction back in
forecast = np.exp(hist[-24:])
airline holdout (train 120, test 24), lags [1,2,3,12,13] on log
LinearRegression       MAE 33.9   RMSE 37.7   <- beats SARIMA (39.5 / 43.2)
GradientBoosting       MAE 41.5   RMSE 54.8   <- can't extrapolate the trend
swap in any sklearn regressor: same two-step recipe
11 · Recap

Forecasting is regression with the right features

The machine-learning route to forecasting is a single idea — slide a window to turn the series into a feature table — followed by the entire supervised toolbox. Use lag features (a seasonal lag is gold), add calendar and exogenous columns ARIMA can't easily take, pick a regressor (linear is an AR model; trees handle nonlinearity), and forecast recursively or directly. Mind the two traps: tree models can't extrapolate a trend, so difference first; and feature engineering must never peek across the time split. On one short series the classical methods hold their own — but give ML many series and rich features, and it pulls ahead. Either way, the discipline from the evaluation dive is what keeps the number honest.

Window → table

Lag features turn a sequence into supervised rows.

Any regressor fits

Linear (=AR), random forest, gradient boosting — your pick.

Difference for trees

Tree models can't extrapolate; predict changes, not levels.

Validate in time

Rolling-origin CV, and no features that peek ahead.

Reframe the series as a lag-feature table and forecasting is just regression — every model from the ML lab applies. It buys flexibility (any feature, any nonlinearity, many series) at the price of the extrapolation trap and stricter leakage control. The window is the whole bridge between this lab and machine learning.
Deep dive · explain, don't just extrapolate

Time Series Regression

Every model so far has predicted the series from its own past — autoregression, smoothing, lag features. Time series regression takes the opposite stance: explain the series with explanatory features. Build columns for a deterministic trend, for the season, and — the real prize — for external drivers like price or weather, then fit an ordinary regression. The catch is that regression assumes independent errors, which a time series flatly violates; the fix, regression with ARIMA errors (dynamic regression), models that leftover autocorrelation and is one of the most useful forecasting tools in practice. This dive builds the linear model, exposes the autocorrelation trap, and repairs it.

1 · The idea

A regression, with time as data

Ordinary regression explains y from predictors x. Time series regression does the same, but the predictors are things you can build for any date: a trend term (the row index t), seasonal indicators (which month it is), and any external variable you have a value for. The model is plain linear regression — yt = β₀ + β₁t + (seasonal) + (drivers) + εt — and its coefficients are directly interpretable: "+1.0% per month of trend," "+21% in July." That interpretability, and the ability to fold in drivers, is why it's the workhorse of econometric forecasting.

Time series regression explains the series with built features — trend, season, external drivers — rather than its own lags. The coefficients tell you why, not just what next.
2 · The linear model

Trend plus season

The simplest useful version regresses (log) airline traffic on a linear trend and a set of monthly indicators. Toggle the features and watch the fit: trend alone is a sloped line that captures the growth but misses every seasonal swing; add the seasonal terms and the fitted curve snaps onto the data:

Trend-only explains 90% of the variance (R² = 0.90) but leaves the whole seasonal pattern in the residuals; adding eleven monthly indicators lifts it to R² = 0.98 and the fitted line tracks each peak and trough. The fitted trend coefficient is +1.0% per month — about 12.8% annual growth — and the July indicator sits highest, the November lowest, recovering the seasonal shape as plain regression coefficients you can read off and report.

3 · Trend & seasonal dummies

The deterministic features

Two feature types do the work here. The trend is just the time index t = 0, 1, 2, … (or a polynomial, or a piecewise spline if growth bends). The season is encoded as dummy variables: for monthly data, eleven 0/1 columns (one month is the baseline, absorbed into the intercept), each coefficient measuring that month's average offset from baseline after trend. Unlike the differencing in ARIMA, these are deterministic — they're known for any future date, so forecasting just means evaluating the formula forward. The cost: eleven parameters for the season alone, which the next idea trims dramatically.

4 · Fourier terms

Seasonality in a few waves

Eleven dummies is a lot of parameters. Fourier terms express the same seasonality as a sum of sine and cosine waves — each "harmonic" is one sin/cos pair at a multiple of the seasonal frequency. A handful of harmonics reconstruct a smooth seasonal shape with far fewer parameters. Drag the number of harmonics and watch them build up the monthly pattern:

harmonics K 2 = 4 parameters

With K harmonics you spend just 2K parameters instead of eleven, and for the airline season two or three harmonics already capture R² ≈ 0.97–0.98 of what the full dummy set achieves. Here is the design matrix — the deterministic features computed straight from the time index t:

ABC
ttrend=tsin₁=sin(2πt/12)cos₁=cos(2πt/12)log yttarget
000.0001.0004.718
110.5000.8664.771
220.8660.5004.883
331.0000.0004.860
440.866-0.5004.796
550.500-0.8664.905
Row by row: every feature is a deterministic function of the time index A = t. The trend is t itself; the seasonal columns are sine and cosine evaluated at t (here the first harmonic, period 12). Because they depend only on the date, every one is known for future months too — so forecasting is just extending the table and applying the fitted coefficients. Add the pair sin₂/cos₂ for the second harmonic, and so on.
5 · Exogenous predictors

The real power: outside drivers

Trend and season you could get from ARIMA. What time series regression adds for free is exogenous predictors — any external series you believe drives the target. Retail sales regressed on advertising spend and a price index; electricity demand on temperature; bookings on a holiday calendar. Each is just another column in the feature matrix, and its coefficient is the estimated effect of that driver, holding the others fixed. This is the bridge to causal-flavoured questions ("how much does a 1° rise in temperature add to demand?") that pure autoregression can't answer. The price: to forecast, you now need future values of the drivers — known for a calendar, but themselves a forecasting problem for things like temperature.

6 · The autocorrelation trap

Why plain OLS isn't enough

Here's the catch that makes time series regression its own topic. Ordinary least squares assumes the errors are independent — but in a time series, a residual above the line is almost always followed by another above the line. Plot the autocorrelation of the trend-plus-season residuals and the violation is blatant:

The OLS residuals have a lag-1 autocorrelation of 0.78 and a Durbin–Watson statistic of 0.43 (2.0 means no autocorrelation) — the errors carry enormous leftover structure. The coefficients themselves stay unbiased, but their standard errors are badly wrong, so every p-value and confidence interval the regression reports is untrustworthy, and the forecasts ignore exploitable signal. You cannot stop here.

7 · Regression with ARIMA errors

Model the leftover structure

The fix is elegant: keep the regression, but stop pretending the error is white noise. Regression with ARIMA errors writes yt = (regression on features) + ηt, where the error ηt follows an ARIMA process, and fits both parts together:

yt = β₀ + β₁t + Σ (seasonal, drivers) + ηt    where ηt ~ ARIMA(p,d,q)

Toggle the residual plot above to "+ ARIMA errors": the autocorrelation collapses inside the bands — the ARIMA part has absorbed exactly the structure plain OLS left behind. Now the standard errors are valid, inference is honest, and the forecasts use the error dynamics too. This combination — interpretable regression coefficients plus a properly-modelled stochastic error — is dynamic regression, and it's the right way to put external predictors into a forecast.

8 · Dynamic harmonic regression

The practical recipe

Put the pieces together for a seasonal series and you get dynamic harmonic regression: Fourier terms for a compact deterministic season, plus ARIMA errors (with differencing) to handle trend and leftover autocorrelation. For the airline series, Fourier(K=3) regressors with ARIMA(1,1,1)(1,0,1)₁₂ errors leaves residuals that pass the Ljung–Box test (p = 0.79 — white) and forecasts a two-year peak near 715 passengers — squarely among SARIMA (738), Holt–Winters (705), and the ML lag model (702). Four methods, four philosophies, one consensus future. It's especially handy when the season is long (weekly data, period 52) where a SARIMA would need an unwieldy number of seasonal terms but a few Fourier harmonics suffice.

Seeing is believing. Here are all four forecasting families from this lab — dynamic regression, SARIMA, Holt–Winters, and the ML lag model — each fit to the airline series and projected two years ahead. Built on four different philosophies, they trace nearly the same future:

peak forecasts — dyn-reg 715 · SARIMA 738 · Holt–Winters 705 · ML lags 702

The four lines bunch together through the forecast — that agreement is the signal, and it should raise your confidence in the consensus shape. Where they fan apart, at the far summer peaks two years out, is exactly where the future is genuinely uncertain and no method can rescue you. Four philosophies converging is worth more than any single model's claim.

9 · Where it fits

Four ways to forecast, compared

ARIMA

Explains the series from its own past values and shocks. No external drivers, minimal assumptions.

Exponential smoothing

Tracks level/trend/season as updating components. Robust, automatic, no predictors.

ML lag features

The series predicts itself, nonlinearly, with many engineered columns. Great with many series.

Dynamic regression

Explains the series from external drivers + deterministic terms, with ARIMA errors. Interpretable, causal-flavoured.

Dynamic regression is the one to reach for when you have drivers — variables outside the series that explain it — and when you need to report the effect of each, not just a forecast. For a pure self-driven series, ARIMA or ETS is simpler.

10 · In code

OLS, then ARIMA errors

The linear model is one matrix solve; the upgrade to ARIMA errors is one statsmodels call with an exog argument:

import numpy as np
from statsmodels.tsa.statespace.sarimax import SARIMAX

t = np.arange(len(y))
def fourier(t, K, P=12):
    cols = [f(2*np.pi*k*t/P) for k in range(1, K+1) for f in (np.sin, np.cos)]
    return np.column_stack(cols)

X = np.column_stack([np.ones_like(t), t, fourier(t, 3)])   # const + trend + season
beta = np.linalg.lstsq(X, np.log(y), rcond=None)[0]         # plain OLS (biased SEs!)

# --- dynamic regression: same features, ARIMA error ---
m = SARIMAX(np.log(y), exog=fourier(t, 3),
            order=(1,1,1), seasonal_order=(1,0,1,12)).fit()
future = fourier(np.arange(len(y), len(y)+24), 3)
forecast = np.exp(m.get_forecast(24, exog=future).predicted_mean)
airline · trend + seasonal dummies (OLS)
R2 = 0.983 , trend = +1.0%/mo (12.8%/yr)
residual autocorrelation: lag-1 = 0.78 , Durbin-Watson = 0.43   <- errors NOT independent
+ ARIMA(1,1,1)(1,0,1)12 errors (dynamic harmonic regression)
residual Ljung-Box p = 0.79  -> white   ·   24-month forecast peak ~715
11 · Recap

Explain the series, then model the rest

Time series regression flips the forecasting question from "what does the past imply?" to "what explains this?" Build deterministic features — a trend term, seasonal dummies or compact Fourier harmonics — and add any external drivers you have, then fit a linear model whose coefficients you can interpret and report. But never trust plain OLS on a time series: its residuals are autocorrelated, its standard errors are wrong. Upgrade to regression with ARIMA errors, which keeps the interpretable regression while modelling the leftover structure, giving honest inference and forecasts that exploit both the drivers and the error dynamics. When you have outside variables that move your series — and you want to know how much — this is the tool.

Features, not lags

Trend, season, and external drivers as regression columns.

Fourier is compact

A few sin/cos harmonics replace many seasonal dummies.

OLS errors lie

Autocorrelated residuals → wrong standard errors. Check Durbin–Watson.

Add ARIMA errors

Dynamic regression: interpretable model + honest, white residuals.

Time series regression explains a series with built features — trend, season, drivers — and dynamic regression repairs its one flaw by giving the error an ARIMA structure. It's the forecasting tool for when something outside the series drives it, and you need to measure how much.
Deep dive · the deep-learning toolkit

Neural Network Forecasting

The ML dive turned a series into a feature table and fed it to linear models and trees. Neural networks take the same table and replace the model with a stack of learnable nonlinear transformations — and then go further, with architectures built specifically for sequences: recurrent networks that carry memory, convolutions that scan with a growing receptive field, and attention that looks anywhere in the past at once. This dive builds a neuron from scratch, shows why stacking them approximates any function, walks the four architecture families, and ends with the field's most humbling lesson — that on a single short series, all this machinery often loses to a good classical model.

1 · The idea

Learnable nonlinear features

Linear regression on lags can only add up its inputs; a tree can only split them. A neural network instead passes the inputs through layers of weighted sums and nonlinear "activations," learning its own intermediate features as it trains. With enough neurons it can represent essentially any smooth relationship between the recent past and the next value. That flexibility is the appeal — and the danger: it needs a lot of data to pin down all those weights, and on a short series it will happily memorise noise.

A neural network is a stack of weighted sums and nonlinearities that learns its own features. Maximum flexibility, maximum appetite for data.
2 · The neuron

One unit, from scratch

The building block is a single neuron: multiply each input by a weight, add a bias, and pass the total through a nonlinear activation. That's the whole computation — here it is by hand for inputs [1.0, 0.5, −0.2] with weights [0.6, −0.3, 0.2] and bias 0.1:

AB
inputxvaluewweightx·w=A·B
x₁1.00.60.60
x₂0.5-0.3-0.15
x₃-0.20.2-0.04
Σweighted sum + bias 0.1z = 0.51
Then the activation: the neuron squashes z = 0.51 through a nonlinearity — tanh(0.51) = 0.47, or ReLU(0.51) = 0.51, or sigmoid(0.51) = 0.62. Without that nonlinear step every layer would collapse into one big linear map and the network could do no more than linear regression. The nonlinearity is the whole point — it's what lets stacked neurons bend.
3 · The forward pass

From inputs to prediction, by hand

A network is just neurons in layers. Push three inputs x = [1.0, 0.5, −0.2] through a two-neuron hidden layer (tanh), then one output neuron. Each hidden neuron does its own weighted sum of all three inputs, adds its bias, and applies tanh — every number by hand:

ABC
neuronw₁x₁prodw₂x₂prodw₃x₃prodz=A+B+C+biash=tanh(z)
h₁0.50-0.20-0.060.340.3275
h₂-0.200.30-0.10-0.20-0.1974
Hidden weights are [0.5, −0.4, 0.3] with bias 0.1 for h₁, and [−0.2, 0.6, 0.5] with bias −0.2 for h₂. So z₁ = 0.50 − 0.20 − 0.06 + 0.10 = 0.34, and tanh(0.34) = 0.3275. Then the output neuron combines the two hidden activations with weights [0.7, −0.5] and bias 0.2: ŷ = 0.7·0.3275 + (−0.5)·(−0.1974) + 0.2 = 0.2293 + 0.0987 + 0.2 = 0.528. Input vector in, single prediction out — that is the entire forward pass.

In matrix form it's two lines, and the from-scratch code prints exactly the by-hand number — this is what every neural-net library computes internally:

import numpy as np
x  = np.array([1.0, 0.5, -0.2])                 # input (scaled lags)
W1 = np.array([[0.5,-0.4,0.3],[-0.2,0.6,0.5]])  # hidden weights (2x3)
b1 = np.array([0.1, -0.2])
W2 = np.array([0.7, -0.5]);  b2 = 0.2           # output weights

h    = np.tanh(W1 @ x + b1)    # hidden layer:  [0.3275, -0.1974]
yhat = W2 @ h + b2             # prediction out
from-scratch forward pass
h    = [0.3275, -0.1974]
yhat = 0.52792          <- matches the by-hand value exactly
4 · Universal approximation

Why stacking neurons works

One neuron draws one soft bend. Put many in a layer and sum their outputs, and you can approximate any continuous function — the more neurons, the finer the detail. Drag the hidden-unit count and watch a network fit a wiggly target: one neuron manages only a near-flat line, while sixteen trace every curve (and start chasing noise):

hidden neurons 4

This is the universal approximation property: a single hidden layer, wide enough, can represent any continuous function to arbitrary accuracy. It's why neural networks are so expressive — and why capacity control (regularisation, early stopping, enough data) matters so much. Too few neurons underfit; too many, with too little data, overfit. The whole art is finding the middle.

5 · How a network learns

Gradient descent and backprop

So far the weights were given. Training finds them by shrinking a loss. Say the true value was 1.0 but the forward pass predicted 0.528 — the squared-error loss is L = ½(0.528 − 1)² = 0.111. Backpropagation sends that error backward through the layers by the chain rule, getting each weight's gradient — how much nudging it moves the loss:

A
gradientvalue∂L/∂·chain rule
∂L/∂ŷ-0.472ŷ − t = 0.528 − 1
∂L/∂W₂₁-0.155(∂L/∂ŷ) · h₁ = −0.472·0.3275
∂L/∂h₁-0.330(∂L/∂ŷ) · W₂₁ = −0.472·0.7
∂L/∂z₁-0.295(∂L/∂h₁) · (1−h₁²) = −0.330·0.893
∂L/∂W₁₁-0.295(∂L/∂z₁) · x₁ = −0.295·1.0
Row by row the error flows backward: from the prediction to the output weights, then through the tanh (multiplying by 1−h², the slope of tanh) into the hidden weights. Each weight then steps downhill, w ← w − η·(∂L/∂w). One step with learning rate η = 0.1 moves the prediction 0.528 → 0.649 and drops the loss 0.111 → 0.061 (down 45%). Repeat over many examples and passes, and the weights converge.

Everything hinges on the step size η. Drag it on this loss bowl (the loss as one weight varies): too small and training crawls; near the critical value it overshoots and bounces; too large and it diverges entirely:

learning rate η 0.30 ·

This is why learning rate is the most fiddled-with knob in deep learning. The same logic scales from this one weight to the millions in a real network — backprop computes every gradient in one backward sweep, and an optimiser (SGD, Adam) takes the step.

6 · Neural net autoregression

NNAR: the simplest forecaster

The most direct way to forecast with a network needs nothing new: take the very same lag-feature table from the ML dive — yt−1, yt−2, … as inputs, yt as target — and fit a feed-forward network instead of a linear model. This is neural network autoregression (NNAR). It inherits everything from the ML dive: the sliding window, recursive multi-step forecasting, and — sharply — the extrapolation problem. Fed a trending series and its own predictions, an unconstrained network doesn't just flatline like a tree; it can spiral to absurd values. The cure is the same: difference or seasonally anchor the series first so the network predicts something that stays in range.

7 · Sequence framing

Windows in, windows out

The architectures built for sequences usually skip hand-made lag columns and consume the raw window. You slide a fixed input window of the last L steps and ask the network to emit an output window of the next H steps — a "sequence-to-sequence" setup. An encoder compresses the input window into a summary vector; a decoder expands that into the forecast horizon. This framing forecasts all H steps at once (no recursive error compounding) and generalises cleanly to multivariate input (many series in) and probabilistic output (a distribution, not a point).

8 · Recurrent networks

Memory that flows through time

A recurrent neural network (RNN) reads the sequence one step at a time, maintaining a hidden state — a memory vector — that it updates at each step from the previous state and the new input. Step through the unrolled network below: the same cell fires at every timestep, passing its memory rightward:

timestep · ht = f(ht−1, xt)

In principle the hidden state can carry information across the whole sequence, but plain RNNs struggle: gradients shrink as they propagate back through many steps (the vanishing gradient problem), so long-range memory fades. The fix is gated cells — the LSTM and GRU — which add learnable "gates" that decide what to keep, what to forget, and what to output. Those gates let a path for the gradient stay open across hundreds of steps, and they were the workhorse of sequence modelling for years.

9 · Convolutions

Scanning with a wider and wider eye

A different idea borrows from image models: slide a small convolutional filter along the time axis, detecting local patterns (a spike, a ramp). To see far back without huge filters, temporal convolutional networks (TCNs) stack dilated causal convolutions — each layer skips exponentially more steps (1, 2, 4, 8…), so the receptive field doubles per layer and reaches deep into the past with few parameters. "Causal" means each output depends only on the present and past, never the future. TCNs train fast (every position computed in parallel, unlike an RNN's sequential pass) and often match or beat recurrent nets on long sequences.

10 · Attention & Transformers

Look anywhere at once

The dominant modern idea drops recurrence entirely. Attention lets the model, when predicting a step, weight every past position by how relevant it is and read a blend of them directly — no information has to squeeze through a single hidden state. For seasonal data the weights light up exactly where you'd hope: the recent points and the same month a year ago. The bars below sketch a plausible attention pattern over the recent history:

attention weights for the next step — tallest at the most recent point and 12 months back

And the weights are real arithmetic, not magic. For a query vector q and three past keys k, attention scores each by the dot product q·k, scales by √d, exponentiates, and normalises (softmax) — then blends the values by those weights:

ABC
pastq·kdotscore=A/√2e^scoreexpweight=C/Σvaluev
t−31.100.7782.1770.350140
t−20.600.4241.5280.246205
t−11.300.9192.5070.404162
Σsoftmax normaliser6.2121.000
The output is the weight-blended value: 0.350·140 + 0.246·205 + 0.404·162 = 164.9. The weights came purely from how similar each past key was to the query — position t−1 scored highest (1.30) so it dominates the blend. That single softmax-weighted average, computed against every past position at once, is the whole mechanism; a Transformer just stacks many of them with learned q, k, v projections.

Stacks of attention layers make a Transformer, the architecture behind large language models and now the state of the art for long-horizon forecasting (Informer, Autoformer, PatchTST, and relatives). Attention's strength is reaching arbitrarily far back in one hop; its cost is computation that grows with the square of the sequence length, which the forecasting variants work hard to tame.

11 · Foundation models

Forecasting's pretrained era

The newest turn mirrors language models: train one enormous network on millions of time series from every domain, then forecast a series it has never seen — zero-shot, with no fitting at all. These time-series foundation models (Chronos, TimesFM, Lag-Llama, Moirai, TimeGPT) tokenise a series much like text and predict the continuation. They're remarkable when you have little history for the target but the pattern resembles something in their vast training set, and they hand you calibrated probabilistic forecasts out of the box. Whether they consistently beat a well-tuned classical model on ordinary business series is still an open, actively-debated question.

12 · The humbling lesson

When neural nets help — and when they don't

Here is the result that surprises newcomers. Neural networks are data-hungry: they have thousands of weights to estimate, and a single series of 144 points starves them. Across the famous M3 and M4 forecasting competitions, simple statistical methods and their ensembles repeatedly beat standalone deep networks on individual series. Deep learning pulls decisively ahead in a different regime — many related series (thousands of products, sensors, or households), long histories, rich exogenous inputs, or complex nonlinear and multivariate structure. The M5 competition, run on such data, was won by gradient boosting with neural nets close behind. Match the tool to the regime, not to the hype.

Reach for a network when…

You have many series or long history, exogenous drivers, or clearly nonlinear dynamics — and the data to fit it.

Reach for classical when…

A single, short, well-behaved series — ARIMA, ETS, or a lag regression will likely win and is far cheaper.

13 · A worked forecast

NNAR on the airline — a cautionary tale

Proof on our running series. A feed-forward NNAR (lags 1, 12, 13; one small hidden layer), forecast two years ahead, against the strong classical model:

neural net (teal) vs SARIMA (amber) · NNAR holdout MAE 93 vs SARIMA 39 · NN peak

The network's forecast is plausible in shape but visibly undershoots the trend, and its holdout error is more than double SARIMA's. With only 144 points it cannot learn the growth and seasonality reliably — exactly the data-starvation lesson, made concrete. This isn't a failure of neural networks; it's a failure to match method to data size. Hand the same architecture millions of points across thousands of series and the verdict flips.

14 · In code

A network is a few lines

The feed-forward NNAR is one estimator swap from the ML dive; the sequence models live in deep-learning frameworks:

# --- from scratch: forward pass + one backprop step ---
h    = np.tanh(W1 @ x + b1);  yhat = W2 @ h + b2   # forward (data in -> out)
t, lr = 1.0, 0.1
dy  = yhat - t                  # dL/dyhat
W2 -= lr * dy * h               # output-layer gradient + step
dz  = (dy * W2) * (1 - h**2)    # backprop through tanh
W1 -= lr * np.outer(dz, x)      # hidden-layer gradient + step
# loss 0.111 -> 0.061 after this single step

In practice you reach for a library, which adds optimisers, batching, and GPU support — the feed-forward NNAR is one estimator swap from the ML dive:

from sklearn.neural_network import MLPRegressor
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline

X, y_t = make_Xy(np.log(series), lags=[1, 12, 13])   # same table as the ML dive
net = make_pipeline(StandardScaler(),                 # networks need scaled inputs!
                    MLPRegressor(hidden_layer_sizes=(8,), activation='tanh',
                                 alpha=0.1, max_iter=6000))
net.fit(X, y_t)                                       # then recursive forecast as before

# RNN / LSTM / Transformer live in torch or keras, e.g.:
# model = nn.LSTM(input_size=1, hidden_size=32); ... train on (window_in, window_out) pairs
airline holdout (train 120, test 24)
NNAR (MLP, lags 1/12/13)   MAE 93    <- data-starved on 144 points
SARIMA                     MAE 39    ·  Holt-Winters  29  ·  lag regression  34
verdict: on one short series, classical wins; NNs need scale
15 · Recap

Powerful, hungry, situational

Neural networks bring genuine new capability to forecasting: learnable nonlinear features (the neuron and universal approximation), and architectures purpose-built for sequences — recurrent nets and LSTMs that carry memory, dilated convolutions that widen their view cheaply, attention and Transformers that reach anywhere in the past, and foundation models pretrained across millions of series for zero-shot use. But all of it rides on data. The same lag-feature framing from the ML dive gets you an NNAR in one line; the sequence models need a deep-learning framework and far more data. On a single short series the classical methods usually win, cheaply and interpretably — so the skill is knowing which regime you're in.

Neuron → network

Weighted sum + nonlinearity, stacked, approximates anything.

Four families

Feed-forward, recurrent/LSTM, convolutional/TCN, attention/Transformer.

Foundation models

Pretrained on millions of series; zero-shot, probabilistic.

Data decides

Scale and many series favour NNs; one short series favours classical.

Neural networks are the most flexible forecasters and the most data-hungry: a neuron's nonlinearity stacks into universal approximators, and sequence architectures add memory, wide receptive fields, and attention. They dominate at scale — many series, long histories, rich inputs — but on a lone short series, a classical model usually wins. Power is not the same as fit.
Capstone · the whole pipeline, end to end

Putting It All Together

Six dives, six tools. This capstone runs them as a single workflow on a series you haven't seen — monthly atmospheric CO₂ from Mauna Loa, 1990–2001. It has a relentless upward trend and a clean annual cycle, but unlike the airline data its seasonal swing stays a constant size (additive, not multiplicative), so it's a real test of whether the methods generalise. We'll look and decompose, read the autocorrelation, force stationarity, fit three rival models, grade them honestly on a holdout, forecast the winner forward, and confirm nothing was left on the table. Every number here is real, computed end to end.

The plan

One series, seven steps

A forecasting project isn't one model — it's a pipeline. Each stage is one of the dives in this lab, and the output of each feeds the next:

The discipline is to go in order. Skip the decomposition and you'll pick the wrong seasonal period; skip stationarity and the model misbehaves; skip evaluation and you'll ship a number you can't defend. Let's walk it.

Step 1 · decompose

Look before you model

Always plot first. The classical decomposition splits CO₂ into trend, season, and residual — and immediately tells us the structure to model:

observed = trend + seasonal + residual · note the seasonal band is a constant ±3.5 ppm — additive, not multiplicative

The trend climbs almost linearly from ~354 to ~371 ppm; the seasonal component repeats a fixed ±3.5 ppm swing (CO₂ dips each northern-hemisphere summer as plants grow, peaks in spring); and the residual is small and structureless. Crucially, the seasonal band doesn't grow with the level the way the airline's did — so here we use additive seasonality and skip the log transform. The anatomy dive's first question — additive or multiplicative? — already changes our recipe.

Step 2 · autocorrelation

Read the orders off the plots

The ACF of the raw series decays with a huge spike at lag 12 — the textbook signature of trend plus annual seasonality, and a loud demand to difference. After differencing, what survives at the seasonal lags tells us how many AR and MA terms, ordinary and seasonal, to shortlist. As in the autocorrelation dive: a PACF cutting off suggests the AR order, an ACF cutting off the MA order. For CO₂ the diagnostics point to one ordinary and one seasonal difference, with a small number of MA terms — candidates like SARIMA(1,1,1)(0,1,1)₁₂.

Step 3 · stationarity

Difference until the test passes

The ADF test makes "difference how much?" precise. Climbing the same ladder from the stationarity dive:

transformADF statp-valueverdict
raw0.080.965non-stationary
Δ (first difference)−2.340.158non-stationary
Δ₁₂ (seasonal difference)−2.270.183non-stationary
Δ Δ₁₂ (both)−5.160.000stationary ✓

Exactly as with the airline: neither difference alone suffices, but a regular and a seasonal difference together drive p below 0.05. That fixes d = 1 and D = 1 in the seasonal model — and tells the ML approach to predict the doubly-differenced series so its trees don't have to extrapolate.

Step 4 · candidate models

Three rivals and a baseline

With the structure known, we fit the three families this lab built, plus the baseline they must beat:

Seasonal naive

Predict the value from 12 months ago. The free baseline — beat it or go home.

SARIMA(1,1,1)(0,1,1)₁₂

Orders read from the ACF/PACF and ADF ladder above.

Holt–Winters (additive)

Level + trend + additive season, smoothing rates fit to history.

Lag-feature regression

Linear model on lags [1,2,3,12,13] — supervised ML on a windowed table.

Step 5 · evaluate

Grade them on a holdout

Train on the first 120 months, forecast the last 24, and score against the truth — a time-ordered split, never shuffled. Switch the metric; the story holds:

ppm · lower is better

Two lessons leap out. First, every real model demolishes the seasonal-naive baseline (MAE ~1.3 ppm) — sub-third-of-a-ppm errors, because CO₂ is extraordinarily regular. Second, the three sophisticated methods finish in a tight cluster, with the lag-feature regression nosing ahead (MAE 0.25) over Holt–Winters (0.28) and SARIMA (0.37). On a clean, strongly-structured series like this, the choice between good models barely matters — which is itself worth knowing before you agonise over it.

Step 6 · forecast

Two years of CO₂

Refit the chosen model on all 144 months and forecast 24 ahead. The trend continues, the annual cycle repeats at constant amplitude, and the band is tight because the series is so predictable:

144 months observed (cyan) → 24-month SARIMA forecast (gold) with 80% band · forecast peak ppm

The forecast says what we'd expect physically — CO₂ keeps rising, cresting near 377 ppm in the spring of the second year — and the narrow band reflects genuine confidence, not optimism: this series simply has very little noise to be uncertain about.

Step 7 · check residuals

Did we miss anything?

The final discipline from the evaluation dive: interrogate the residuals. If structure remains in the model's errors, there's signal left to capture. Running the Ljung–Box test on the fitted model's residuals gives Q(12) = 5.9, p = 0.92 — comfortably failing to reject whiteness. The errors are indistinguishable from noise: the model has wrung out the trend, the season, and the short-term autocorrelation, leaving nothing exploitable behind. That's the signal to stop — the forecast is as good as this series allows.

The decision log

What each step decided

The whole project, as a paper trail — each dive contributing one decision:

steptooldecision
1 Decomposeclassical decompositionadditive season, ~±3.5 ppm → no log
2 AutocorrelationACF / PACFtrend + lag-12 season → difference; small MA orders
3 StationarityADF ladderneed both Δ and Δ₁₂ → d=1, D=1
4 ModelsSARIMA · Holt–Winters · MLthree candidates + seasonal-naive baseline
5 Evaluateholdout MAE/RMSE/MAPEall beat baseline; lag-features win narrowly
6 Forecastrefit on all data24-month forecast, peak ~377 ppm
7 ResidualsLjung–BoxQ=5.9, p=0.92 → white, model is done
In code

The pipeline, condensed

import numpy as np
from statsmodels.tsa.seasonal import seasonal_decompose
from statsmodels.tsa.stattools import adfuller
from statsmodels.tsa.statespace.sarimax import SARIMAX
from statsmodels.stats.diagnostic import acorr_ljungbox

# 1 decompose — additive? then no log
seasonal_decompose(y, model='additive', period=12)
# 2-3 how many differences? climb until ADF passes
adfuller(np.diff(np.diff(y, 12)))            # p = 0.000 -> d=1, D=1
# 4-5 fit candidates, score on a time-ordered holdout
m = SARIMAX(y[:120], order=(1,1,1), seasonal_order=(0,1,1,12)).fit()
mae = np.mean(np.abs(y[120:] - m.forecast(24)))
# 6 refit on everything and forecast
final = SARIMAX(y, order=(1,1,1), seasonal_order=(0,1,1,12)).fit()
forecast = final.get_forecast(24)
# 7 are the residuals white?
acorr_ljungbox(final.resid, lags=[12])       # p = 0.92 -> done
CO2 holdout (train 120, test 24) — MAE in ppm
seasonal naive  1.31    SARIMA  0.37    Holt-Winters  0.28    lag-features  0.25
final forecast peaks ~377 ppm   ·   residual Ljung-Box p = 0.92 (white)
Recap

What you can now do

You've taken an unfamiliar series from raw numbers to a defensible, validated forecast using nothing but the tools in this lab. Decompose to see the structure and choose additive or multiplicative. Read the ACF and PACF to identify orders. Use the ADF test to decide how much to difference. Fit the classical families — ARIMA and exponential smoothing — and the ML reframing, all on the same footing. Split on time, score against a baseline with the right metric, and report the margin. Forecast the winner, then prove it's done by checking the residuals are white. That sequence is the entire discipline of classical forecasting, and it transfers to any series you'll meet.

Structure first

Decompose and plot before fitting anything.

Diagnose, don't guess

ACF/PACF and ADF choose the model, not intuition.

Compare honestly

Time-split, baseline, metric, margin.

Verify and stop

White residuals mean the job is done.

A forecast is a pipeline, not a model: decompose → autocorrelation → stationarity → fit → evaluate → forecast → check. Run it in order on any series, let the diagnostics drive the decisions, and the result is a number you can stand behind — which, on Mauna Loa CO₂, predicts the relentless climb past 377 ppm with errors under a third of a part per million.

Add up the parts

Trend, season & noise

A time series is usually three things stacked: a slow trend, a repeating seasonal swing, and random noise. Drag each to build a series and watch them combine.

Build the series

trend slope
seasonal amp
noise

Real series mix all three. Modelling means pulling them back apart — trend and season are signal you can forecast; noise is the floor you cannot.

Constant vs growing swing

Additive vs multiplicative

Does the seasonal swing stay the same size as the series climbs (additive), or grow with the level (multiplicative)? The answer decides whether you take a log first.

Seasonality type

Multiplicative seasonality (like airline passengers) becomes additive after a log transform — which is why log is the first move on a fanning-out series.

Plot it against its past

The lag plot

Plot each value against the value k steps earlier. A tight diagonal cloud means the series is strongly correlated with its past at that lag — the visual root of autocorrelation.

Lag

k = 1
correlation r =

At lag 1 a smooth series hugs the diagonal; push k up and the cloud loosens. At the seasonal lag (12 for monthly) the diagonal returns — a fingerprint of seasonality.

Correlation with the past

Autocorrelation (ACF)

The ACF is the correlation of a series with itself at every lag, plotted as a stem. Each pattern of memory leaves a different signature — switch the series to see them.

Series

White noise: all bars inside the band. AR(1): geometric decay. Seasonal: a spike at the period. Reading these shapes is how you pick a model.

What no structure looks like

White noise

White noise is a series of independent random draws — no trend, no season, no memory. It is the residual you hope to be left with, and the thing every model tries not to leave on the table. Reroll it.

Fresh draw

If a model’s residuals look like this — and their ACF stays inside the band — there is no structure left to capture. That is when you stop.

Each step is random

Random walk

Add a random step to the last value, over and over. The result wanders with no fixed mean — every run drifts somewhere new. It looks like it has trends, but they are an illusion of accumulated noise.

Watch it wander

Stock prices are nearly random walks — which is why their changes, not their levels, are what you model.

A random walk is the canonical non-stationary series: differencing it (taking step-to-step changes) gives back white noise. That is the “I” in ARIMA.

Stable stats over time

Stationarity

A series is stationary when its statistics — mean, variance — do not drift over time. Most forecasting assumes it; most raw series violate it. Watch the rolling mean (amber) stay flat or wander.

Pick a series

Stationary is what you transform a series toward: a wandering mean says “difference me”, a fanning variance says “log me”.

Subtract the previous

Differencing

Differencing replaces each value with the change from the one before it: Δyₜ = yₜ − yₜ₋₁. A trending series becomes a flat one around zero — the standard cure for a wandering mean.

View

One difference removes a linear trend; a seasonal difference (Δ₁₂) removes a yearly cycle. The number of differences is the “d” in ARIMA.

Slide φ toward 1

Unit root

Generate yₜ = φ·yₜ₋₁ + noise. When φ < 1 the series is pulled back to its mean (stationary); at φ = 1 it becomes a random walk; past 1 it explodes. Slide φ across the boundary.

Persistence

φ = 0.80
stationary — mean-reverting

φ = 1 is the “unit root” the Dickey-Fuller test hunts for. Failing to reject it means: difference before you model.

Tame growing variance

Log transform

When the seasonal swing grows with the level (multiplicative), a log squashes it to a constant width — turning multiplicative structure into additive, which the models can handle. Toggle it.

Scale

Log is the first move on any series whose ups and downs get bigger as it rises — sales, traffic, populations. Forecast the log, then exponentiate back.

Average a sliding window

Moving average

Replace each point with the average of a window around it. A wider window smooths harder — trading wiggles for lag. The simplest way to see the signal under the noise.

Window

width = 7

A centered moving average is also the first step of classical decomposition — it is how you estimate the trend before pulling out the season.

Weight recent more

Exponential smoothing

A moving average treats every point in its window equally. Exponential smoothing instead weights recent points more, with weights that fade geometrically: ℓₜ = α·yₜ + (1−α)·ℓₜ₋₁. Drag α.

Smoothing rate

α = 0.30

High α reacts fast (trusts the latest point); low α is smooth and sluggish.

This recursion is the engine of the whole exponential-smoothing family — add a trend and a seasonal term and you get Holt-Winters.

Split into parts

Decomposition

Classical decomposition reverses the build-a-series move: it pulls one series apart into trend, seasonal, and residual components. Read them top to bottom.

The four panels

Observed = Trend + Seasonal + Residual. The trend is a moving average; the seasonal is the average shape per month; the residual is what is left.

If the residual still shows a pattern, your trend or seasonal estimate missed something. A clean decomposition leaves residual that looks like white noise.

Depends on its own past

Autoregression (AR)

An AR model predicts the next value as a weighted sum of past ones: yₜ = φ·yₜ₋₁ + noise. The coefficient φ sets the personality — smooth and persistent, jittery, or oscillating. Drag it.

AR(1) coefficient

φ = 0.70
positive — smooth, persistent

Fitting an AR model is just least-squares regression of the series on its own lags — the bridge to the ML view of forecasting.

Series → table

Lag features

To forecast with any regressor, slide a window along the series: the last few values become input columns, the next value becomes the target. Watch the window slide and the training row it extracts.

The window

Three lags in (x₁ x₂ x₃), one value out (y). Every position the window stops at is one training example.

This single move — windowing — turns forecasting into ordinary supervised regression, and lets the entire ML toolbox loose on time series.

Point plus uncertainty

Forecast & interval

A forecast is never one line — it is a best guess plus a band of uncertainty that widens the further out you look. Watch the horizon extend and the cone of doubt open up.

The cone of uncertainty

The point forecast (line) is your single best guess; the shaded band is where the truth will likely fall. Honest forecasts always show it.

A point forecast without an interval hides how little you know. The band grows because errors compound step by step into the future.

Beat these first

Naive baselines

Before any model, two free forecasts set the bar. Naive: tomorrow equals today. Seasonal-naive: this month equals the same month last year. If your model cannot beat these, it is not earning its keep.

Baseline

For seasonal data, seasonal-naive is a surprisingly tough bar. Always report your error relative to it.

Never test the past

Time-ordered split

To grade a forecaster you must train on the past and test on the future. Never shuffle a time series before splitting — that leaks tomorrow into training and reports a fantasy score. Toggle the right and wrong way.

Split method

The shuffled split scatters future points into the training set; the model peeks ahead and scores far too well. The contiguous split is the only honest one.

Score the forecast

Error metrics

How wrong is a forecast? Measure the gaps between forecast and actual. MAE averages their size, RMSE punishes big misses harder, MAPE reports error as a percentage. Drag the forecast error to watch all three move.

Forecast quality

error
MAE =
RMSE =
MAPE =

RMSE ≥ MAE always; the gap between them grows when a few errors are huge. MAPE lets you compare across series of different scales.

Errors should be noise

Residual check

After fitting, inspect what is left over. A good model leaves white-noise residuals — no pattern, a flat ACF. Leftover structure (a wave, an ACF spike) means signal you failed to capture. Toggle a good vs bad fit.

The fit

Residual diagnostics are the last gate before trusting a forecast: top panel should look structureless, bottom panel’s bars should sit inside the band.

Seasonality as waves

Fourier terms

Any repeating seasonal shape can be built from a sum of sine waves — a few harmonics. Adding more captures sharper features. This is how regression models encode season compactly, with two columns per harmonic instead of a dummy per month.

Harmonics

# terms = 3

One harmonic is a smooth annual wave; more harmonics sharpen the corners (here, a square seasonal pulse).

For smooth seasonality two or three harmonics suffice — far fewer parameters than a dummy variable for every season.

Spot the odd points

Anomalies

An anomaly is a point that does not fit the recent pattern. A simple, effective detector: flag anything outside a band of ±k standard deviations around a rolling mean. Drag the threshold k to trade sensitivity for false alarms.

Sensitivity

k = 3.0 σ
flagged:

Lower k catches more real anomalies but also more false alarms; higher k is stricter. The right k depends on how costly each kind of mistake is.

Stack the cycles

Seasonal plot

Instead of one long line, overlay each year on the same Jan–Dec axis. The repeating shape jumps out, and the vertical spacing shows year-over-year growth. The fastest way to confirm a series is seasonal.

Years stacked

Each line is one year, light (early) to bright (recent). Peaks line up at the same months — that alignment is seasonality.

When every year traces nearly the same path, a seasonal model will work. Crossing, irregular lines say the season is unstable.

Change the frequency

Resampling

Real data often arrives finer than you need. Resampling aggregates it to a coarser frequency — daily to monthly — averaging away noise and revealing the underlying movement. Switch the frequency.

Aggregate to

Coarser frequency = less noise but fewer points and lost detail. Match the frequency to the decision: daily ops vs monthly planning.

Direct memory only

Partial autocorrelation

The ACF mixes direct memory with echoes passed down the chain. The PACF strips out the echoes, showing each lag’s direct effect. For an AR(p) process it cuts off sharply after lag p — that is how you read the AR order.

Process

ACF decays gradually; PACF cuts off at the AR order. The mirror rule names an MA process: PACF decays, ACF cuts off.

One series leads another

Cross-correlation

Sometimes one series moves before another — ad spend before sales, temperature before energy use. Cross-correlation finds that lead by sliding one series against the other. Shift it and watch them lock into step.

Shift the driver

lag = 0
correlation =

The shift that maximises correlation is the lead time — here the driver leads by about 6 steps. A leading indicator you can exploit for forecasting.

Subtract last year

Seasonal differencing

A regular difference kills a trend; a seasonal difference kills a yearly cycle by subtracting the value 12 months earlier: yₜ − yₜ₋₁₂. The repeating shape vanishes, leaving the year-on-year change. Toggle it.

View

Seasonal differencing is the capital-D in the seasonal ARIMA order (P,D,Q). Often one regular plus one seasonal difference is all a series needs.

Test, do not eyeball

Stationarity tests

Eyes can be fooled, so stationarity gets a formal test. The augmented Dickey–Fuller test returns a p-value: small (< 0.05) means stationary. Climb the transform ladder and watch the p-value crash through the line.

Transform

ADF and KPSS test opposite null hypotheses — agreeing on both is the gold standard before you trust a model.

The transform dial

Box-Cox transform

The log is one point on a dial. The Box–Cox family is governed by a power λ: λ = 1 leaves the series raw, λ = 0 is the log, and values between bend the curve to stabilise variance. Turn the dial.

Power λ

λ = 1.0
λ = 1: raw, untouched

Software can pick λ automatically to make the seasonal swings as even as possible — then you model the transformed series and invert at the end.

Level plus slope

Holt’s linear trend

Plain exponential smoothing forecasts a flat line — useless for a trending series. Holt smooths a second component, the slope, so the forecast inherits the trend. Compare the flat forecast with Holt’s sloped one.

Trend smoothing β

β = 0.30

β controls how fast the slope adapts. The dashed grey line is flat (no trend); the mint line is Holt.

Holt is two smoothing recursions running together — one for level, one for trend. Add a third for season and you have Holt-Winters.

Level, trend, season

Holt-Winters

Holt-Winters runs three smoothing recursions at once — level, trend, and season — so the forecast climbs and repeats the yearly shape. The complete exponential-smoothing model for seasonal data.

The three components

Each component is an exponentially-weighted average that trusts recent data most. The forecast = level + trend×h + the matching season.

Holt-Winters often matches ARIMA on seasonal series from completely different machinery — components instead of autocorrelation.

Robust, flexible split

STL decomposition

Classical decomposition assumes a fixed seasonal shape. STL lets the season evolve over time and shrugs off outliers, using local regression. Watch a seasonal amplitude that grows — and an STL seasonal component that grows with it.

Evolving season

Top: a series whose seasonal swing widens over time. Below: STL’s trend, its growing seasonal, and a clean remainder — something a fixed-shape method cannot do.

STL (Seasonal-Trend decomposition using Loess) is the modern default — flexible season, robust to spikes, and you control how fast each part adapts.

Memory of past shocks

Moving-average model (MA)

Not to be confused with the smoother: an MA(q) model builds the next value from the last few random shocks, yₜ = εₜ + θεₜ₋₁ + … Its fingerprint is an ACF that cuts off sharply after lag q. Toggle the order.

Order q

AR and MA are mirror images: AR has a decaying ACF and a sharp PACF; MA has a sharp ACF and a decaying PACF. ARMA combines both.

The ARIMA orders

Reading p, d, q

ARIMA bundles three integers. p = how many past values (AR), d = how many differences to reach stationarity (I), q = how many past shocks (MA). Dial each and see which diagnostic plot reveals it.

Set the orders

p (AR) 1
d (I) 1
q (MA) 1

The order-reading recipe: d from the ADF test (difference until stationary), p from the PACF cutoff, q from the ACF cutoff.

Orders for the season

Seasonal ARIMA

A seasonal series needs orders at the seasonal lag too. SARIMA adds a second triple (P,D,Q) applied every m steps. Its ACF shows spikes not just at lags 1–2 but at the season and its multiples — 12, 24, 36.

Seasonal structure

The full notation is ARIMA(p,d,q)(P,D,Q)ₘ. The seasonal spikes in the ACF are the tell that you need the second triple.

Penalise complexity

AIC & BIC

Bigger models always fit the training data better — so fit alone cannot choose orders. AIC and BIC add a penalty for each parameter, so the score bottoms out at a model that is complex enough but no more. Slide the order.

Model complexity

order = 3
AIC picks the dip — not the lowest training error.

BIC penalises parameters harder than AIC, so it favours simpler models — especially with lots of data. Pick the order at the minimum, not the edge.

Two ways to horizon H

Multi-step strategies

To forecast many steps ahead you either go recursive — predict one step, feed it back, repeat (errors compound) — or direct — train a separate model for each horizon (no feedback, more models). Watch the two uncertainty cones diverge.

Horizon

H = 18
recursive band grows faster (errors feed forward); direct grows slower.

Recursive is simple and needs one model; direct sidesteps compounding but trains H models. Most libraries default to recursive — know its cost at long horizons.

Refit and roll forward

Backtesting

One split gives one noisy score. Rolling-origin backtesting earns many: train up to a point, forecast the next chunk, score it, then roll the origin forward and refit. Watch the window march down the series.

Rolling origin

train grows, the test fold slides forward, and errors accumulate into an honest average across many origins.

Backtesting mimics how the model will actually be used — always forecasting genuine future data — and averages out the luck of any single split.

Compare across series

Scale-free error (MASE)

A MAE of 50 is tiny for airline counts and huge for interest rates — raw errors cannot be compared across series. MASE divides your error by the naive forecast’s error, giving one scale-free number: below 1 beats naive, above 1 loses.

Two series, very different scales

Raw MAE (left bars) is incomparable — one series is 100× the other. MASE (right bars) puts them on the same footing.

MASE is the metric of choice in forecasting competitions precisely because it lets you average performance across thousands of differently-scaled series.

The wisdom of forecasts

Combining forecasts

One of forecasting’s most reliable tricks: average several models. Their errors partly cancel, so the combination usually beats most — often all — of its members. Simple averaging is a famously hard baseline to top.

Four models + their average

Each thin line is one model’s forecast, scattered around the truth. The bold line is their mean — closer than the individuals.

Diversity is the key: combining models that err in different directions cancels error. Combining near-identical models gains nothing.

Outside information in

Exogenous drivers

Some movements come from outside the series — a promotion, a price change, the weather. ARIMAX / dynamic regression feeds those drivers in as extra inputs, so the model explains the spikes instead of treating them as noise. Toggle the driver on.

Use the driver?

The catch: to forecast the future you need the driver’s future values too — known for holidays and price plans, but itself a forecast for weather.

When the regime shifts

Change points

Sometimes a series does not drift — it jumps to a new regime (a policy change, a sensor swap). A change-point detector finds the split that best separates two stable stretches. Slide the candidate split and watch the cost bottom out at the true break.

Candidate split

position
within-segment spread = (minimise it)

Real detectors scan every position and pick the lowest cost automatically; many handle several change points and penalise adding too many.

Series predicting series

Vector autoregression (VAR)

When several series move together, model them jointly: each one’s next value depends on the recent past of all of them. VAR captures that feedback. Toggle the coupling and watch a shock in the top series ripple into the bottom one.

Coupling

VAR is the multivariate workhorse in economics; Granger causality then asks whether one series’ past genuinely helps predict another’s.

Cycles in the frequency domain

The periodogram

Instead of looking at time, look at frequency. The periodogram measures how much power sits at each possible cycle length — hidden periodicities show up as sharp peaks. Here a series built from a 12- and a 4-step cycle.

Time → frequency

Top: the raw series (two cycles tangled together). Bottom: the periodogram — power against period — with peaks at 12 and 4, the cycles hiding inside.

Spectral analysis reveals cycles you would never spot by eye, and underpins methods for irregular or very long seasonal patterns.

Clustered turbulence

Volatility (GARCH)

In finance the level is a random walk, but the volatility is predictable: calm and turbulent periods cluster. GARCH models that changing variance. Watch the conditional band breathe — wide in storms, tight in calm.

Conditional volatility

The line is daily returns; the shaded band is ±2 estimated standard deviations. Big moves bunch together — that clustering is what GARCH predicts.

You cannot forecast tomorrow’s return, but you can forecast its risk — which is what option pricing and risk limits need.

Track a hidden state

State space & Kalman

Many series are a clean hidden state seen through noisy measurements. The Kalman filter recovers the state by blending each prediction with each new observation — trusting whichever it deems more reliable. Drag how much it trusts the data.

Trust in measurements

gain = 0.30
Low gain = smooth but laggy; high gain = responsive but jumpy.

The Kalman filter is the engine behind state-space forecasting (and GPS, and spacecraft) — exponential smoothing is a special case of it.

Weekdays, holidays, events

Calendar effects

Many series march to the human calendar: weekdays differ from weekends, holidays spike or crater, paydays bump. These calendar features are predictable structure — encode them and the model stops treating them as noise.

Eight weeks, daily

Weekends are shaded; one holiday is marked. The inset shows the average by day-of-week — a clean, exploitable pattern.

Day-of-week, month, holiday flags and “days since payday” are standard regression columns — cheap features that often beat a fancier model.

When points go missing

Gaps & irregular time

Real series have holes — a sensor drops out, a market closes. Most models need an even grid, so you must decide how to fill the gaps. Toggle between leaving them and interpolating across.

Handle the gaps

Interpolation invents data — fine for a one-off plot, risky for modelling, since it can leak the future and shrink apparent uncertainty. Flag what you filled.

Trends fake a link

Spurious correlation

Two completely unrelated random walks will often look strongly correlated — just because both wander. Correlating raw trending series is a classic trap. Reroll two independent walks and watch the correlation lie.

Two unrelated walks

correlation =

The fix: correlate the changes, not the levels. Differenced, these two series show no correlation — because there never was one.

Are the residuals random?

Whiteness test

Eyeballing an ACF is subjective. The Ljung–Box test rolls the first h autocorrelations into one number Q and asks: could this all be chance? A small Q (large p-value) means the residuals are indistinguishable from white noise.

Residuals

Run Ljung-Box on a model’s residuals: pass means no structure left to model; fail means go back and add a term.

Two kinds of trend

Trend- vs difference-stationary

Not all trends are equal. A deterministic trend is a fixed line you remove by subtracting a fitted regression. A stochastic trend (a random walk) wanders and is only tamed by differencing. Using the wrong cure leaves the series non-stationary.

Cure

Detrending a random walk fails; differencing a deterministic trend over-differences and adds noise. The ADF test (with vs without a trend term) tells the two apart.

Local lines, stitched

Loess smoothing

A moving average weights every point in its window equally; loess fits a little weighted regression line at each point, so it bends smoothly with the local shape and respects the ends. Drag the span to set how local it stays.

Span (locality)

span = 0.30
Small span = wiggly & local; large span = smooth & global.

Loess is the engine inside STL and many trend estimates — flexible, end-aware, and tunable by a single span parameter.

Trend vs cycle

Trend & cycle (HP filter)

Economists split a series into a smooth trend and a wobbling cycle around it. The Hodrick–Prescott filter does this by trading data-fit against trend-smoothness, governed by λ. Crank λ up and the trend straightens out.

Smoothness λ

λ = 100
Low λ: trend hugs the data (tiny cycle). High λ: trend is nearly a line (big cycle).

The same idea (a Whittaker smoother) appears across fields; the catch is λ is a choice, and it can invent cycles that are really just artefacts.

Error, trend, season

The ETS family

Exponential smoothing is not one method but a family, named by three letters: Error, Trend, Season — each None, Additive, or (for some) damped/multiplicative. Toggle the parts and watch the forecast shape change, with the ETS code updating.

Trend

Season

ETS(A,N,N) is simple smoothing, (A,A,N) is Holt, (A,A,A) is additive Holt-Winters — one framework, automatically chosen by AIC.

The dark-horse winner

Theta method

A method so simple it was almost dismissed — then it won a major forecasting competition. Theta splits the series into a straight long-run trend and a smoothed short-run line, forecasts each, and averages them. Plain, robust, hard to beat.

Average of two views

The linear line captures the long-run direction; the smoothed line captures the recent level. Their average is the Theta forecast.

Theta’s lesson: a clever combination of two dead-simple components routinely beats elaborate models — simplicity plus combination is a powerful recipe.

Mostly zeros

Intermittent demand

Spare parts, rare events: demand that is zero most of the time with occasional spikes. Ordinary smoothing chases each zero to nonsense. Croston’s method forecasts the demand rate instead — average size divided by average gap.

Sparse demand

Bars are actual demand (mostly zero). The flat line is Croston’s steady rate — the expected demand per period, the only sensible thing to forecast here.

Standard error metrics also break on intermittent series (a flat-zero forecast scores great). It is a reminder to match the method to the data’s shape.

Calibrated uncertainty

Interval coverage

A 90% prediction interval is only honest if the truth lands inside it about 90% of the time. Calibration checks exactly that. Toggle a well-tuned model against an overconfident one whose intervals are far too narrow.

Interval width

Overconfident intervals are dangerous: they promise precision that is not there. Always validate coverage on held-out data, not just the point forecast.

A fan of quantiles

Quantile forecasts

A point forecast and a single interval throw away shape. Quantile forecasting predicts the whole distribution — the 10th, 25th, 50th, 75th, 90th percentiles — as a fan. Scored by the pinball loss, it rewards being honest about asymmetry.

The quantile fan

Each shaded layer is a quantile band; the bold line is the median. Wider fan = more uncertainty. Decisions can read the exact percentile they care about.

Quantile (probabilistic) forecasts are now standard in energy and retail — a 95th-percentile demand matters more than the average when you are sizing inventory.

Align warped shapes

Dynamic time warping

Two series can have the same shape but be stretched or shifted in time — point-by-point distance then calls them different. DTW warps the time axis to match peaks to peaks, measuring shape similarity. The grey links show the alignment it finds.

Elastic matching

The two curves are the same motif at different speeds. DTW links each point of one to its best match on the other — note the links fan out where the timing differs.

DTW powers speech recognition, gesture matching and clustering of differently-paced series — anywhere shape matters more than exact timing.

Does the past help predict?

Granger causality

“X Granger-causes Y” if X’s past improves the forecast of Y beyond what Y’s own past already gives. It is predictive precedence, not true causation. Compare a model using Y alone with one that adds X’s lags.

Does X help?

Granger causality is the backbone test in a VAR — but “predicts” is not “causes”; a common driver can make two series Granger-cause each other.

Parts sum to the total

Hierarchical forecasting

Sales split into regions; regions into stores. Forecast each level on its own and the parts will not add up to the total. Reconciliation adjusts them so the hierarchy stays coherent — and usually improves accuracy too.

Coherence

Three regions feed one total. After reconciliation the children sum exactly to the parent — a constraint independent forecasts violate.

Methods like MinT optimally combine every level’s forecast; the reconciled set both adds up and borrows strength across the hierarchy.

Tethered wanderers

Cointegration

Two series can each wander like a random walk — non-stationary on their own — yet stay tethered together, so a particular combination of them is stationary. Like a dog and its owner: both roam, the leash between them does not.

Two walks, one leash

Top: two non-stationary series drifting together. Bottom: their spread — stationary, mean-reverting. That stationary combination is what cointegration finds.

Cointegration underpins pairs trading and error-correction models: when the spread strays from its mean, bet on it snapping back.

Switching states

Regime switching

Some series flip between hidden regimes — calm and crisis, expansion and recession — each with its own mean and volatility. A Markov-switching model infers which regime is active when, instead of forcing one set of parameters on all of it.

Two hidden states

The series is coloured by its active regime; the bar beneath marks where each rules. One model with two personalities, switched by a hidden chain.

Regime models capture sudden behaviour changes that a single stationary model smears over — crucial in finance and macroeconomics.

Memory that lingers

Long memory

Most models have short memory: their ACF dies off geometrically in a few lags. Some real series — river flows, volatility — have long memory, with an ACF that decays so slowly it is still positive hundreds of lags out. Fractional differencing models it.

How fast does the ACF die?

Short memory drops to near-zero within a handful of lags; long memory lingers, decaying as a slow power law.

Long memory sits between stationary (d=0) and a unit root (d=1): a fractional d like 0.3 captures persistence a whole integer difference would destroy.

Systematically off?

Forecast bias

Accuracy is not enough — a forecast can be accurate on average yet always lean one way. Bias is a non-zero mean error: consistently over- or under-shooting. The tell is a cumulative error that drifts instead of hovering around zero. Toggle it.

The model

A drifting cumulative error means you are leaving money on the table the same way every period — easy to detect and easy to correct by adding the mean error back.

Distribution-free bands

Conformal intervals

Most prediction intervals assume the errors are Gaussian. Conformal prediction assumes nothing: it sets aside calibration data, looks at how big the errors actually were, and takes the 90th percentile of those as the half-width — guaranteeing roughly 90% coverage whatever the shape.

From residuals to a band

The histogram is the calibration errors. The 90% quantile of their size (amber line) becomes the interval half-width — no bell-curve assumption needed.

Conformal intervals are model-agnostic — wrap them around any forecaster, even a black-box neural net, and get honest coverage with a one-line guarantee.

Resample in blocks

Block bootstrap

To get uncertainty without assuming a formula, resample the data — but you cannot shuffle single points or you destroy the autocorrelation. The block bootstrap resamples contiguous chunks, preserving short-range dependence. Each reshuffle is a plausible alternative history.

Plausible alternate paths

Each faint line stitches together random blocks of the residuals onto the trend — the spread is a distribution-free forecast interval.

Block bootstrapping powers bagged forecasts and honest intervals when the error distribution is unknown — the block length must exceed the memory you want to keep.

An input’s delayed echo

Transfer functions

When an input drives a series, its effect is rarely instant — a price cut lifts sales today, tomorrow, and fading after. A transfer-function model captures that whole delayed, decaying response (the impulse response) instead of a single coefficient. Fire a pulse and watch the echo.

Decay of the effect

persistence

The input is a single pulse; the output rises after a short delay then decays geometrically. Higher persistence = longer echo.

Transfer-function (dynamic-regression) models generalise ARIMAX, letting each driver have its own lag shape — essential for advertising, pricing and policy effects.

Many cycles at once

Multiple seasonality

High-frequency data carries several seasonal cycles at once — hourly electricity has a daily rhythm and a weekly one; daily sales have weekly and yearly. Methods like TBATS and Prophet model them together. Peel the cycles apart.

Show component

A single seasonal period cannot fit two rhythms; you either add multiple seasonal terms (TBATS) or build them from Fourier harmonics at each frequency (Prophet).

Cycles that come and go

Wavelet transform

A Fourier periodogram assumes cycles are present the whole time. But real cycles start, stop and shift. The wavelet transform localises frequency in time — a 2-D scalogram showing which cycle is active when. Here a signal whose period shortens as it goes.

Time × scale

Top: a signal that speeds up over time. Below: the scalogram — brightness is power at each period (vertical) and time (horizontal). The bright ridge slides as the cycle changes.

Wavelets shine on non-stationary spectra — detecting regime changes, transients, and evolving seasonality that a global Fourier view would blur into one smear.

Pull back to the leash

Error correction (VECM)

If two series are cointegrated, the gap between them is temporary — an error-correction term drags it back whenever it strays. The vector error-correction model adds exactly that pull to a VAR. Drag the adjustment speed and watch the spread snap back faster or drift longer.

Adjustment speed γ

γ = 0.20

The arrows show the corrective force −γ·(spread): bigger when the spread is far from zero, always pointing home.

γ is the speed of adjustment: large means the relationship reasserts itself quickly; near zero means the “equilibrium” barely binds.

Different rules by level

Threshold models (TAR)

A linear model uses one rule everywhere. A threshold autoregression switches rules depending on where the series is — say, gently mean-reverting when high but explosive when low. The regime is set by the observed value, not a hidden state. Move the threshold.

Threshold

level

Points are coloured by which regime they fall in. The dynamics differ above and below the line — a nonlinear series no single AR can capture.

Threshold (and smooth-transition) models capture asymmetries — unemployment rising fast but falling slow, prices sticky one way — that linear models smooth over.

Zoom levels combined

Temporal aggregation

Forecast the same series at several time scales and merge them. The fine view captures short dynamics; the coarse view captures the trend and is less noisy. Combining across frequencies (as MAPA does) is more robust than betting on one. See the same series at three zoom levels.

Three resolutions

Monthly (faint), quarterly (mid), and annual (bold) views of one series. Each frequency reveals — and forecasts — a different slice of the structure.

Aggregating up averages out high-frequency noise and exposes the trend; the multi-frequency combination hedges against picking the wrong single scale.

Spikes vs steps

Outlier types

Not all anomalies are alike, and the fix differs. An additive outlier is a lone spike. A level shift is a permanent step to a new baseline. A transient change spikes then decays back. Misreading a level shift as a spike wrecks every later forecast. Toggle each.

Type of event

A level shift moves the whole future; a spike does not. Detecting which is which determines whether you patch one point or re-baseline the series.

Curve-fitting decomposition

Prophet-style models

Rather than model autocorrelation, Prophet-style models fit curves: a piecewise-linear trend that bends at changepoints, plus Fourier seasonality, plus holiday bumps — all added together. Robust to gaps and outliers, and easy for non-experts to steer.

Trend + season, added

The dashed line is the piecewise trend (dots mark changepoints where its slope bends); the bold curve adds Fourier seasonality and continues as the forecast.

It treats forecasting as a curve-fitting problem, not a stochastic process — wonderfully practical, though it ignores autocorrelation a statistical model would exploit.

Train across many series

Global models

The classical way fits one model per series — starving each on its own short history. A global model trains a single set of parameters across thousands of related series, borrowing strength. It is why machine-learning forecasters now win at scale.

One model, many series

Each sparkline is a short, noisy series. A local model sees only one; a global model learns shared patterns from all of them — and forecasts each better.

Global models dominated recent forecasting competitions; the trade is interpretability and per-series tailoring for accuracy and scale.

Sequence neural nets

Deep learning for TS

The modern deep toolkit reframes forecasting as sequence learning. RNN/LSTM carry a memory state; TCN stack dilated convolutions; Transformers attend across the whole window; N-BEATS stacks pure feed-forward blocks. Toggle to see how each wires a window of lags to a forecast.

Architecture

These shine on many long series with shared structure — but as the neural dive showed, on a single short series they are easily beaten by ARIMA. Power needs data.

Describe, then decide

Feature-based forecasting

Summarise each series by a handful of features — strength of trend, strength of seasonality, autocorrelation, spikiness — then let those numbers pick the model or cluster similar series. A whole series compressed to a fingerprint. Toggle two very different series.

Series fingerprint

Feature-based methods (tsfeatures, catch22, FFORMA) scale model selection to thousands of series — and reveal which series are even forecastable.

Repeats & surprises

Matrix profile

For every short window in a series, find its nearest twin elsewhere and record the distance. The result — the matrix profile — dips where a pattern repeats (a motif) and spikes where something is unlike anything else (a discord, i.e. an anomaly). One curve, both jobs.

Distance to nearest twin

Top: the series. Below: the matrix profile. The low point marks a repeated motif; the high point marks the discord — the most anomalous window.

The matrix profile makes motif and anomaly discovery fast and parameter-light — a Swiss-army knife for exploring long, unlabeled series.

Interactive · the forecasting loop, one step at a time

The forecasting machine — exponential smoothing

Every forecasting method runs the same loop through time: hold a state, predict the next value, see the error, then correct the state by a fraction of that error. Press Next to walk one stage at a time. Here the state is a single level and the correction fraction is a fixed gain α you choose.

step 1  ·  stage state gain α = 0.30
① state — what the machine carries
② predict  →  ③ error (recent steps)
▶ the arithmetic, this step
predicting through time
the errors it makes (innovations)
That loop — predict from the state, measure the error, nudge the state by gain×error — is exponential smoothing, and it is the skeleton of every method here. The gain α is a fixed dial: large α chases each new error (reactive), small α barely moves (smooth). Swap the predict box for a richer formula and you get ARIMA; let the machine compute its own gain and you get the Kalman filter — the loop never changes.
Interactive · the same loop, a richer predict

The forecasting machine — ARIMA

The exact same predict→error→correct loop — only the predict box changes. It now mixes the last value (the AR part, weight φ) with the last error fed forward (the MA part, weight θ). There is no separate gain dial: the correction is baked into θ. Everything tinted amber is the new predict; the rest is identical.

step 1  ·  stage state φ θ
① state — last value & last error
② predict AR + MA  →  ③ error
▶ the arithmetic, this step
predicting through time
the errors (each one feeds the next predict)
Look at what didn’t change: predict, measure the error, move on. All that moved is the predict box — it now leans on the last value (φ) and folds in a slice of the last error (θ). That θ is the correction term; ARIMA has no separate gain dial because the gain is learned once and frozen into the coefficients. Same loop, richer predict.
Interactive · the same loop, the gain computed

The forecasting machine — the Kalman filter

Still predict→error→correct — but now the machine carries its own uncertainty and uses it to compute the optimal gain K instead of you choosing it. Trust the data more when the model is unsure, less when measurements are noisy. The remarkable part: hold the noise dials fixed and K settles to a constant — and that constant is exponential smoothing’s α.

step 1  ·  stage predict process Q measure R
① predict state uncertainty grows
② predict measurement  →  innovation (recent)
▶ the arithmetic, this step
estimate & its uncertainty band
the gain K converging — its limit is α
This is the general forecasting machine. It computes the optimal gain K = P⁻/(P⁻+R) from how much it trusts its model (P) versus the measurements (R), then corrects exactly like every other machine. Freeze Q and R and K converges to a fixed number — and a Kalman filter with a constant gain is simple exponential smoothing, with K playing the role of α. One loop, three ways to set the gain — chosen (smoothing), baked in (ARIMA), or computed (Kalman). Every forecaster in this suite is a variation on it.
Interactive · the bridge to machine learning

The forecasting machine — learned from lag features

The first three machines carried a hand-built state. This one throws that away and learns: turn the series into a table of (recent lags → next value), then fit the weights by the very same gradient-descent loop as the ML lab. Press Next to walk one stage — the state is now the weights and the gain is the learning rate. Pool many series into one fit and it becomes a global model.

iter 0  ·  stage weights learning rate 0.20
① weights — learned, not built
② predict  →  ③ error  →  ④ cost (sample rows)
▶ the arithmetic, this step
fit & recursive forecast — sharpens each iteration
cost over iterations
This is the exact gradient-descent loop from the ML lab — weights, predict, error, cost, gradients, update — only the features are lags of the series and the target is its next value. So an AR model is just linear regression on lags, and the “correct the state by gain×error” of the other machines is here “update the weights by learning-rate×gradient.” Pool the rows of many series into one fit and you have a global model — the engine behind every modern large-scale forecaster. One loop underlies them all — only the gain ever changes.
Interactive · the same loop, now three states

The forecasting machine — Holt-Winters

The smoothing machine carried one number. Real series have a level, a trend, and a repeating season — so carry all three, and correct each one with its own gain (α, β, γ) from the same error. Same predict→error→correct loop; just more state. Watch it learn a seasonal shape and forecast it forward.

step 12  ·  stage state α0.40  β0.10  γ0.30
① state — level, trend, season
② predict  →  ③ error (recent steps)
▶ the arithmetic, this step
observed · fit · forecast
the errors it makes
Three states, three gains, one error — that is all Holt-Winters adds to exponential smoothing. The level tracks where the series is, the trend where it’s heading, and the seasonal indices the repeating shape; each is nudged by its own fraction (α, β, γ) of the one-step error. The forecast is just level + h·trend + the matching season, projected forward.
Interactive · the same loop, run on the variance

The forecasting machine — volatility (GARCH)

Every other machine forecasts the level of the series. This one forecasts its spread. Markets are calm for a while, then turbulent — volatility clusters. GARCH runs the same predict→error→correct loop on the variance: predict today’s variance from yesterday’s squared surprise (weight a) and yesterday’s variance (weight b). The band around the returns breathes with it.

step 1  ·  stage state react a0.12  persist b0.82
① state — current variance
② predict variance  →  ③ squared return
▶ the arithmetic, this step
returns with a ±2σ band that breathes
the conditional variance σ²
Same loop, different target. The level machines ask “where will the series be?”; GARCH asks “how far might it stray?” It carries a variance, predicts the next one from the last squared shock (a) plus its own memory (b), and the persistence a+b says how long calm or turbulent spells last. Forecast variance decays back toward the long-run level ω/(1−a−b) — the volatility you expect on a typical day.
Interactive · fit the residuals, again and again

The forecasting machine — gradient boosting

A different idea: don’t carry a state, stack corrections. Start with a flat guess, look at what’s left over (the residuals), fit a tiny tree to those leftovers, add a small fraction ν of it, and repeat. Each round chips away at the error. It is still predict→error→correct — but the correction is a little tree and the gain is the shrinkage ν. The modern workhorse of tabular forecasting.

round 0  ·  stage fit shrinkage ν0.30
① state — the ensemble so far
② predict  →  ③ residuals (sample points)
▶ the arithmetic, this step
data · the stacked fit (sharpens each round)
training error over rounds
Boosting turns forecasting into a sequence of tiny corrections: each new tree is fit to what the ensemble still gets wrong, and only a fraction ν is added so it never overshoots. Small ν needs more rounds but generalizes better. One honest limit: trees split on the inputs they saw, so on a raw time index they cannot extrapolate — the forecast goes flat past the data, unlike the parametric machines that carry a trend. That is why real boosting forecasters feed it lag and calendar features, not the clock.
Interactive · the same loop, but the state is a vector

The forecasting machine — vector autoregression (VAR)

Series rarely move alone — sales and ad-spend, rates and inflation, two coupled markets. VAR predicts several series at once, each from the last values of all of them. The state becomes a vector and the coefficients a matrix: A₁₂ says how much yesterday’s series 2 moves today’s series 1. Same predict→error loop — just wider. Drag the coupling and watch a shock in one series spill into the other.

step 1  ·  stage state y₂→y₁ A₁₂0.35  y₁→y₂ A₂₁-0.30
① state — the vector & the matrix
② predict both  →  ③ errors
▶ the arithmetic, this step
two coupled series · fit · forecast
impulse response: shock y₂, watch y₁
The whole loop is unchanged — predict, measure error, advance — but “predict” now reads a vector through a coefficient matrix, so every series helps forecast every other. The off-diagonal terms are the cross-couplings: set A₁₂ to zero and series 2 stops mattering to series 1. The impulse-response chart isolates that one wire — a single shock to y₂ and the echo it sends through y₁ — which is how economists read a VAR.
Interactive · the committee beats the experts

The forecasting machine — the ensemble

No single method wins everywhere. So run several simple forecasters side by side — a naive last-value, a drift line, the running mean, an exponential smoother — and average their forecasts. The blend cancels each one’s idiosyncratic mistakes, and the combined error is usually lower than any single member’s. Toggle members on and off and watch the running scores. The most reliable accuracy trick in forecasting.

step 2  ·  stage predict
① the members on the committee
② each predicts  →  ③ running error
▶ the arithmetic, this step
each member (thin) · the ensemble (bold)
mean abs error — ensemble vs members
Averaging works because the members err in different directions — one runs hot where another runs cold, and the mean lands between. As long as members are decent and not all wrong the same way, the ensemble’s error sits at or below the best single member, and it is far more robust to a bad pick. This is why competition-winning forecasts are nearly always combinations, not a single clever model.
Interactive · forecasting the whole distribution

The forecasting machine — Monte-Carlo simulation

Every other machine draws a single line (maybe with a band). This one asks a bigger question: what is the whole range of futures? Fit a model, then roll it forward hundreds of times with fresh random shocks each run. Each roll is one possible future; together they form a cloud, and the prediction interval is just where the middle 90% of those paths land. Draw paths one at a time and watch the fan and its distribution fill in.

paths drawn: 0 shock size σ1.0×
① the fitted model
② the latest path  →  ③ the interval
▶ the arithmetic, this step
simulated future paths · 90% band
distribution of the endpoint
A forecast interval is not a formula handed down — it is a statement about simulated futures: roll the model forward many times, and 90% of the paths fall inside the band. More shocks (σ) or more persistence (φ) fan the cloud wider. The histogram is the same paths read at the final horizon — the full predictive distribution, of which the familiar ± interval is just two slices. This is how modern probabilistic forecasters report uncertainty.
Interactive · forecasting spiky, mostly-zero demand

The forecasting machine — Croston (intermittent demand)

Spare parts, rare events, lumpy orders — series that are zero most of the time with occasional spikes. Smoothing them directly fails: the average gets dragged toward zero. Croston’s trick is to forecast two things separately, each with its own smoother: how big a demand is when it happens (size z) and how often it happens (interval p). The forecast rate is simply z divided by p — updated only on the periods when something actually arrives.

period 0  ·  stage observe smoothing α0.20
① the two smoothed estimates
② observe  →  ③ update or carry
▶ the arithmetic, this step
intermittent demand · forecast rate
size z and interval p
Croston splits an impossible smoothing problem into two easy ones. Because z and p only update when a demand occurs, the rate ignores the long runs of zeros that would otherwise pull a naive average down. The forecast is a flat rate z/p — the expected demand per period — which is exactly what inventory planning needs. It is the standard for slow-moving items, and a clean example of choosing the right thing to smooth.
Interactive · the learned machine, made nonlinear

The forecasting machine — neural network

The learned machine fit a straight line through the lags. But some series bend — they react differently when high than when low — and no single line can capture that. Slip a small hidden layer of tanh units between the lags and the output and the machine can bend its response. Same gradient-descent loop — forward pass, error, backprop the gradients, nudge every weight — just with more knobs. Watch it curve to fit a shape a linear model can’t.

iter 0  ·  stage weights learning rate0.15
① the network & its weights
② forward pass  →  ③ error (sample rows)
▶ the arithmetic, this step
series · nonlinear fit · forecast
cost over iterations
A neural forecaster is the learned machine with a bend in it: the hidden tanh layer lets the prediction curve, so the same 2 lags can map to a nonlinear response. Training is identical in spirit — measure the error, send it backward through the layers (backprop) to see how each weight moved it, and step downhill. More units bend more sharply but overfit faster; the learning rate sets the step size. It is the same predict→error→correct loop, now with thirteen knobs instead of three.
Interactive · split, forecast each piece, recombine

The forecasting machine — the Theta method

The surprise winner of the big forecasting competitions, and it is almost embarrassingly simple. Split the series into two theta lines: a straight regression trend (θ = 0) that captures the long-run direction, and an amplified copy (θ = 2) that exaggerates the short-run wiggles. Forecast the trend by extending the line and the amplified copy by smoothing it flat — then average the two. The result is exponential smoothing with exactly half the drift baked back in.

stage trend θ2.0  α0.30
① the decomposition
② the two theta lines (recent)
▶ the arithmetic, this step
series · trend · θ-line · combined forecast
two forecasts averaged into one
Theta works by forecasting two things that are each easy: a trend line you simply extend, and an amplified series you smooth to a flat level. Averaging them gives a forecast that keeps moving — but at half the raw trend’s slope — which turns out to be a remarkably hard baseline to beat. The θ knob sets how much the second line exaggerates; the recombination weights (1/θ and 1−1/θ) always put it back together so the pieces sum to the whole.
Interactive · different rules in different states

The forecasting machine — regime switching

One set of dynamics is not always enough — markets behave differently in booms and busts, demand differently in and out of season. A threshold model carries two AR machines and switches between them based on where the series sits: below the threshold τ it follows one rule, above it another. Each step, the machine checks which regime is active, predicts with that regime’s coefficients, and the crossing of τ flips the behaviour. Watch the series cycle as it bounces between the two laws.

step 1  ·  stage state threshold τ40
① state — which regime is active
② predict in-regime  →  ③ error
▶ the arithmetic, this step
series coloured by active regime
phase plot: yₜ vs yₜ₋₁
A threshold (SETAR) model is the simplest way to let dynamics change: a single value τ splits the line in two, and each side gets its own intercept and slope. The phase plot makes it vivid — two regression lines meeting at τ, with the series hopping between them as it crosses. Because the low regime pushes up and the high regime pulls down, the series settles into a self-sustaining cycle that no single linear AR could ever produce.
Menu ← → move · Esc menu · 1–9 jump