Twice now this track has hinted that its models are “really state-space models” — ETS in lesson 2, ARIMA’s likelihood in lesson 3. This lesson opens the box. The state-space view is the track’s unifying formalism, and its crown jewel — the Kalman filter — is arguably the most deployed algorithm of the twentieth century: it navigated Apollo, sits in every GPS receiver and drone, and computes the exact likelihood of every structural time-series model. Better still, you have already derived most of it: it is the Bayesian update lesson running on a moving target.
The model: what you want is not what you see
Two equations, two admissions of ignorance:
with , . The state is what you care about (true demand level, true position, true temperature); is what you get (bookings, GPS pings, a cheap sensor). The simplest instance — the local level model, , — is already profound: it says the world drifts () and the measurement lies (), and asks for the best running belief about the truth. Note it is exactly a random walk observed in noise — ARIMA(0,1,1) in disguise, and SES’s optimal home, as your exercises have twice foreshadowed.
The filter: Bayes, recursively
Maintain a Gaussian belief and alternate two steps.
Predict — push the belief through the dynamics; uncertainty grows:
Update — treat the prediction as the prior, the new observation as evidence, and apply the Gaussian-conjugacy algebra of the Bayesian lesson. For the scalar local level it collapses to three unforgettable lines:
The Kalman gain is a trust dial set by the noise ratio: prediction uncertain relative to the sensor () ⟹ , believe the data; sensor noisy () ⟹ , believe the model. And “move your estimate a fraction toward the surprise” is — the third time this shape appears in the track — exponential smoothing, now with its α derived from first principles rather than tuned: in steady state, SES’s optimal α is exactly the converged Kalman gain of the local level model.
At the correct settings (√Q ≈ 0.22, √R = 1), the readout shows the filter’s whole value proposition: RMSE against the hidden truth roughly HALVES relative to trusting the raw observations — information extracted by nothing but two variances and Bayes. Now lie to it. Crank √Q up: the filter thinks the world is jumpy, K rises toward 1, the estimate glues itself to the noisy dots — smoothing gone. Crush √Q down: the filter thinks truth barely moves, K falls toward 0, and the estimate becomes a serene, confidently WRONG lag — watch it sail straight through the truth’s turns while its ±2σ band stays slim. That band tracks the filter’s SELF-assessed uncertainty, and a mis-specified filter is miscalibrated exactly when it is most confident. Q and R are the model; the filter is only as honest as they are.
Why this is the track’s master formalism
- Every ETS and ARIMA model has a state-space form, and the filter’s
one-step prediction errors give the exact Gaussian likelihood — this is
literally how
statsmodels/forecastcompute the MLEs of the last two lessons. One algorithm underwrites the whole classical stack. - Missing data is trivial: no observation, no update — just predict. Irregular sampling, sensor dropouts, holidays: handled by the same three lines. (Try doing that to a bare ARIMA recursion.)
- Smoothing (the RTS backward pass) refines every past state using the full series — for retrospective analysis; the filter alone is the honest choice for forecasting, since it uses only what was known at the time.
- Structural models: add trend, seasonal and regression states and you get interpretable components with uncertainty — the Bayesian structural time series behind causal-impact analyses.
- Beyond linear-Gaussian: real dynamics bend the assumptions, and the extensions map the terrain — EKF (linearize, the Jacobian doing the work the Matrix Calculus lesson trained), UKF (sample sigma points), and the particle filter (Monte Carlo over states — the 1/√n lesson’s machinery chasing a distribution through time). Each trades exactness for realism; all keep the predict–update heartbeat.
import numpy as np
rng = np.random.default_rng(3)
n, Q, R = 200, 0.05, 1.0
x = np.cumsum(np.sqrt(Q) * rng.standard_normal(n)) # hidden truth
y = x + np.sqrt(R) * rng.standard_normal(n) # what you see
m, P, est = y[0], 10.0, []
for t in range(n):
Pp = P + Q # predict
K = Pp / (Pp + R) # gain
m = m + K * (y[t] - m) # update on surprise
P = (1 - K) * Pp
est.append(m)
rmse = lambda a: np.sqrt(np.mean((np.array(a) - x) ** 2))
print(rmse(y), rmse(est), K) # filter beats raw; K ≈ steady state
Exercises
Work these before the next lesson
- Derive the scalar update step from Gaussian conjugacy: prior , likelihood — show the posterior mean is with . (You did this algebra in the Bayesian lesson; here it just runs every tick.)
Solution
Worked solutions are part of Premium — unlock all of them for £5/month →
- 5 more exercises — each with a worked solution — are part of Premium. Unlock everything for £5/month →
References
- R. E. Kalman, “A New Approach to Linear Filtering and Prediction Problems”, 1960 — the paper.
- J. Durbin & S. J. Koopman, Time Series Analysis by State Space Methods, 2nd ed. — the reference for everything here.
- S. Scott & H. Varian, “Predicting the Present with Bayesian Structural Time Series”, 2014 — structural models at work.