#+OPTIONS: H:3 num:nil toc:nil \n:nil @:t ::t |:t ^:nil -:t f:t *:t TeX:t LaTeX:t skip:t d:t tags:not-in-toc creator:t timestamp:nil author:nil title:nil html5-fancy:t
#+HTML_DOCTYPE: html5
#+HTML_HEAD:
#+HTML_HEAD:
#+HTML_HEAD:
#+HTML_HEAD:
#+HTML_HEAD:
#+HTML_HEAD:
#+LANGUAGE: en-GB
#+STARTUP: latexpreview
#+TITLE: The Kalman filter and the Hodrick-Prescott filter
#+DATE: September 2026
#+AUTHOR: Stéphane Adjemian
#+EMAIL: stephane.adjemian@univ-lemans.fr
#+PROPERTY: header-args:python :python /tmp/blog-kalman/bin/python
#+BEGIN_QUOTE
The Kalman filter recursively computes the distribution of an unobserved
state vector given past observations, in a linear Gaussian model written in
state space form. This note derives in detail the filter, the smoother,
which also exploits future observations, and the likelihood, which comes out
as a by-product of the filter. It studies the convergence of the filter
towards a steady state and, for the local level model, its link with
exponential smoothing. The application shows that the Hodrick-Prescott
filter is the smoother of a particular trend model, which makes it possible
to estimate by maximum likelihood the smoothing parameter that is usually
fixed a priori. The computations use French GDP per capita since 1820 and
are done in Python, with an implementation of the filter in about thirty
lines. Solutions to the exercises are given in collapsible blocks.
#+END_QUOTE
\\
\\
\\
#+BEGIN_SRC bash :results silent :exports none :async t
python3 -m venv /tmp/blog-kalman
source /tmp/blog-kalman/bin/activate
pip install numpy scipy matplotlib pandas openpyxl statsmodels
#+END_SRC
#+begin_src python :session kalman-en :exports none :results none
import os
import urllib.request as url
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy import sparse
from scipy.sparse.linalg import spsolve
from scipy.linalg import solve_discrete_lyapunov, solve_discrete_are
from scipy.optimize import minimize, minimize_scalar
from scipy.stats import chi2
rng = np.random.default_rng(2014)
#+end_src
* State space models
:PROPERTIES:
:CUSTOM_ID: modele-etat-mesure
:END:
A state space model separates what is observed, a series $y_t$, from the
unobserved dynamics that generate it, summarised by a state vector
$\alpha_t$. The framework is very general: ARMA processes, unobserved
components models (trend, cycle, seasonality), regressions with
time-varying coefficients and linearised DSGE models can all be written in
this form. We follow the notation of Harvey (/Forecasting, Structural Time
Series Models and the Kalman Filter/, 1989) and of Durbin and Koopman (/Time
Series Analysis by State Space Methods/, 2012), up to a difference in the
timing of the state equation.
#+BEGIN_definition
A state space model is defined by a measurement equation and a state
equation:
\begin{equation*}
\begin{aligned}
y_t &= Z_t\alpha_t + d_t + \varepsilon_t, &\qquad \varepsilon_t &\sim \mathcal N(0, H_t),\\
\alpha_t &= T_t\alpha_{t-1} + c_t + R_t\eta_t, &\qquad \eta_t &\sim \mathcal N(0, Q_t),
\end{aligned}
\end{equation*}
for $t = 1, \dots, n$, with initial condition
\(\alpha_0 \sim \mathcal N(a_0, P_0)\). The observation $y_t$ belongs to
$\mathbb R^p$, the state $\alpha_t$ to $\mathbb R^m$ and the state
disturbance $\eta_t$ to $\mathbb R^r$. The matrices $Z_t$ of size
$p\times m$, $T_t$ of size $m\times m$, $R_t$ of size $m\times r$, $H_t$
of size $p\times p$ and $Q_t$ of size $r\times r$, as well as the vectors
$d_t$ and $c_t$, are non-random. The variables $\alpha_0$,
$\varepsilon_1, \dots, \varepsilon_n$ and $\eta_1, \dots, \eta_n$ are
mutually independent.
#+END_definition
We denote by $Y_t = \{y_1, \dots, y_t\}$ the information available at date
$t$, with $Y_0 = \emptyset$, and by $n$ the sample size, the letter $T$
being reserved for the transition matrix. The state vector summarises the
whole past: the sequence of states is a Markov chain and, given the states,
the observations are independent, $y_t$ depending only on $\alpha_t$. The
selection matrix $R_t$ allows for fewer shocks than states, which is the
rule as soon as the state contains lagged values.
** Examples
:PROPERTIES:
:CUSTOM_ID: exemples
:END:
#+BEGIN_exemple
The local level model is a random walk observed with noise:
\begin{equation*}
y_t = \mu_t + \varepsilon_t,\qquad \mu_t = \mu_{t-1} + \eta_t,
\end{equation*}
with $\varepsilon_t \sim \mathcal N(0, \sigma_\varepsilon^2)$ and
$\eta_t \sim \mathcal N(0, \sigma_\eta^2)$. The state is the level,
$\alpha_t = \mu_t$, and the system matrices are scalars:
\(Z = T = R = 1\), \(H = \sigma_\varepsilon^2\), \(Q = \sigma_\eta^2\) and
$d = c = 0$.
#+END_exemple
#+BEGIN_exemple
The local linear trend model adds a stochastic slope $\beta_t$ to the
level:
\begin{equation*}
y_t = \mu_t + \varepsilon_t,\qquad
\mu_t = \mu_{t-1} + \beta_{t-1} + \eta_t,\qquad
\beta_t = \beta_{t-1} + \zeta_t,
\end{equation*}
where the three disturbances are independent Gaussian white noises, with
variances $\sigma_\varepsilon^2$, $\sigma_\eta^2$ and $\sigma_\zeta^2$. The
state is $\alpha_t = (\mu_t, \beta_t)'$ and
\begin{equation*}
Z = \begin{pmatrix} 1 & 0 \end{pmatrix},\quad
T = \begin{pmatrix} 1 & 1\\ 0 & 1 \end{pmatrix},\quad
R = I_2,\quad
H = \sigma_\varepsilon^2,\quad
Q = \begin{pmatrix} \sigma_\eta^2 & 0\\ 0 & \sigma_\zeta^2 \end{pmatrix}.
\end{equation*}
The special case $\sigma_\eta^2 = 0$ is the /smooth trend model/: the level
is no longer disturbed directly, only the slope is, and the trend is a
smooth curve. Only the second column of $R$ remains, \(R = (0, 1)'\), and
\(Q = \sigma_\zeta^2\). This model plays the leading role in the
application.
#+END_exemple
#+BEGIN_exemple
An autoregressive process of order $p$,
\(y_t = \phi_1 y_{t-1} + \dots + \phi_p y_{t-p} + \eta_t\), is written in
state space form by stacking the lagged values in the state,
\(\alpha_t = (y_t, y_{t-1}, \dots, y_{t-p+1})'\):
\begin{equation*}
Z = \begin{pmatrix} 1 & 0 & \cdots & 0 \end{pmatrix},\quad
T = \begin{pmatrix}
\phi_1 & \phi_2 & \cdots & \phi_{p-1} & \phi_p\\
1 & 0 & \cdots & 0 & 0\\
0 & 1 & \cdots & 0 & 0\\
\vdots & & \ddots & & \vdots\\
0 & 0 & \cdots & 1 & 0
\end{pmatrix},\quad
R = \begin{pmatrix} 1\\ 0\\ \vdots\\ 0 \end{pmatrix},
\end{equation*}
with $H = 0$ and $Q = \sigma^2$. The observation is perfect, without
measurement error: all the uncertainty lies in the state.
#+END_exemple
#+BEGIN_exemple
For an ARMA($p$, $q$) process,
\(y_t = \sum_{i=1}^p \phi_i y_{t-i} + \eta_t + \sum_{j=1}^q \theta_j\eta_{t-j}\),
Harvey (1989) sets $s = \max(p, q+1)$, pads the coefficients with zeros,
$\phi_i = 0$ for $i > p$ and $\theta_j = 0$ for $j > q$, and takes a state
of dimension $s$ with
\begin{equation*}
Z = \begin{pmatrix} 1 & 0 & \cdots & 0 \end{pmatrix},\quad
T = \begin{pmatrix}
\phi_1 & 1 & 0 & \cdots & 0\\
\phi_2 & 0 & 1 & \cdots & 0\\
\vdots & & & \ddots & \\
\phi_{s-1} & 0 & 0 & \cdots & 1\\
\phi_s & 0 & 0 & \cdots & 0
\end{pmatrix},\quad
R = \begin{pmatrix} 1\\ \theta_1\\ \vdots\\ \theta_{s-1} \end{pmatrix},
\end{equation*}
$H = 0$ and $Q = \sigma^2$. For an ARMA(1,1), the state
\(\alpha_t = (\alpha_{1,t}, \alpha_{2,t})'\) satisfies
\(\alpha_{2,t} = \theta\eta_t\) and
\begin{equation*}
\alpha_{1,t} = \phi\alpha_{1,t-1} + \alpha_{2,t-1} + \eta_t
= \phi\alpha_{1,t-1} + \eta_t + \theta\eta_{t-1},
\end{equation*}
and since $y_t = \alpha_{1,t}$, we indeed recover the ARMA(1,1) process.
The case $p = 0$ gives the representation of an MA($q$) process.
#+END_exemple
#+BEGIN_remarque
The state space representation of a process is not unique. For any
invertible $m\times m$ matrix $M$, the state \(\alpha_t^* = M\alpha_t\)
defines a model with the same observations, whose matrices are
\begin{equation*}
\begin{gathered}
Z^* = ZM^{-1},\quad T^* = MTM^{-1},\quad R^* = MR,\\
c^* = Mc,\quad a_0^* = Ma_0,\quad P_0^* = MP_0M'.
\end{gathered}
\end{equation*}
The filter gives \(a_{t|t}^* = Ma_{t|t}\) and \(P_{t|t}^* = MP_{t|t}M'\),
but the innovations, their variances, the likelihood and the forecasts of
$y_t$ are unchanged. For the MA(1) process
\(y_t = \eta_t + \theta\eta_{t-1}\), Harvey's form above, with state
$(y_t, \theta\eta_t)'$, and that of Hamilton (/Time Series Analysis/, 1994),
with state $(\eta_t, \eta_{t-1})'$,
\(Z = \begin{pmatrix} 1 & \theta \end{pmatrix}\),
\(T = \begin{pmatrix} 0 & 0\\ 1 & 0 \end{pmatrix}\) and $R = (1, 0)'$, are
related by
\begin{equation*}
M = \begin{pmatrix} 1 & \theta\\ \theta & 0 \end{pmatrix},
\end{equation*}
which is invertible as soon as $\theta \neq 0$. The dimension of the state
can also be increased by components that the shocks do not reach or that
the observations do not see. A representation is minimal when it is
controllable and observable, and two minimal representations of the same
relation between shocks and observations are related by a change of basis
(Hannan and Deistler, /The Statistical Theory of Linear Systems/, 1988, chapter 2).
Finally, different shock structures can generate the same distribution of
the observations: the local level model, with two independent shocks, has
the distribution of an ARIMA(0,1,1) process with a single shock, as shown
in the section on the [[#forme-reduite][reduced form]]. The choice of
representation is therefore a matter of convenience for filtering and the
likelihood. On the other hand, the parameters of a representation whose
matrices were all free would not be identified: structural models remove
this indeterminacy by fixing $Z$, $T$ and $R$.
#+END_remarque
A stochastic seasonal component, a cycle or explanatory variables are added
in the same way, by augmenting the state vector: this is what makes the
framework useful in practice. In what follows, the system matrices are
constant and there are no deterministic terms ($d = c = 0$). The filter
code handles vector observations, but the likelihood functions and the
applications assume a scalar observation ($p = 1$). A model is represented
by a function that returns its five matrices:
#+begin_src python :session kalman-en :exports code :results none
def local_level(s2_eps, s2_eta):
Z, T, R = np.eye(1), np.eye(1), np.eye(1)
return Z, T, R, np.array([[s2_eps]]), np.array([[s2_eta]])
def local_linear_trend(s2_eps, s2_eta, s2_zeta):
Z = np.array([[1.0, 0.0]])
T = np.array([[1.0, 1.0],
[0.0, 1.0]])
R = np.eye(2)
return Z, T, R, np.array([[s2_eps]]), np.diag([s2_eta, s2_zeta])
def smooth_trend(s2_eps, s2_zeta):
Z, T, _, H, _ = local_linear_trend(s2_eps, 0.0, s2_zeta)
R = np.array([[0.0],
[1.0]])
return Z, T, R, H, np.array([[s2_zeta]])
#+end_src
* The Kalman filter
:PROPERTIES:
:CUSTOM_ID: filtre
:END:
** Objective
:PROPERTIES:
:CUSTOM_ID: objectif
:END:
We seek the distribution of the state $\alpha_t$ given information $Y_s$.
We denote by
\begin{equation*}
a_{t|s} = \mathbb E\left[\alpha_t \mid Y_s\right],\qquad
P_{t|s} = \mathbb V\left[\alpha_t \mid Y_s\right]
\end{equation*}
its conditional mean and variance matrix. Three cases arise: /prediction/
if $s = t-1$, /filtering/ if $s = t$ and /smoothing/ if $s = n$. The Kalman
filter computes $a_{t|t-1}$, $P_{t|t-1}$, $a_{t|t}$ and $P_{t|t}$ for
$t = 1, \dots, n$, recursively, incorporating one observation at each step.
In a Gaussian model, the conditional distribution of the state is normal,
hence entirely described by these two moments, and the conditional
expectation is the best predictor of $\alpha_t$ in the mean squared error
sense, among all functions of the observations.
#+BEGIN_remarque
Without the normality assumption, but with disturbances uncorrelated with
each other and over time, the recursions derived below remain valid if the
conditional expectation is replaced by the linear projection: $a_{t|t}$ is
then the best /linear/ predictor of $\alpha_t$ as a function of
$y_1, \dots, y_t$, and $P_{t|t}$ the variance matrix of its error (Harvey,
1989, chapter 3).
#+END_remarque
** A lemma on the normal distribution
:PROPERTIES:
:CUSTOM_ID: lemme-gaussien
:END:
The whole derivation rests on an elementary result, the conditional
distribution of a Gaussian vector.
#+BEGIN_lemma
Let
\begin{equation*}
\begin{pmatrix} x\\ y \end{pmatrix} \sim
\mathcal N\left(\begin{pmatrix} \mu_x\\ \mu_y \end{pmatrix},
\begin{pmatrix} \Sigma_{xx} & \Sigma_{xy}\\ \Sigma_{yx} & \Sigma_{yy} \end{pmatrix}\right)
\end{equation*}
be a Gaussian vector, where $\Sigma_{yy}$ is invertible. The distribution
of $x$ given $y$ is normal:
\begin{equation*}
x \mid y \sim \mathcal N\left(\mu_x + \Sigma_{xy}\Sigma_{yy}^{-1}(y - \mu_y),\;
\Sigma_{xx} - \Sigma_{xy}\Sigma_{yy}^{-1}\Sigma_{yx}\right).
\end{equation*}
#+END_lemma
#+BEGIN_proof
Let \(e = x - \mu_x - \Sigma_{xy}\Sigma_{yy}^{-1}(y - \mu_y)\). The vector
$(e, y)$ is an affine transformation of $(x, y)$, hence Gaussian. The
covariance between $e$ and $y$ is zero:
\begin{equation*}
\mathrm{Cov}(e, y) = \Sigma_{xy} - \Sigma_{xy}\Sigma_{yy}^{-1}\Sigma_{yy} = 0.
\end{equation*}
For a Gaussian vector, zero correlation is equivalent to independence: $e$
is independent of $y$. In the decomposition
\begin{equation*}
x = \mu_x + \Sigma_{xy}\Sigma_{yy}^{-1}(y - \mu_y) + e,
\end{equation*}
the first term is a function of $y$ and the second is independent of $y$.
Given $y$, the vector $x$ is therefore the sum of a constant and of a
Gaussian vector with distribution $\mathcal N(0, \mathbb V[e])$, which
establishes the normality of the conditional distribution and gives its
mean. Its variance is
\begin{equation*}
\mathbb V[e] = \mathrm{Cov}(e, x) = \Sigma_{xx} - \Sigma_{xy}\Sigma_{yy}^{-1}\Sigma_{yx},
\end{equation*}
the first equality following from the fact that $e$ differs from $x$ by an
affine function of $y$, with which $e$ is uncorrelated.
#+END_proof
** The filter equations
:PROPERTIES:
:CUSTOM_ID: equations-filtre
:END:
#+BEGIN_proposition
Starting from $a_{0|0} = a_0$ and $P_{0|0} = P_0$, the conditional moments
of the state are computed for $t = 1, \dots, n$ by a /prediction/ step,
\begin{equation*}
\begin{aligned}
a_{t|t-1} &= T_t a_{t-1|t-1} + c_t,\\
P_{t|t-1} &= T_t P_{t-1|t-1} T_t' + R_t Q_t R_t',
\end{aligned}
\end{equation*}
followed by an /updating/ step,
\begin{equation*}
\begin{aligned}
v_t &= y_t - Z_t a_{t|t-1} - d_t, & F_t &= Z_t P_{t|t-1} Z_t' + H_t,\\
K_t &= P_{t|t-1} Z_t' F_t^{-1}, & &\\
a_{t|t} &= a_{t|t-1} + K_t v_t, & P_{t|t} &= (I_m - K_t Z_t) P_{t|t-1}.
\end{aligned}
\end{equation*}
The distribution of $\alpha_t$ given $Y_t$ is normal, with mean $a_{t|t}$
and variance $P_{t|t}$. The vector $v_t$ is the /innovation/, $F_t$ its
variance matrix and $K_t$ the /gain/ of the filter.
#+END_proposition
#+BEGIN_proof
We proceed by induction. Suppose that the distribution of $\alpha_{t-1}$
given $Y_{t-1}$ is $\mathcal N(a_{t-1|t-1}, P_{t-1|t-1})$, which holds for
$t = 1$ since $Y_0$ is empty.
/Prediction./ The observations $y_1, \dots, y_{t-1}$ are functions of
$\alpha_0$ and of the disturbances $\varepsilon_s$ and $\eta_s$ for
$s \leq t-1$: like $\alpha_{t-1}$, they are independent of $\eta_t$. Given
$Y_{t-1}$, the state \(\alpha_t = T_t\alpha_{t-1} + c_t + R_t\eta_t\) is
therefore an affine transformation of two independent Gaussian vectors. Its
distribution is normal, with mean $T_t a_{t-1|t-1} + c_t$ and variance
$T_t P_{t-1|t-1} T_t' + R_t Q_t R_t'$.
/Joint distribution of the state and the observation./ The disturbance
$\varepsilon_t$ is independent of $\alpha_t$ and $Y_{t-1}$. Given
$Y_{t-1}$, the pair formed by $\alpha_t$ and
\(y_t = Z_t\alpha_t + d_t + \varepsilon_t\) is therefore Gaussian:
\begin{equation*}
\begin{pmatrix} \alpha_t\\ y_t \end{pmatrix} \Bigm| Y_{t-1} \sim
\mathcal N\left(\begin{pmatrix} a_{t|t-1}\\ Z_t a_{t|t-1} + d_t \end{pmatrix},
\begin{pmatrix} P_{t|t-1} & P_{t|t-1} Z_t'\\ Z_t P_{t|t-1} & F_t \end{pmatrix}\right),
\end{equation*}
the conditional covariance coming from
\(\mathrm{Cov}(\alpha_t, Z_t\alpha_t + \varepsilon_t \mid Y_{t-1}) = P_{t|t-1}Z_t'\).
/Updating./ Conditioning on $Y_t$ amounts to conditioning on $Y_{t-1}$ and
then on $y_t$. We therefore apply the lemma to the previous joint
distribution, with $x = \alpha_t$ and $y = y_t$. The distribution of
$\alpha_t$ given $Y_t$ is normal, with mean
\begin{equation*}
a_{t|t-1} + P_{t|t-1}Z_t'F_t^{-1}\left(y_t - Z_t a_{t|t-1} - d_t\right) = a_{t|t-1} + K_t v_t
\end{equation*}
and variance
\(P_{t|t-1} - P_{t|t-1}Z_t'F_t^{-1}Z_t P_{t|t-1} = (I_m - K_t Z_t)P_{t|t-1}\),
which completes the induction.
#+END_proof
#+BEGIN_remarque
The gain determines how much the observation corrects the prediction. In
the scalar case, \(K_t = P_{t|t-1}Z_t/(Z_t^2P_{t|t-1} + H_t)\). If the
measurement is perfect ($H_t = 0$), the gain is $1/Z_t$ and the filtered
state follows exactly from the observation. If the measurement is very
noisy ($H_t \to \infty$), or if the state is already known precisely
($P_{t|t-1} \to 0$), the gain tends to zero and the observation is ignored.
Note also that the variances $P_{t|t-1}$, $F_t$ and $P_{t|t}$ do not depend
on the observations: they could be computed before the data are available.
#+END_remarque
#+BEGIN_property
The filtered variance can also be written in /Joseph form/:
\begin{equation*}
P_{t|t} = (I_m - K_t Z_t) P_{t|t-1} (I_m - K_t Z_t)' + K_t H_t K_t'.
\end{equation*}
#+END_property
#+BEGIN_proof
Expanding the right-hand side gives
\begin{equation*}
P_{t|t-1} - K_t Z_t P_{t|t-1} - P_{t|t-1} Z_t' K_t' + K_t (Z_t P_{t|t-1} Z_t' + H_t) K_t'.
\end{equation*}
The last term equals \(K_t F_t K_t' = P_{t|t-1}Z_t'K_t'\), since
\(K_t F_t = P_{t|t-1}Z_t'\). It cancels the third one, leaving
$(I_m - K_tZ_t)P_{t|t-1}$.
#+END_proof
The two expressions of $P_{t|t}$ are equal in exact arithmetic, but not in
floating-point arithmetic. The Joseph form is a sum of two symmetric
positive semi-definite matrices: it remains so whatever the rounding errors
on the gain, whereas the short form subtracts two nearly equal terms when
$P_{t|t-1}$ is large and may produce a non-symmetric matrix, or even
negative variances. We shall see that this happens at initialisation. The
implementation follows the equations to the letter, with the Joseph form. A
missing observation, coded as =np.nan=, is handled by skipping the updating
step: without an observation, the filtered distribution is the predicted
one.
#+begin_src python :session kalman-en :exports code :results none
def kalman_filter(y, Z, T, R, H, Q, a0, P0):
y = np.asarray(y, dtype=float).reshape(len(y), -1)
n, p = y.shape
m = len(a0)
RQR = R @ Q @ R.T
res = {'a_pred': np.zeros((n, m)), 'P_pred': np.zeros((n, m, m)),
'a_filt': np.zeros((n, m)), 'P_filt': np.zeros((n, m, m)),
'v': np.full((n, p), np.nan), 'F': np.full((n, p, p), np.nan),
'K': np.zeros((n, m, p))}
a, P = np.asarray(a0, dtype=float), np.asarray(P0, dtype=float)
for t in range(n):
# Prediction
a = T @ a
P = T @ P @ T.T + RQR
res['a_pred'][t], res['P_pred'][t] = a, P
# Updating, skipped if the observation is missing
if not np.isnan(y[t]).any():
v = y[t] - Z @ a
F = Z @ P @ Z.T + H
K = np.linalg.solve(F, Z @ P).T # P Z' F^{-1}
a = a + K @ v
IKZ = np.eye(m) - K @ Z
P = IKZ @ P @ IKZ.T + K @ H @ K.T # Joseph form
res['v'][t], res['F'][t], res['K'][t] = v, F, K
res['a_filt'][t], res['P_filt'][t] = a, P
return res
#+end_src
The function returns a dictionary of arrays indexed by date, from $0$ to
$n-1$ in Python for dates $1$ to $n$ in the note.
** The innovations
:PROPERTIES:
:CUSTOM_ID: innovations
:END:
#+BEGIN_property
The innovation $v_t$ is independent of $Y_{t-1}$ and has distribution
$\mathcal N(0, F_t)$. The innovations $v_1, \dots, v_n$ are independent:
they form a Gaussian white noise, heteroskedastic if $F_t$ varies with $t$.
#+END_property
#+BEGIN_proof
By the joint distribution established in the proof of the filter, the
distribution of $y_t$ given $Y_{t-1}$ is
$\mathcal N(Z_t a_{t|t-1} + d_t, F_t)$, hence that of $v_t$ given $Y_{t-1}$
is $\mathcal N(0, F_t)$. This conditional distribution does not depend on
$Y_{t-1}$, since $F_t$ is not random. The innovation $v_t$ is therefore
independent of $Y_{t-1}$, and its distribution is $\mathcal N(0, F_t)$. The
past innovations $v_1, \dots, v_{t-1}$ are functions of $Y_{t-1}$, of which
$v_t$ is independent. Applying this argument for $t = n, n-1, \dots, 2$,
the joint density of $(v_1, \dots, v_n)$ is the product of the marginal
densities.
#+END_proof
The innovation $v_t$ is the part of $y_t$ that was not predictable at date
$t-1$. Since $v_t$ equals $y_t$ minus a function of $Y_{t-1}$, the sequence
of innovations up to date $t$ and that of the observations can be deduced
from each other: they carry the same information. If the model is correctly
specified, the /standardised innovations/ \(e_t = F_t^{-1/2}v_t\) are
independent standard normal. Their sample autocorrelations therefore provide
a simple specification diagnostic, which the application uses extensively.
** Initialisation
:PROPERTIES:
:CUSTOM_ID: initialisation
:END:
It remains to choose $a_0$ and $P_0$. If the model is stationary, with
constant matrices and eigenvalues of $T$ of modulus strictly less than one,
$\alpha_0$ is drawn from the stationary distribution of the state:
\(a_0 = (I_m - T)^{-1}c\) and $P_0$ is the solution of the Lyapunov
equation
\begin{equation*}
P_0 = T P_0 T' + R Q R',\qquad\text{that is}\qquad
\mathrm{vec}(P_0) = (I_{m^2} - T\otimes T)^{-1}\,\mathrm{vec}(RQR'),
\end{equation*}
computed by SciPy's =solve_discrete_lyapunov= function.
If the state has non-stationary components, such as the level of a random
walk, this stationary distribution does not exist. The ignorance about the
initial state is then expressed by a very dispersed distribution, $a_0 = 0$
and $P_0 = \kappa I_m$ with $\kappa$ large: this is the /approximate diffuse
initialisation/. In the local level model, the first step of the filter
gives \(P_{1|0} = \kappa + \sigma_\eta^2\), then
\begin{equation*}
K_1 = \frac{P_{1|0}}{P_{1|0} + \sigma_\varepsilon^2},\qquad
a_{1|1} = K_1 y_1,\qquad
P_{1|1} = \frac{\sigma_\varepsilon^2 P_{1|0}}{P_{1|0} + \sigma_\varepsilon^2}.
\end{equation*}
As $\kappa$ tends to infinity, $a_{1|1}$ tends to $y_1$ and $P_{1|1}$ to
$\sigma_\varepsilon^2$: the first observation pins down the level, with the
precision of the measurement, and the initial distribution is forgotten.
More generally, if $d$ components of the state are diffuse, the first $d$
observations serve to determine them, after which the effect of $\kappa$ is
of order $1/\kappa$. Three precautions are needed.
- The approximation is all the better as $\kappa$ is large relative to the
variance of the data and to the square of their distance from $a_0$. With
data of the order of $1\,000$, as in the application, $\kappa$ must be
well above $10^6$.
- The variance $P_{t|t}$ of the first steps is the difference of two terms
of order $\kappa$, and the computation loses about $\log_{10}\kappa$ of
the sixteen significant digits of floating-point arithmetic. The Joseph
form preserves symmetry and positivity, but an excessive value of
$\kappa$ degrades precision. We use $\kappa = 10^7$ for the simulations
and $\kappa = 10^9$ for the application, after checking.
- The variances $F_t$ of the first $d$ innovations are of order $\kappa$:
these innovations carry no information about the parameters, and their
contribution to the likelihood depends on the arbitrary value of
$\kappa$. They must be discarded, as we shall see in the
[[#concentree][section on estimation]].
#+BEGIN_remarque
The /exact diffuse initialisation/ handles the limit $\kappa \to \infty$
analytically, by separating in $P_{t|t-1}$ a term proportional to $\kappa$
and a finite term (De Jong, /Annals of Statistics/, 1991; Koopman, /Journal
of the American Statistical Association/, 1997; Durbin and Koopman, 2012,
chapter 5). It removes the arbitrariness of the choice of $\kappa$ at the
cost of heavier recursions during the first periods. The =statsmodels=
library implements it; it will serve as an external check in the
application.
#+END_remarque
** An illustration
:PROPERTIES:
:CUSTOM_ID: illustration
:END:
We simulate $n = 100$ observations of a local level model, with
$\sigma_\varepsilon = 1$ and $\sigma_\eta = 0.5$, and apply the filter with
an approximate diffuse initialisation.
#+begin_src python :session kalman-en :exports code :results none
n = 100
sigma_eps, sigma_eta = 1.0, 0.5
mu = np.cumsum(sigma_eta*rng.standard_normal(n))
y_sim = mu + sigma_eps*rng.standard_normal(n)
kappa = 1e7
Z, T, R, H, Q = local_level(sigma_eps**2, sigma_eta**2)
res_sim = kalman_filter(y_sim, Z, T, R, H, Q, np.zeros(1), kappa*np.eye(1))
rmse = lambda x: np.sqrt(np.mean((x - mu)**2))
print(rmse(y_sim), rmse(res_sim['a_filt'][:, 0]))
#+end_src
The filtered state estimates the level with a root mean squared error of
$0.61$, against $1.01$ for the observation itself taken as a naive
estimator. The figure [[fig:niveau-local-filtre][below]] shows that the
filtered state follows the level with a slight lag: using only the past, it
can tell a lasting change in the level from measurement noise only after a
few observations.
#+begin_src python :session kalman-en :exports none :results none
t_sim = np.arange(1, n+1)
a_f, s_f = res_sim['a_filt'][:, 0], np.sqrt(res_sim['P_filt'][:, 0, 0])
fig, ax = plt.subplots(figsize=(6, 4))
ax.plot(t_sim, y_sim, 'k.', markersize=3, label=r'$y_t$')
ax.plot(t_sim, mu, 'k', linewidth=0.8, label=r'$\mu_t$')
ax.plot(t_sim, a_f, 'b', linewidth=1, label=r'$a_{t|t}$')
ax.fill_between(t_sim, a_f-2*s_f, a_f+2*s_f, color='b', alpha=0.15, linewidth=0)
ax.set_xlabel(r'$t$')
ax.legend()
fig.tight_layout()
fig.savefig("kalman-niveau-local-filtre.svg", transparent=True)
#+end_src
#+CAPTION: *Simulated local level model ($\sigma_\varepsilon = 1$, $\sigma_\eta = 0.5$): observations, level and filtered state, with a band of plus or minus two conditional standard deviations.*
#+LABEL: fig:niveau-local-filtre
[[file:kalman-niveau-local-filtre.svg]]
* Convergence of the filter
:PROPERTIES:
:CUSTOM_ID: convergence
:END:
When the system matrices are constant, the variance recursion depends
neither on the observations nor on the date. Substituting the updating step
into the prediction step gives a recursion on the prediction variance
alone.
#+BEGIN_corollary
With constant matrices, the prediction variance satisfies the /Riccati
equation/
\begin{equation*}
P_{t+1|t} = T P_{t|t-1} T' - T P_{t|t-1} Z' F_t^{-1} Z P_{t|t-1} T' + RQR',
\qquad F_t = Z P_{t|t-1} Z' + H,
\end{equation*}
and the predicted state satisfies \(a_{t+1|t} = T a_{t|t-1} + c + T K_t v_t\).
#+END_corollary
#+BEGIN_proof
It suffices to substitute $a_{t|t} = a_{t|t-1} + K_t v_t$ and
\(P_{t|t} = P_{t|t-1} - P_{t|t-1}Z'F_t^{-1}ZP_{t|t-1}\) into the prediction
equations of date $t+1$.
#+END_proof
The sequence of prediction variances converges under very general
conditions. We take the following result for granted (Anderson and Moore,
/Optimal Filtering/, 1979, chapter 4, section 4.4).
#+BEGIN_proposition
Suppose that $H$ is positive definite, that the pair $(T, Z)$ is detectable
and that the pair $(T, RQ^{1/2})$ is stabilisable. Then, for any positive
semi-definite initial variance, $P_{t+1|t}$ converges to the unique
positive semi-definite solution $\bar P$ of the algebraic Riccati equation
\begin{equation*}
\bar P = T\bar PT' - T\bar PZ'(Z\bar PZ' + H)^{-1}Z\bar PT' + RQR',
\end{equation*}
and the gain $K_t$ converges to \(\bar K = \bar PZ'\bar F^{-1}\), with
\(\bar F = Z\bar PZ' + H\).
#+END_proposition
Detectability requires that the unstable components of the state show up in
the observations, stabilisability that the shocks reach all these
components. Once the steady state is reached, the filter is a time-invariant
linear filter, \(a_{t+1|t} = T(I_m - \bar KZ)a_{t|t-1} + T\bar Ky_t\), and
computing the variances becomes unnecessary. The matrix $\bar P$ is given by
SciPy's =solve_discrete_are= function, whose convention transposes that of
the filter: =solve_discrete_are(T.T, Z.T, R @ Q @ R.T, H)=.
** The local level case
:PROPERTIES:
:CUSTOM_ID: niveau-local-riccati
:END:
In the local level model, everything can be computed explicitly. Let
\(q = \sigma_\eta^2/\sigma_\varepsilon^2\) denote the signal-to-noise ratio
and \(p_t = P_{t|t-1}/\sigma_\varepsilon^2\) the prediction variance
relative to that of the measurement noise.
#+BEGIN_property
The sequence $p_t$ satisfies $p_{t+1} = f(p_t)$, with
\(f(p) = p/(1+p) + q\). For any $p_1 \geq 0$, it converges monotonically to
the unique positive fixed point
\begin{equation*}
\bar p = \frac{q + \sqrt{q^2 + 4q}}{2},
\end{equation*}
and the gain \(K_t = p_t/(1+p_t)\) converges to
\(\bar K = \bar p/(1 + \bar p)\).
#+END_property
#+BEGIN_proof
With $Z = T = R = 1$, the Riccati equation reads
\(P_{t+1|t} = P_{t|t-1} - P_{t|t-1}^2/(P_{t|t-1} + \sigma_\varepsilon^2) + \sigma_\eta^2\),
that is, dividing by $\sigma_\varepsilon^2$,
\(p_{t+1} = p_t - p_t^2/(1 + p_t) + q = p_t/(1+p_t) + q\). The fixed points
are the roots of the polynomial $p^2 - qp - q$, of which $\bar p$ is the
only positive one. The function $f$ is increasing on $[0, \infty)$, and the
function \(g(p) = f(p) - p = q - p^2/(1+p)\) is decreasing and vanishes at
$\bar p$. If $p_t < \bar p$, we therefore have
\(p_t < f(p_t) < f(\bar p) = \bar p\): the sequence is increasing and
bounded above by $\bar p$. Symmetrically, it is decreasing and bounded below
by $\bar p$ if $p_t > \bar p$. In both cases it converges, and its limit is
a fixed point of $f$, that is $\bar p$.
#+END_proof
Near the fixed point, the distance to $\bar p$ is multiplied at each period
by \(f'(\bar p) = (1 + \bar p)^{-2}\): convergence is geometric, and all the
faster as the signal-to-noise ratio is high. The figure [[fig:gain][below]]
checks this for three values of $q$, with the approximate diffuse
initialisation. The gain starts at one, since the first observation pins
down the level, and reaches its steady-state value, $0.200$, $0.390$ and
$0.618$ for $q$ equal to $0.05$, $0.25$ and $1$, within $10^{-4}$ after
$19$, $10$ and $6$ observations respectively. For $q = 1$, $\bar p$ is the
golden ratio and $\bar K$ its inverse.
#+begin_src python :session kalman-en :exports code :results none
def local_level_fixed_point(q):
p_bar = (q + np.sqrt(q**2 + 4*q))/2
return p_bar, p_bar/(1 + p_bar)
for q in (0.05, 0.25, 1):
r = kalman_filter(np.zeros(30), *local_level(1.0, q), np.zeros(1), kappa*np.eye(1))
K_bar = local_level_fixed_point(q)[1]
print(q, K_bar, 1 + np.argmax(np.abs(r['K'][:, 0, 0] - K_bar) < 1e-4))
#+end_src
#+begin_src python :session kalman-en :exports none :results none
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
dates = np.arange(1, 31)
for q, c in ((0.05, 'b'), (0.25, 'g'), (1, 'r')):
r = kalman_filter(np.zeros(30), *local_level(1.0, q), np.zeros(1), kappa*np.eye(1))
p_bar, K_bar = local_level_fixed_point(q)
axes[0].plot(dates, r['P_pred'][:, 0, 0], c+'o-', markersize=3, linewidth=1, label=fr'$q = {q}$')
axes[0].axhline(p_bar, color=c, linestyle='--', linewidth=0.8)
axes[1].plot(dates, r['K'][:, 0, 0], c+'o-', markersize=3, linewidth=1, label=fr'$q = {q}$')
axes[1].axhline(K_bar, color=c, linestyle='--', linewidth=0.8)
axes[0].set_yscale('log')
axes[0].set_ylim(0.1, 10)
axes[0].set_xlabel(r'$t$')
axes[0].set_ylabel(r'$P_{t|t-1}/\sigma_\varepsilon^2$')
axes[0].legend()
axes[1].set_xlabel(r'$t$')
axes[1].set_ylabel(r'$K_t$')
axes[1].legend()
fig.tight_layout()
fig.savefig("kalman-gain.svg", transparent=True)
#+end_src
#+CAPTION: *Convergence of the prediction variance (left, logarithmic scale) and of the gain (right) in the local level model, for three values of the signal-to-noise ratio. Dotted lines show the steady-state values $\bar p$ and $\bar K$.*
#+LABEL: fig:gain
[[file:kalman-gain.svg]]
** Reduced form and exponential smoothing
:PROPERTIES:
:CUSTOM_ID: forme-reduite
:END:
The steady state of the filter has a remarkable interpretation in the local
level model, through the reduced form of this model.
#+BEGIN_property
In the local level model, the first difference
\(\Delta y_t = \eta_t + \varepsilon_t - \varepsilon_{t-1}\) is an invertible
MA(1) process, \(\Delta y_t = \xi_t + \theta\xi_{t-1}\), where $\xi_t$ is a
white noise with variance \(\sigma_\xi^2 = -\sigma_\varepsilon^2/\theta\)
and
\begin{equation*}
\theta = \frac{\sqrt{q^2 + 4q} - q - 2}{2} \in (-1, 0).
\end{equation*}
The process $y_t$ is therefore an ARIMA(0,1,1).
#+END_property
#+BEGIN_proof
The autocovariances of $\Delta y_t$ are
\(\gamma_0 = \sigma_\eta^2 + 2\sigma_\varepsilon^2\),
\(\gamma_1 = -\sigma_\varepsilon^2\) and $\gamma_k = 0$ for $k \geq 2$:
they are those of an MA(1) process. Those of $\xi_t + \theta\xi_{t-1}$ are
\(\gamma_0 = (1 + \theta^2)\sigma_\xi^2\) and
\(\gamma_1 = \theta\sigma_\xi^2\). Equating the first-order
autocorrelations, \(\theta/(1 + \theta^2) = -1/(q + 2)\), that is
\(\theta^2 + (q + 2)\theta + 1 = 0\). The discriminant, $q^2 + 4q$, is
positive, both roots are negative and their product is one: only one lies
between $-1$ and $0$, the invertible root. Finally,
\(\sigma_\xi^2 = \gamma_1/\theta = -\sigma_\varepsilon^2/\theta\).
#+END_proof
#+BEGIN_property
The steady-state gain is $\bar K = 1 + \theta$, and the steady-state
innovation variance, \(\bar F = \sigma_\varepsilon^2(1 + \bar p)\), equals
$\sigma_\xi^2$. In the steady state, the prediction follows the
/exponential smoothing/ recursion,
\begin{equation*}
a_{t+1|t} = (1 - \bar K)\,a_{t|t-1} + \bar K y_t,
\end{equation*}
and the innovations of the filter are those of the reduced form:
\(\Delta y_t = v_t + \theta v_{t-1}\).
#+END_property
#+BEGIN_proof
Let $r = \sqrt{q^2 + 4q}$, so that $\bar p = (q + r)/2$ and
$1 + \theta = (r - q)/2$. Multiplying numerator and denominator by
$q + 2 + r$, and since $r^2 = q^2 + 4q$,
\begin{equation*}
-\frac{1 + \theta}{\theta} = \frac{r - q}{q + 2 - r}
= \frac{(r - q)(q + 2 + r)}{(q + 2)^2 - r^2}
= \frac{2q + 2r}{4} = \bar p.
\end{equation*}
Hence $1 + \bar p = -1/\theta$, then
\(\bar K = \bar p/(1 + \bar p) = 1 + \theta\) and
\(\bar F = \sigma_\varepsilon^2(1 + \bar p) = -\sigma_\varepsilon^2/\theta = \sigma_\xi^2\).
In the steady state, with $Z = T = 1$, the predicted state is
\(a_{t+1|t} = a_{t|t} = a_{t|t-1} + \bar K(y_t - a_{t|t-1})\), which is the
stated recursion. Finally, $y_t = a_{t|t-1} + v_t$ and
\(a_{t|t-1} - a_{t-1|t-2} = \bar K v_{t-1}\) give
\begin{equation*}
\Delta y_t = \bar K v_{t-1} + v_t - v_{t-1} = v_t - (1 - \bar K)v_{t-1} = v_t + \theta v_{t-1}.
\end{equation*}
#+END_proof
The Kalman filter in steady state is therefore the exponential smoothing
forecasting method, and exponential smoothing is the optimal forecast of an
ARIMA(0,1,1) process (Muth, /Journal of the American Statistical
Association/, 1960). The filter adds two things: the smoothing constant
follows from the parameters of the model, and the gain varies during the
transition phase to account for the initial uncertainty. We check this on
the simulated series, with $q = 0.25$, by comparing the prediction of the
filter with exponential smoothing with constant $\bar K$ initialised at the
first observation:
#+begin_src python :session kalman-en :exports code :results none
q = sigma_eta**2/sigma_eps**2
p_bar, K_bar = local_level_fixed_point(q)
theta = (np.sqrt(q**2 + 4*q) - q - 2)/2
print(K_bar, 1 + theta)
exp_smoothing = np.zeros(n)
exp_smoothing[1] = y_sim[0]
for t in range(1, n-1):
exp_smoothing[t+1] = (1 - K_bar)*exp_smoothing[t] + K_bar*y_sim[t]
gap = np.abs(exp_smoothing[1:] - res_sim['a_pred'][1:, 0])
print(gap[[3, 8, 18]])
#+end_src
The gap between the two predictions, of the order of $2\times 10^{-2}$ at
date $5$, is only $3\times 10^{-3}$ at date $10$ and $2\times 10^{-5}$ at
date $20$: it decreases geometrically, at rate
$1 - \bar K = -\theta \approx 0.61$ per period. The right panel of the
figure [[fig:lissage-exponentiel][below]] also checks that the standardised
innovations $e_t$ are not autocorrelated, as they should not be when the
model is correctly specified. The sample autocorrelation function will be
used again in the application.
#+begin_src python :session kalman-en :exports code :results none
def acf(x, kmax):
x = x - x.mean()
return np.array([np.sum(x[k:]*x[:-k]) for k in range(1, kmax+1)])/np.sum(x**2)
e_sim = res_sim['v'][1:, 0]/np.sqrt(res_sim['F'][1:, 0, 0])
print(acf(e_sim, 10))
#+end_src
#+begin_src python :session kalman-en :exports none :results none
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
axes[0].semilogy(np.arange(2, 41), gap[:39], 'b', linewidth=1)
axes[0].set_xlabel(r'$t$')
axes[0].set_ylabel('gap between the two predictions')
k = np.arange(1, 11)
band = 1.96/np.sqrt(len(e_sim))
axes[1].bar(k, acf(e_sim, 10), color='b', width=0.5)
for s in (-1, 1):
axes[1].axhline(s*band, color='k', linestyle='--', linewidth=0.8)
axes[1].axhline(0, color='k', linewidth=0.5)
axes[1].set_xticks(k)
axes[1].set_xlabel('lag')
axes[1].set_ylabel(r'autocorrelation of $e_t$')
fig.tight_layout()
fig.savefig("kalman-lissage-exponentiel.svg", transparent=True)
#+end_src
#+CAPTION: *Left, absolute gap between the filter prediction $a_{t|t-1}$ and exponential smoothing with constant $\bar K$ (logarithmic scale). Right, autocorrelations of the standardised innovations, with the band $\pm 1.96/\sqrt{n}$.*
#+LABEL: fig:lissage-exponentiel
[[file:kalman-lissage-exponentiel.svg]]
* The smoother
:PROPERTIES:
:CUSTOM_ID: lisseur
:END:
The filter estimates the state at date $t$ with the information available
at that date. When the whole sample is observed, it is natural to also use
the later observations: this is the purpose of /smoothing/, which computes
$a_{t|n}$ and $P_{t|n}$ for $t = 1, \dots, n$. The filter is one-sided, the
smoother two-sided. The starting point is a Markov property: once the state
at date $t+1$ is known, the later observations bring nothing more about the
state at date $t$.
#+BEGIN_lemma
For $t < n$, the distribution of $\alpha_t$ given $\alpha_{t+1}$ and $Y_n$
is equal to the distribution of $\alpha_t$ given $\alpha_{t+1}$ and $Y_t$.
#+END_lemma
#+BEGIN_proof
Let $Y_{t+1:n} = (y_{t+1}, \dots, y_n)$ denote the observations after date
$t$. By the model equations, they can be written as a function of
$\alpha_{t+1}$ and of the disturbances
\(\varepsilon_{t+1}, \dots, \varepsilon_n, \eta_{t+2}, \dots, \eta_n\),
gathered in a vector $\omega$. The triple formed by $\alpha_t$,
$\alpha_{t+1}$ and $Y_t$ is a function of $\alpha_0$, of
\(\varepsilon_1, \dots, \varepsilon_t\) and of
\(\eta_1, \dots, \eta_{t+1}\), of which $\omega$ is independent. Given
$\alpha_t$, $\alpha_{t+1}$ and $Y_t$, the distribution of $Y_{t+1:n}$ is
therefore that of a function of $\alpha_{t+1}$ and $\omega$: it depends
only on $\alpha_{t+1}$. By Bayes' formula, denoting densities by $p$,
\begin{equation*}
p(\alpha_t \mid \alpha_{t+1}, Y_n)
\propto p(Y_{t+1:n} \mid \alpha_t, \alpha_{t+1}, Y_t)\, p(\alpha_t \mid \alpha_{t+1}, Y_t)
= p(Y_{t+1:n} \mid \alpha_{t+1})\, p(\alpha_t \mid \alpha_{t+1}, Y_t),
\end{equation*}
where the symbol $\propto$ denotes proportionality as functions of
$\alpha_t$. The first factor does not depend on $\alpha_t$: the density of
$\alpha_t$ given $\alpha_{t+1}$ and $Y_n$ is proportional, hence equal, to
that of $\alpha_t$ given $\alpha_{t+1}$ and $Y_t$.
#+END_proof
#+BEGIN_proposition
Starting from $a_{n|n}$ and $P_{n|n}$, computed by the filter, the smoothed
moments are obtained by the backward recursion, for
$t = n-1, n-2, \dots, 1$,
\begin{equation*}
\begin{aligned}
J_t &= P_{t|t} T_{t+1}' P_{t+1|t}^{-1},\\
a_{t|n} &= a_{t|t} + J_t\left(a_{t+1|n} - a_{t+1|t}\right),\\
P_{t|n} &= P_{t|t} + J_t\left(P_{t+1|n} - P_{t+1|t}\right)J_t',
\end{aligned}
\end{equation*}
assuming $P_{t+1|t}$ is invertible. The distribution of $\alpha_t$ given
$Y_n$ is normal, with mean $a_{t|n}$ and variance $P_{t|n}$.
#+END_proposition
This recursion is due to Rauch, Tung and Striebel (/AIAA Journal/, 1965).
It requires only one backward pass over the output of the filter.
#+BEGIN_proof
/Joint distribution./ Given $Y_t$, the state $\alpha_t$ has distribution
$\mathcal N(a_{t|t}, P_{t|t})$ and
\(\alpha_{t+1} = T_{t+1}\alpha_t + c_{t+1} + R_{t+1}\eta_{t+1}\), where
$\eta_{t+1}$ is independent of $\alpha_t$ and $Y_t$. Given $Y_t$, the pair
formed by $\alpha_t$ and $\alpha_{t+1}$ is therefore Gaussian:
\begin{equation*}
\begin{pmatrix} \alpha_t\\ \alpha_{t+1} \end{pmatrix} \Bigm| Y_t \sim
\mathcal N\left(\begin{pmatrix} a_{t|t}\\ a_{t+1|t} \end{pmatrix},
\begin{pmatrix} P_{t|t} & P_{t|t}T_{t+1}'\\ T_{t+1}P_{t|t} & P_{t+1|t} \end{pmatrix}\right).
\end{equation*}
/Conditioning on the next state./ The lemma on the normal distribution
gives
\begin{equation*}
\alpha_t \mid \alpha_{t+1}, Y_t \sim
\mathcal N\left(a_{t|t} + J_t(\alpha_{t+1} - a_{t+1|t}),\; P_{t|t} - J_tP_{t+1|t}J_t'\right),
\end{equation*}
and, by the previous lemma, this is also the distribution of $\alpha_t$
given $\alpha_{t+1}$ and $Y_n$.
/Smoothed mean./ By the law of iterated expectations,
\begin{equation*}
a_{t|n} = \mathbb E\left[\mathbb E[\alpha_t \mid \alpha_{t+1}, Y_n] \mid Y_n\right]
= a_{t|t} + J_t\left(a_{t+1|n} - a_{t+1|t}\right).
\end{equation*}
/Smoothed variance./ By the law of total variance,
\begin{equation*}
\begin{aligned}
P_{t|n} &= \mathbb E\left[\mathbb V[\alpha_t \mid \alpha_{t+1}, Y_n] \mid Y_n\right]
+ \mathbb V\left[\mathbb E[\alpha_t \mid \alpha_{t+1}, Y_n] \mid Y_n\right]\\
&= P_{t|t} - J_tP_{t+1|t}J_t' + J_tP_{t+1|n}J_t'.
\end{aligned}
\end{equation*}
Finally, the distribution of $\alpha_t$ given $Y_n$ is normal because
$\alpha_t$ and $Y_n$ form a Gaussian vector.
#+END_proof
#+BEGIN_remarque
The same law of total variance, applied by conditioning on $Y_t$ rather
than on $\alpha_{t+1}$, gives
\(P_{t|t} = P_{t|n} + \mathbb V\left[a_{t|n} \mid Y_t\right]\): the smoothed
variance is smaller than the filtered variance, in the sense of positive
semi-definite matrices. When $P_{t+1|t}$ is singular, which happens when the
state contains components observed without error, the inverse can be
replaced by a generalised inverse, or one can use the recursions of De Jong
(/Journal of the American Statistical Association/, 1989), which avoid any
inversion.
#+END_remarque
#+begin_src python :session kalman-en :exports code :results none
def kalman_smoother(res, T):
a_smooth, P_smooth = res['a_filt'].copy(), res['P_filt'].copy()
for t in range(len(a_smooth) - 2, -1, -1):
J = np.linalg.solve(res['P_pred'][t+1], T @ res['P_filt'][t]).T # P_{t|t} T' P_{t+1|t}^{-1}
a_smooth[t] = res['a_filt'][t] + J @ (a_smooth[t+1] - res['a_pred'][t+1])
P_smooth[t] = res['P_filt'][t] + J @ (P_smooth[t+1] - res['P_pred'][t+1]) @ J.T
return a_smooth, P_smooth
a_smooth, P_smooth = kalman_smoother(res_sim, T)
print(rmse(a_smooth[:, 0]))
print(res_sim['P_filt'][50, 0, 0], P_smooth[50, 0, 0])
#+end_src
On the simulated series, the smoother reduces the root mean squared error
from $0.61$ to $0.48$. In the middle of the sample, the smoothed variance
is $0.243$ against $0.390$ for the filtered variance: the confidence band
is about a fifth narrower, except at the ends of the sample, where the
smoother does not have observations on both sides and joins the filter at
date $n$. The figure [[fig:niveau-local-lisseur][below]] also shows that
the smoother corrects the lag of the filter: it anticipates changes in the
level, since it sees the following observations.
#+begin_src python :session kalman-en :exports none :results none
s_s = np.sqrt(P_smooth[:, 0, 0])
fig, ax = plt.subplots(figsize=(6, 4))
ax.plot(t_sim, mu, 'k', linewidth=0.8, label=r'$\mu_t$')
ax.plot(t_sim, a_f, 'b', linewidth=1, label=r'$a_{t|t}$')
ax.fill_between(t_sim, a_f-2*s_f, a_f+2*s_f, color='b', alpha=0.12, linewidth=0)
ax.plot(t_sim, a_smooth[:, 0], 'r', linewidth=1, label=r'$a_{t|n}$')
ax.fill_between(t_sim, a_smooth[:, 0]-2*s_s, a_smooth[:, 0]+2*s_s, color='r', alpha=0.2, linewidth=0)
ax.set_xlabel(r'$t$')
ax.legend()
fig.tight_layout()
fig.savefig("kalman-niveau-local-lisseur.svg", transparent=True)
#+end_src
#+CAPTION: *Simulated level, filtered state and smoothed state, with their bands of plus or minus two conditional standard deviations.*
#+LABEL: fig:niveau-local-lisseur
[[file:kalman-niveau-local-lisseur.svg]]
* Maximum likelihood estimation
:PROPERTIES:
:CUSTOM_ID: estimation
:END:
The system matrices generally depend on unknown parameters, gathered in a
vector $\psi$: the variances of the local level model, the coefficients and
the variance of an ARMA process. The Kalman filter delivers the likelihood
of these parameters at almost no extra cost.
** Prediction error decomposition
:PROPERTIES:
:CUSTOM_ID: decomposition
:END:
#+BEGIN_proposition
The log-likelihood of the observations $y_1, \dots, y_n$ is
\begin{equation*}
\log L(\psi) = -\frac{np}{2}\log 2\pi - \frac12\sum_{t=1}^n \log\det F_t - \frac12\sum_{t=1}^n v_t'F_t^{-1}v_t,
\end{equation*}
where the innovations $v_t$ and their variances $F_t$ are computed by the
filter with the value $\psi$ of the parameters.
#+END_proposition
#+BEGIN_proof
The joint density factorises as a product of conditional densities,
\begin{equation*}
f(y_1, \dots, y_n) = \prod_{t=1}^n f(y_t \mid Y_{t-1}),
\end{equation*}
with the convention $f(y_1 \mid Y_0) = f(y_1)$. By the proof of the filter,
the distribution of $y_t$ given $Y_{t-1}$ is normal, with mean
$Z_t a_{t|t-1} + d_t$ and variance $F_t$, and its density at $y_t$ is
\begin{equation*}
f(y_t \mid Y_{t-1}) = (2\pi)^{-p/2}(\det F_t)^{-1/2}\exp\left(-\frac12 v_t'F_t^{-1}v_t\right).
\end{equation*}
It remains to take the logarithm of the product.
#+END_proof
Computing the likelihood directly would require inverting the variance
matrix of the sample, of size $np \times np$, at a cost of order $n^3p^3$.
The filter implicitly computes a triangular factorisation of it, at a cost
proportional to $n$. For an ARMA process initialised with its stationary
distribution, this is the /exact/ likelihood, which accounts for the
distribution of the first observations, and not the likelihood conditional
on the first observations that least squares maximise (see the note on the
[[https://stephane-adjemian.fr/en/posts/ols-bias-in-an-ar1-process/][bias of the least squares estimator of an AR(1)]]
and the [[ex-h-nul][exercise]] on the autoregressive case). The code allows
the first $d$ innovations to be discarded, for the reason given below:
#+begin_src python :session kalman-en :exports code :results none
def loglik(res, d=0):
v, F = res['v'][d:, 0], res['F'][d:, 0, 0]
ok = ~np.isnan(v)
return -0.5*np.sum(np.log(2*np.pi*F[ok]) + v[ok]**2/F[ok])
#+end_src
** Concentrated likelihood
:PROPERTIES:
:CUSTOM_ID: concentree
:END:
In unobserved components models, the parameters are often variances, and
one of them can be eliminated analytically. We assume $p = 1$ and that the
parameters split into a scale factor $\sigma^2$ and a vector $\psi$, with
$H = \sigma^2H_*(\psi)$, $Q = \sigma^2Q_*(\psi)$ and
$P_0 = \sigma^2P_{0*}(\psi)$. We denote by $v_t^*$ and $F_t^*$ the
quantities computed by the filter with $\sigma^2 = 1$, and by $N$ the
number of innovations included in the likelihood.
#+BEGIN_property
The innovations do not depend on $\sigma^2$, $v_t = v_t^*$, and
$F_t = \sigma^2F_t^*$. The log-likelihood is maximised in $\sigma^2$ at
\begin{equation*}
\hat\sigma^2(\psi) = \frac1N\sum_t \frac{(v_t^*)^2}{F_t^*},
\end{equation*}
and the /concentrated log-likelihood/, obtained by replacing $\sigma^2$
with $\hat\sigma^2(\psi)$, is
\begin{equation*}
\log L_c(\psi) = -\frac N2\left(\log 2\pi + 1\right) - \frac12\sum_t\log F_t^* - \frac N2\log\hat\sigma^2(\psi).
\end{equation*}
#+END_property
#+BEGIN_proof
By induction, if $P_{t-1|t-1} = \sigma^2P_{t-1|t-1}^*$, the filter
equations give $P_{t|t-1} = \sigma^2P_{t|t-1}^*$, $F_t = \sigma^2F_t^*$ and
\(K_t = P_{t|t-1}Z'F_t^{-1} = K_t^*\): the gain, and therefore the predicted
states and the innovations, do not depend on $\sigma^2$, while
$P_{t|t} = \sigma^2P_{t|t}^*$. The log-likelihood then reads
\begin{equation*}
\log L = -\frac N2\log 2\pi - \frac N2\log\sigma^2 - \frac12\sum_t\log F_t^* - \frac{1}{2\sigma^2}\sum_t\frac{(v_t^*)^2}{F_t^*}.
\end{equation*}
As a function of $1/\sigma^2$, it is concave, and its derivative vanishes
at $\hat\sigma^2(\psi)$. Substituting, the last term equals $-N/2$.
#+END_proof
With the approximate diffuse initialisation, $P_0 = \kappa I_m$ is not
proportional to $\sigma^2$, but the discrepancy is of order $1/\kappa$.
More importantly, the first $d$ innovations, whose variance is of order
$\kappa$, contribute to the likelihood through terms $-\frac12\log F_t$
that depend on the arbitrary value of $\kappa$: they are discarded, and the
sums run over dates $t = d+1, \dots, n$, with $N = n - d$. This is the
/diffuse likelihood/ (Harvey, 1989, section 3.4.3). It differs from the
likelihood computed with the exact diffuse initialisation by a constant
that does not depend on the parameters, provided the diffuse part of the
model, in $Z$ and $T$, does not depend on them either, as in the trend
models of this note (Francke, Koopman and De Vos, /Journal of Time Series
Analysis/, 2010): both then give the same estimates, as we shall check in
the application.
#+begin_src python :session kalman-en :exports code :results none
def concentrated_loglik(res, d=0):
v, F = res['v'][d:, 0], res['F'][d:, 0, 0]
ok = ~np.isnan(v)
N = ok.sum()
s2 = np.mean(v[ok]**2/F[ok])
return -0.5*N*(np.log(2*np.pi) + 1) - 0.5*np.sum(np.log(F[ok])) - 0.5*N*np.log(s2), s2
#+end_src
** Precision of the estimators and tests
:PROPERTIES:
:CUSTOM_ID: erreurs-standard
:END:
Under regularity conditions, which require in particular that the true
value of the parameters lies in the interior of the parameter space, the
maximum likelihood estimator is asymptotically normal, and its variance
matrix is estimated by the inverse of minus the Hessian of the
log-likelihood at the maximum. The Hessian is computed by central finite
differences:
\begin{equation*}
\begin{aligned}
\frac{\partial^2 \log L}{\partial\psi_i\partial\psi_j} \approx \frac{1}{4h^2}\Big[
&\log L(\psi + he_i + he_j) - \log L(\psi + he_i - he_j)\\
&- \log L(\psi - he_i + he_j) + \log L(\psi - he_i - he_j)\Big],
\end{aligned}
\end{equation*}
where $e_i$ is the \(i\)-th vector of the canonical basis.
#+begin_src python :session kalman-en :exports code :results none
def hessian(f, x, h=1e-3):
x = np.atleast_1d(np.asarray(x, dtype=float))
E = h*np.eye(len(x))
return np.array([[(f(x+ei+ej) - f(x+ei-ej) - f(x-ei+ej) + f(x-ei-ej))/(4*h**2)
for ej in E] for ei in E])
#+end_src
The variance parameters are estimated in logarithms, which removes the
positivity constraint. Confidence intervals are built for the logarithm and
then transformed, which makes them asymmetric. The curvature of the
concentrated log-likelihood directly gives the precision of $\hat\psi$: the
inverse of minus its Hessian equals the corresponding block of the inverse
of minus the Hessian of the full
log-likelihood (Patefield, /Biometrika/, 1985). Finally, two
nested models
are compared by the likelihood ratio test,
\(\mathrm{LR} = 2(\log L_1 - \log L_0)\), whose asymptotic distribution is a
$\chi^2$ with as many degrees of freedom as restrictions, provided the
restricted model does not put a parameter on the boundary of the parameter
space, such as a zero variance.
* Forecasting
:PROPERTIES:
:CUSTOM_ID: prevision
:END:
#+BEGIN_proposition
For $h \geq 1$, the distribution of $\alpha_{n+h}$ given $Y_n$ is
$\mathcal N(a_{n+h|n}, P_{n+h|n})$, where
\begin{equation*}
a_{n+h|n} = T a_{n+h-1|n} + c,\qquad P_{n+h|n} = T P_{n+h-1|n} T' + RQR',
\end{equation*}
starting from $a_{n|n}$ and $P_{n|n}$. The forecast of the observation is
\(\hat y_{n+h|n} = Z a_{n+h|n} + d\), with variance
\(F_{n+h|n} = Z P_{n+h|n} Z' + H\), and, in the scalar case, a forecast
interval with level $1 - \delta$ is
\(\hat y_{n+h|n} \pm z_{1-\delta/2}\sqrt{F_{n+h|n}}\), where
$z_{1-\delta/2}$ is a quantile of the standard normal distribution.
#+END_proposition
#+BEGIN_proof
No observation is available after date $n$: the distribution of
$\alpha_{n+h}$ given $Y_n$ is obtained by applying $h$ times the prediction
step of the filter, without updating, and the distribution of $y_{n+h}$
follows from the measurement equation.
#+END_proof
In other words, forecasting amounts to filtering a series extended with
missing observations at dates $n+1, \dots, n+h$. With constant matrices, we
get \(a_{n+h|n} = T^h a_{n|n} + \sum_{j=0}^{h-1}T^jc\) and
\begin{equation*}
P_{n+h|n} = T^h P_{n|n} (T')^h + \sum_{j=0}^{h-1} T^j RQR' (T')^j.
\end{equation*}
#+BEGIN_exemple
In the smooth trend model,
\(T^h = \begin{pmatrix} 1 & h\\ 0 & 1 \end{pmatrix}\) and \(ZT^jR = j\).
The forecast extends the filtered trend linearly,
\(\hat y_{n+h|n} = a_{\mu, n|n} + h\,a_{\beta, n|n}\), where
$a_{\mu, n|n}$ and $a_{\beta, n|n}$ are the components of $a_{n|n}$. Since
\(\mu_{n+h} = \mu_n + h\beta_n + \sum_{j=1}^{h-1} j\,\zeta_{n+h-j}\), its
variance is
\begin{equation*}
F_{n+h|n} = \sigma_\varepsilon^2 + \begin{pmatrix} 1 & h \end{pmatrix} P_{n|n} \begin{pmatrix} 1\\ h \end{pmatrix}
+ \sigma_\zeta^2\sum_{j=1}^{h-1} j^2.
\end{equation*}
The last term, equal to $\sigma_\zeta^2(h-1)h(2h-1)/6$, grows like
$\sigma_\zeta^2h^3/3$: the forecast intervals of a trend integrated of
order two widen like $h^{3/2}$.
#+END_exemple
#+begin_src python :session kalman-en :exports code :results none
def forecast(a, P, Z, T, R, H, Q, h):
RQR = R @ Q @ R.T
y_fc, F_fc = np.zeros(h), np.zeros(h)
for j in range(h):
a = T @ a
P = T @ P @ T.T + RQR
y_fc[j], F_fc[j] = (Z @ a)[0], (Z @ P @ Z.T + H)[0, 0]
return y_fc, F_fc
#+end_src
* The Hodrick-Prescott filter as a Kalman smoother
:PROPERTIES:
:CUSTOM_ID: application
:END:
The Hodrick and Prescott filter (/Journal of Money, Credit and Banking/,
1997, from a 1980 working paper) is the most widely used tool to separate
the trend and the cycle of a macroeconomic series. We show that it
coincides with the Kalman smoother of a smooth trend model, which makes it
possible to estimate its parameter by maximum likelihood, and then apply
the result to French GDP per capita.
** The Hodrick-Prescott filter
:PROPERTIES:
:CUSTOM_ID: filtre-hp
:END:
#+BEGIN_definition
For a series $y_1, \dots, y_n$ and a parameter $\lambda > 0$, the
Hodrick-Prescott trend is the sequence $\hat\mu_1, \dots, \hat\mu_n$ that
minimises
\begin{equation*}
S(\mu) = \sum_{t=1}^n (y_t - \mu_t)^2 + \lambda\sum_{t=3}^n \left(\Delta^2\mu_t\right)^2,
\qquad \Delta^2\mu_t = \mu_t - 2\mu_{t-1} + \mu_{t-2}.
\end{equation*}
The cycle is the deviation $y_t - \hat\mu_t$.
#+END_definition
The criterion trades off the fit to the data against the smoothness of the
trend, measured by its second differences. For $\lambda = 0$, the trend is
the series itself; as $\lambda$ tends to infinity, it tends to a straight
line (see the [[ex-limites-hp][exercise]]).
#+BEGIN_property
The Hodrick-Prescott trend is the unique solution of the linear system
\((I_n + \lambda D'D)\hat\mu = y\), where $D$ is the $(n-2)\times n$ matrix
whose row $t$ contains the coefficients $1$, $-2$ and $1$ in columns $t$,
$t+1$ and $t+2$, and zeros elsewhere.
#+END_property
#+BEGIN_proof
The criterion reads \(S(\mu) = (y - \mu)'(y - \mu) + \lambda\mu'D'D\mu\). It
is a quadratic function with Hessian $2(I_n + \lambda D'D)$, positive
definite since $D'D$ is positive semi-definite: the criterion is strictly
convex, and its unique minimum sets the gradient
\(-2(y - \mu) + 2\lambda D'D\mu\) to zero.
#+END_proof
The matrix $I_n + \lambda D'D$ is pentadiagonal: the system is solved with a
sparse solver, in a number of operations proportional to $n$.
#+begin_src python :session kalman-en :exports code :results none
def hp_filter(y, lam):
n = len(y)
D = sparse.diags([1.0, -2.0, 1.0], [0, 1, 2], shape=(n-2, n))
return spsolve((sparse.eye(n) + lam*D.T @ D).tocsc(), y)
#+end_src
#+BEGIN_remarque
Applied to an infinite series, the filter is a symmetric moving average
whose gain for the trend is \(1/(1 + 16\lambda\sin^4(\omega/2))\) at
frequency $\omega$ (King and Rebelo, /Journal of Economic Dynamics and
Control/, 1993). This gain equals one half for cycles of period
$2\pi/\omega$ such that \(16\lambda\sin^4(\omega/2) = 1\): shorter
fluctuations are attributed to the cycle, longer ones to the trend. The
usual value $\lambda = 1\,600$ for quarterly data puts this period at about
forty quarters, that is about ten years. For annual data, $\lambda = 100$,
used by Backus and Kehoe (/American Economic Review/, 1992), puts it at
about twenty years, and $\lambda = 6.25$, recommended by Ravn and Uhlig
(/Review of Economics and Statistics/, 2002) to match the usual quarterly
filter, at about ten years.
#+END_remarque
** The smooth trend model
:PROPERTIES:
:CUSTOM_ID: tendance-lisse
:END:
#+BEGIN_theorem
Consider the smooth trend model
\begin{equation*}
y_t = \mu_t + \varepsilon_t,\qquad
\mu_t = \mu_{t-1} + \beta_{t-1},\qquad
\beta_t = \beta_{t-1} + \zeta_t,
\end{equation*}
with $\varepsilon_t \sim \mathcal N(0, \sigma_\varepsilon^2)$,
$\zeta_t \sim \mathcal N(0, \sigma_\zeta^2)$ and a diffuse prior on the
initial level and slope. The smoothed trend $\mathbb E[\mu_t \mid Y_n]$ is
the Hodrick-Prescott trend with parameter
\(\lambda = \sigma_\varepsilon^2/\sigma_\zeta^2\).
#+END_theorem
#+BEGIN_proof
The model equations give, for $t \geq 3$,
\begin{equation*}
\Delta^2\mu_t = (\mu_t - \mu_{t-1}) - (\mu_{t-1} - \mu_{t-2}) = \beta_{t-1} - \beta_{t-2} = \zeta_{t-1}.
\end{equation*}
The trend $(\mu_1, \dots, \mu_n)$ is therefore the image of
$(\mu_1, \beta_1, \zeta_2, \dots, \zeta_{n-1})$ under the linear map
defined by $\mu_2 = \mu_1 + \beta_1$ and
\(\mu_t = 2\mu_{t-1} - \mu_{t-2} + \zeta_{t-1}\) for $t \geq 3$. This map is
bijective, with a unit triangular matrix, and its Jacobian is one. With a
constant prior density for $(\mu_1, \beta_1)$, the limit of a normal
distribution whose variance tends to infinity, and independent $\zeta_t$
with distribution $\mathcal N(0, \sigma_\zeta^2)$, the prior density of the
trend is
\begin{equation*}
p(\mu_1, \dots, \mu_n) \propto \exp\left(-\frac{1}{2\sigma_\zeta^2}\sum_{t=3}^n\left(\Delta^2\mu_t\right)^2\right).
\end{equation*}
Given the trend, the observations are independent, with distribution
$\mathcal N(\mu_t, \sigma_\varepsilon^2)$. By Bayes' formula, the posterior
density of the trend is proportional to the product of the likelihood and
the prior density:
\begin{equation*}
p(\mu_1, \dots, \mu_n \mid Y_n) \propto
\exp\left(-\frac{1}{2\sigma_\varepsilon^2}\left[\sum_{t=1}^n(y_t - \mu_t)^2 + \lambda\sum_{t=3}^n\left(\Delta^2\mu_t\right)^2\right]\right).
\end{equation*}
This is the density of a normal distribution. Its mean equals its mode,
which minimises the bracket, that is the Hodrick-Prescott trend. Now the
Kalman smoother, with the diffuse initialisation, computes precisely the
posterior mean $\mathbb E[\mu_t \mid Y_n]$. The slope $\beta_n$, which does
not enter the observations, plays no role.
#+END_proof
This result extends the Bayesian interpretation of smoothing splines
(Wahba, /Journal of the Royal Statistical Society B/, 1978). Harvey and
Jaeger (/Journal of Applied Econometrics/, 1993) drew its consequences for
business cycle analysis: the Hodrick-Prescott trend depends only on the
ratio of the two variances, and the filter is optimal if the series is
generated by the smooth trend model with
\(\sigma_\varepsilon^2/\sigma_\zeta^2 = \lambda\). In return, the model
provides what the filter does not: confidence bands around the trend, a
one-sided trend, computed by the filter, that uses only the past, forecasts
and above all a likelihood, which makes it possible to estimate $\lambda$
instead of fixing it.
#+BEGIN_remarque
The smoothed trend is not a property of the distribution of the
observations, but of the chosen decomposition. The smooth trend model
assumes that the shocks to the trend and those to the deviation from trend
are independent; other assumptions are compatible with the same
distribution of $y_t$ and give other trends. At the other extreme, the
Beveridge and Nelson decomposition (/Journal of Monetary Economics/, 1981)
defines the trend as the long-run forecast of the series, net of trend
growth, which amounts to assuming that the shocks to the trend and to the
cycle are perfectly correlated: the trend is then a random walk. Morley,
Nelson and Zivot (/Review of Economics and Statistics/, 2003) note that,
for US GDP, this trend accounts for most of the fluctuations, and show that
Clark's trend and cycle model,
estimated without imposing independent shocks, gives filtered estimates of
the trend and the cycle identical to those of Beveridge and Nelson. For US
quarterly GDP from 1947 to 1998, the estimated correlation between the
shocks is $-0.9$, and independence is rejected at the 5% level, narrowly.
For a random walk with drift, which the data favour
[[#tendance-lineaire-locale][below]] for French GDP per capita over the
long run, the Beveridge-Nelson trend is the series itself and the cycle is
zero. The Hodrick-Prescott trend is therefore only one decomposition among
others, set by two assumptions: independent components and the shape of
the trend, whose smoothness is governed by $\lambda$.
#+END_remarque
** The data
:PROPERTIES:
:CUSTOM_ID: donnees
:END:
We use French GDP per capita from the Maddison Project Database, 2020
version, downloaded as in the note on
[[https://stephane-adjemian.fr/en/posts/plotting-a-gdp-time-series/][plotting a GDP time series]].
The annual series is continuous from 1820 to 2018, that is $n = 199$
observations. We work with \(y_t = 100\log(\text{GDP per capita})\), so that
first differences are growth rates in percent and standard deviations are
in percentage points.
#+begin_src python :session kalman-en :exports code :results none
MADDISON = 'mpd2020'
if not os.path.exists(MADDISON+'.xlsx'):
url.urlretrieve('https://www.rug.nl/ggdc/historicaldevelopment/maddison/data/'+MADDISON+'.xlsx',
MADDISON+'.xlsx')
data = pd.read_excel(MADDISON+'.xlsx', 'Full data')
france = data[(data.countrycode == 'FRA') & (data.year >= 1820)]
years = france.year.values.astype(int)
y = 100*np.log(france.gdppc.values)
dy = np.diff(y)
for start, end in ((1820, 2018), (1820, 1913), (1950, 1973), (1974, 2018)):
print(start, end, (y[years == end] - y[years == start])/(end - start))
print(years[1:][np.argsort(dy)[:2]], np.sort(dy)[:2], years[1:][np.argmax(dy)], dy.max())
#+end_src
Average annual growth is $1.54$ points over the whole period: $1.21$ points
per year between 1820 and 1913, $3.94$ between 1950 and 1973 and $1.39$
since 1974. The two world wars leave considerable falls, of $21.8$ points
in 1918 and $20.0$ points in 1941, followed by large rebounds, up to
$40.4$ points in 1946. These exceptional years weigh on all the results
that follow.
** Numerical check
:PROPERTIES:
:CUSTOM_ID: verification
:END:
We compare the Hodrick-Prescott trend, with $\lambda = 100$, and the
smoothed trend of the model with $\sigma_\varepsilon^2 = 1$ and
$\sigma_\zeta^2 = 1/100$, for several values of $\kappa$:
#+begin_src python :session kalman-en :exports code :results none
lam = 100
hp_trend = hp_filter(y, lam)
def smoothed_trend(y, s2_eps, s2_zeta, kappa):
Z, T, R, H, Q = smooth_trend(s2_eps, s2_zeta)
res = kalman_filter(y, Z, T, R, H, Q, np.zeros(2), kappa*np.eye(2))
return res, kalman_smoother(res, T)
for kap in (1e3, 1e5, 1e7, 1e9, 1e11):
_, (a_s, _) = smoothed_trend(y, 1.0, 1/lam, kap)
print(kap, np.max(np.abs(hp_trend - a_s[:, 0])))
kappa = 1e9
#+end_src
| $\kappa$ | Maximum difference |
|-----------+----------------------|
| $10^3$ | $3.3\times 10^{-1}$ |
| $10^5$ | $3.3\times 10^{-3}$ |
| $10^7$ | $3.3\times 10^{-5}$ |
| $10^9$ | $3.2\times 10^{-7}$ |
| $10^{11}$ | $2.2\times 10^{-5}$ |
The difference decreases like $1/\kappa$ as long as the diffuse
approximation dominates, then rises again when rounding errors take over:
with $\kappa = 10^{11}$, computing the first filtered variances loses about
ten significant digits. It is largest at the beginning of the sample, where
the initialisation matters, as shown in the figure [[fig:pib-france][below]].
From now on we use $\kappa = 10^9$.
#+begin_src python :session kalman-en :exports none :results none
res_hp, (a_hp, P_hp) = smoothed_trend(y, 1.0, 1/lam, kappa)
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
axes[0].plot(years, y, 'k', linewidth=0.8, label=r'$y_t$')
axes[0].plot(years, hp_trend, 'b', linewidth=1.5, label='Hodrick-Prescott')
axes[0].plot(years, a_hp[:, 0], 'r--', linewidth=1.5, label='Kalman smoother')
axes[0].set_xlabel('year')
axes[0].legend()
axes[1].plot(years, 1e7*(hp_trend - a_hp[:, 0]), 'b', linewidth=1)
axes[1].axhline(0, color='k', linewidth=0.5)
axes[1].set_xlabel('year')
axes[1].set_ylabel(r'difference ($\times 10^{-7}$)')
fig.tight_layout()
fig.savefig("kalman-pib-france.svg", transparent=True)
#+end_src
#+CAPTION: *Left, logarithm of French GDP per capita (multiplied by $100$), Hodrick-Prescott trend with $\lambda = 100$ and smoothed trend of the smooth trend model with $\kappa = 10^9$. Right, difference between the two trends.*
#+LABEL: fig:pib-france
[[file:kalman-pib-france.svg]]
The external check uses the =UnobservedComponents= class of =statsmodels=,
which implements the exact diffuse initialisation. We compare the smoothed
trends and the log-likelihoods for three pairs of variances:
#+begin_src python :session kalman-en :exports code :results none
from statsmodels.tsa.statespace.structural import UnobservedComponents
model_sm = UnobservedComponents(y, level='smooth trend', use_exact_diffuse=True)
for s2e, s2z in ((1.0, 0.01), (10.0, 5.0), (40.0, 0.4)):
res, (a_s, _) = smoothed_trend(y, s2e, s2z, kappa)
trend_sm = model_sm.smooth([s2e, s2z]).smoothed_state[0]
print(np.max(np.abs(trend_sm - a_s[:, 0])),
model_sm.loglike([s2e, s2z]) - loglik(res, d=2))
#+end_src
The trends differ by less than $1.4\times 10^{-5}$, and the difference
between the log-likelihoods is $-1.83787$ within $2\times 10^{-5}$ for the
three pairs, that is $-\log 2\pi$: as announced, the diffuse likelihood and
the exact diffuse likelihood differ only by a constant, which affects
neither the estimates nor the tests.
** Estimating the smoothing parameter
:PROPERTIES:
:CUSTOM_ID: estimer-lambda
:END:
The parameter $\lambda$ is the inverse of the signal-to-noise ratio
\(q = \sigma_\zeta^2/\sigma_\varepsilon^2\). We concentrate the likelihood
with respect to $\sigma_\varepsilon^2$ and maximise it in $\log q$,
discarding the first $d = 2$ innovations, since the initial level and slope
are diffuse. We also compute the likelihood with $\lambda = 100$, and the
estimate over two subperiods.
#+begin_src python :session kalman-en :exports code :results none
def logLc_smooth_trend(log_q, y):
Z, T, R, H, Q = smooth_trend(1.0, np.exp(log_q))
res = kalman_filter(y, Z, T, R, H, Q, np.zeros(2), kappa*np.eye(2))
return concentrated_loglik(res, d=2)
def estimate_lambda(y):
opt = minimize_scalar(lambda x: -logLc_smooth_trend(x, y)[0],
bounds=(np.log(1e-6), np.log(1e3)), method='bounded',
options={'xatol': 1e-8})
return opt.x
log_q = estimate_lambda(y)
logL_max, s2_eps = logLc_smooth_trend(log_q, y)
lam_hat = np.exp(-log_q)
se = 1/np.sqrt(-hessian(lambda x: logLc_smooth_trend(x[0], y)[0], [log_q])[0, 0])
ci = np.exp(-log_q + np.array([-1.96, 1.96])*se)
logL_100, s2_eps_100 = logLc_smooth_trend(np.log(1/100), y)
LR = 2*(logL_max - logL_100)
print(lam_hat, se, ci, LR, chi2.sf(LR, 1))
print(2*np.pi/(2*np.arcsin((16*lam_hat)**-0.25)))
for start in (1870, 1950):
print(start, np.exp(-estimate_lambda(y[years >= start])))
#+end_src
The estimate is $\hat\lambda = 1.24$, with a 95% confidence interval of
$[0.65, 2.36]$, built from the standard error of $\log\hat q$, which is
$0.33$. This is very far from the usual values: the likelihood ratio test
rejects $\lambda = 100$ with a statistic of $93.8$, for a critical value of
$3.84$ at the 5% level. By the remark on the frequency response, the gain
of the trend equals one half for a period of $6.4$ years with
$\hat\lambda$, against about twenty years with $\lambda = 100$: the
estimated trend absorbs most medium-term fluctuations. The result is not
due to the wars alone: the estimate is $0.86$ from 1870 and $0.53$
from 1950. The figure [[fig:vraisemblance][below]] shows the concentrated
log-likelihood as a function of $\lambda$: $\lambda = 6.25$ is already
clearly less likely than $\hat\lambda$, and $\lambda = 100$ or
$\lambda = 1\,600$ much less so.
To understand this rejection, we compare the two models, with the estimated
variances, using the standardised innovations and the cycle
$y_t - a_{\mu, t|n}$:
#+begin_src python :session kalman-en :exports code :results none
def fit(y, s2_eps, lam):
res, (a_s, P_s) = smoothed_trend(y, s2_eps, s2_eps/lam, kappa)
e = res['v'][2:, 0]/np.sqrt(res['F'][2:, 0, 0])
return res, a_s, P_s, e, y - a_s[:, 0]
results = {'100': fit(y, s2_eps_100, 100), 'hat': fit(y, s2_eps, lam_hat)}
for name, (res, a_s, P_s, e, cycle) in results.items():
print(name, acf(e, 3), cycle.std(), acf(cycle, 1))
#+end_src
| | $\lambda = 100$ | $\hat\lambda = 1.24$ |
|-------------------------------------+-----------------+----------------------|
| $\hat\sigma_\varepsilon$ | $6.85$ | $3.40$ |
| $\hat\sigma_\zeta$ | $0.68$ | $3.06$ |
| Log-likelihood | $-704.4$ | $-657.5$ |
| Autocorrelation of innovations (1) | $0.66$ | $0.08$ |
| Autocorrelation of innovations (2) | $0.34$ | $-0.26$ |
| Standard deviation of the cycle | $5.94$ | $2.70$ |
| Autocorrelation of the cycle (1) | $0.57$ | $-0.22$ |
#+begin_src python :session kalman-en :exports none :results none
lam_grid = np.logspace(-2, 4, 121)
profile = [logLc_smooth_trend(-np.log(l), y)[0] for l in lam_grid]
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
axes[0].plot(lam_grid, profile, 'b', linewidth=1)
for l, c, label in ((lam_hat, 'r', r'$\hat\lambda$'), (6.25, 'g', r'$\lambda = 6.25$'),
(100, 'k', r'$\lambda = 100$')):
axes[0].axvline(l, color=c, linestyle='--', linewidth=0.8, label=label)
axes[0].set_xscale('log')
axes[0].set_xlabel(r'$\lambda$')
axes[0].set_ylabel('concentrated log-likelihood')
axes[0].legend()
k = np.arange(1, 11)
axes[1].bar(k - 0.17, acf(results['100'][3], 10), width=0.34, color='k', label=r'$\lambda = 100$')
axes[1].bar(k + 0.17, acf(results['hat'][3], 10), width=0.34, color='r', label=r'$\hat\lambda$')
band = 1.96/np.sqrt(len(results['hat'][3]))
for s in (-1, 1):
axes[1].axhline(s*band, color='k', linestyle='--', linewidth=0.8)
axes[1].axhline(0, color='k', linewidth=0.5)
axes[1].set_xticks(k)
axes[1].set_xlabel('lag')
axes[1].set_ylabel('autocorrelation of innovations')
axes[1].legend()
fig.tight_layout()
fig.savefig("kalman-vraisemblance.svg", transparent=True)
#+end_src
#+CAPTION: *Left, concentrated log-likelihood of the smooth trend model as a function of $\lambda$ (logarithmic scale). Right, autocorrelations of the standardised innovations for $\lambda = 100$ and for $\hat\lambda$, with the band $\pm 1.96/\sqrt{n-2}$.*
#+LABEL: fig:vraisemblance
[[file:kalman-vraisemblance.svg]]
The two values of $\lambda$ tell very different stories (figure
[[fig:cycles][below]]). With $\lambda = 100$, the trend is smooth and the
cycle, with a standard deviation of $5.9$ points, is very persistent: it
records the wars as recessions lasting several years. But the model
assumes that the deviation from trend, $\varepsilon_t$, is white noise, and
its standardised innovations have a first-order autocorrelation of $0.66$:
the specification is rejected by its own diagnostics. Maximum likelihood
resolves the contradiction by making the trend flexible. With
$\hat\lambda$, the trend absorbs the persistent fluctuations and the
residual cycle, with a standard deviation of $2.7$ points, is no longer
persistent. The smooth trend model has no room for a persistent cycle: it
knows only the trend and the noise. Fixing $\lambda$ therefore amounts to
choosing a definition of the cycle, not to estimating it, a conclusion
already reached by Harvey and Jaeger (1993).
#+begin_src python :session kalman-en :exports none :results none
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
axes[0].plot(years, y, 'k', linewidth=0.8, label=r'$y_t$')
axes[0].plot(years, results['100'][1][:, 0], 'b', linewidth=1.2, label=r'trend, $\lambda = 100$')
axes[0].plot(years, results['hat'][1][:, 0], 'r', linewidth=1.2, label=r'trend, $\hat\lambda$')
axes[0].set_xlabel('year')
axes[0].legend()
axes[1].plot(years, results['100'][4], 'b', linewidth=1, label=r'$\lambda = 100$')
axes[1].plot(years, results['hat'][4], 'r', linewidth=1, label=r'$\hat\lambda$')
axes[1].axhline(0, color='k', linewidth=0.5)
axes[1].set_xlabel('year')
axes[1].set_ylabel(r'$y_t - a_{\mu, t|n}$')
axes[1].legend()
fig.tight_layout()
fig.savefig("kalman-cycles.svg", transparent=True)
#+end_src
#+CAPTION: *Smoothed trends (left) and cycles (right) for $\lambda = 100$ and for $\hat\lambda$.*
#+LABEL: fig:cycles
[[file:kalman-cycles.svg]]
*** Freeing the level variance
:PROPERTIES:
:CUSTOM_ID: tendance-lineaire-locale
:END:
The smooth trend model imposes $\sigma_\eta^2 = 0$. The local linear trend
model, which estimates all three variances, decides otherwise. We
concentrate the likelihood with respect to $\sigma_\eta^2$, the ratios of
the other two variances to $\sigma_\eta^2$ being estimated in logarithms and
bounded below by $e^{-20}$.
#+begin_src python :session kalman-en :exports code :results none
def logLc_llt(x, y):
Z, T, R, H, Q = local_linear_trend(np.exp(x[0]), 1.0, np.exp(x[1]))
res = kalman_filter(y, Z, T, R, H, Q, np.zeros(2), kappa*np.eye(2))
return concentrated_loglik(res, d=2)
opt_llt = minimize(lambda x: -logLc_llt(x, y)[0], [0.0, -2.0], method='L-BFGS-B',
bounds=[(-20.0, 5.0)]*2)
logL_llt, s2_eta = logLc_llt(opt_llt.x, y)
print(opt_llt.x, s2_eta, logL_llt, 2*(logL_llt - logL_max))
#+end_src
The optimum lies on the boundary: both ratios are at, or very close to,
their lower bound, and $\hat\sigma_\eta = 5.99$. The model reduces to a
random walk with drift, $\Delta y_t = \beta + \eta_t$, whose constant slope
is the average growth rate, $1.54$ points per year, and whose shocks have
the standard deviation of annual growth. The log-likelihood, $-635.0$,
exceeds that of the smooth trend model by $22.6$. The likelihood ratio
statistic, $45.1$, does not follow a $\chi^2$ distribution, since the
hypothesis $\sigma_\eta^2 = 0$ puts a parameter on the boundary of the
parameter space. In the setting of independent observations studied by
Self and Liang (/Journal of the American Statistical Association/, 1987),
its asymptotic distribution would be an equal mixture of a point mass at
zero and a $\chi^2(1)$ distribution; for a state variance in a time series
model, it may differ. With a statistic of this size, however, the
conclusion is beyond doubt. The
random walk with drift, which has only one variance parameter, is far more
likely than the smooth trend model, which has two: each annual change in
GDP per capita is a permanent shock to the level, including the falls and
rebounds of the wars.
#+BEGIN_remarque
We find again the pile-up phenomenon at the boundary mentioned in the note
on the
[[https://stephane-adjemian.fr/en/posts/ols-bias-in-an-ar1-process/#etat-mesure][bias of the least squares estimator of an AR(1)]]:
in unobserved components models, estimated variances are exactly zero with
positive probability. More importantly, if the series is a random walk, the
Hodrick-Prescott filter creates a persistent cycle that the series does not
contain (Cogley and Nason, /Journal of Economic Dynamics and Control/,
1995): the cycle obtained with $\lambda = 100$ is partly such an artefact.
#+END_remarque
** Filtered and smoothed trends
:PROPERTIES:
:CUSTOM_ID: filtre-vs-lisseur
:END:
The filtered trend $a_{\mu, t|t}$ uses only past observations: it is the
one-sided, real-time version of the Hodrick-Prescott filter (Stock and
Watson, /Journal of Monetary Economics/, 1999). Its gain quickly reaches its
steady-state value, computed from the algebraic Riccati equation:
#+begin_src python :session kalman-en :exports code :results none
for name, l in (('100', 100), ('hat', lam_hat)):
Z, T, R, H, Q = smooth_trend(1.0, 1/l)
P_bar = solve_discrete_are(T.T, Z.T, R @ Q @ R.T, H)
K_bar = P_bar @ Z.T/(Z @ P_bar @ Z.T + H)
K_t = results[name][0]['K'][:, :, 0]
print(name, K_bar.ravel(), years[np.argmax(np.all(np.abs(K_t - K_bar.ravel()) < 1e-3, axis=1))])
res, a_s, P_s, e, cycle = results['100']
revision = res['a_filt'][:, 0] - a_s[:, 0]
print(years[np.argmax(np.abs(revision))], revision.min())
print(np.sqrt(res['P_filt'][years == 1930, 0, 0]), np.sqrt(P_s[years == 1930, 0, 0]))
print(acf(y - res['a_filt'][:, 0], 1), acf(y - a_s[:, 0], 1))
#+end_src
With $\lambda = 100$, the steady-state gain is $(0.362, 0.080)'$, reached
within $10^{-3}$ from 1834: each year, the filtered trend corrects its level
by 36% of the innovation and its slope by 8%. With $\hat\lambda$, it is
$(0.750, 0.450)'$, reached from 1825. The figure
[[fig:filtre-vs-lisseur][below]] compares the two trends for
$\lambda = 100$ around the two wars. In real time, the collapses are partly
attributed to the trend, which drops; with hindsight, the smoother, which
sees the reconstruction, attributes them to the cycle. In 1945, the
filtered trend is $31.5$ points below the smoothed trend. The conditional
standard deviation of the trend is $4.1$ points for the filter and $2.3$
points for the smoother, away from the ends of the sample. The one-sided
cycle is more persistent than the two-sided cycle, with a first-order
autocorrelation of $0.66$ against $0.57$.
#+begin_src python :session kalman-en :exports none :results none
window = (years >= 1900) & (years <= 1960)
af, sf = res['a_filt'][:, 0], np.sqrt(res['P_filt'][:, 0, 0])
asm, ssm = a_s[:, 0], np.sqrt(P_s[:, 0, 0])
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
axes[0].plot(years[window], y[window], 'k', linewidth=0.8, label=r'$y_t$')
axes[0].plot(years[window], af[window], 'b', linewidth=1.2, label=r'filtered trend $a_{\mu, t|t}$')
axes[0].fill_between(years[window], (af-2*sf)[window], (af+2*sf)[window], color='b', alpha=0.12, linewidth=0)
axes[0].plot(years[window], asm[window], 'r', linewidth=1.2, label=r'smoothed trend $a_{\mu, t|n}$')
axes[0].fill_between(years[window], (asm-2*ssm)[window], (asm+2*ssm)[window], color='r', alpha=0.2, linewidth=0)
axes[0].set_xlabel('year')
axes[0].legend(loc='upper left')
axes[1].plot(years, y - af, 'b', linewidth=1, label='one-sided cycle')
axes[1].plot(years, y - asm, 'r', linewidth=1, label='two-sided cycle')
axes[1].axhline(0, color='k', linewidth=0.5)
axes[1].set_xlabel('year')
axes[1].legend()
fig.tight_layout()
fig.savefig("kalman-filtre-vs-lisseur.svg", transparent=True)
#+end_src
#+CAPTION: *Smooth trend model with $\lambda = 100$. Left, filtered and smoothed trends from 1900 to 1960, with bands of plus or minus two conditional standard deviations. Right, one-sided cycle, $y_t - a_{\mu, t|t}$, and two-sided cycle, $y_t - a_{\mu, t|n}$, which is the Hodrick-Prescott cycle.*
#+LABEL: fig:filtre-vs-lisseur
[[file:kalman-filtre-vs-lisseur.svg]]
#+BEGIN_remarque
The handling of missing observations makes it possible to interpolate the
trend. If the years 1939 to 1945 are removed, the smoother extends the
trend through the war: it estimates it at $880.8$ points in 1942, with a
standard deviation of $3.7$ points, against $846.6$ observed and a trend of
$861.4$ with all the data.
#+END_remarque
#+begin_src python :session kalman-en :exports code :results none
y_missing = y.copy()
y_missing[(years >= 1939) & (years <= 1945)] = np.nan
_, (a_m, P_m) = smoothed_trend(y_missing, s2_eps_100, s2_eps_100/100, kappa)
print(a_m[years == 1942, 0], np.sqrt(P_m[years == 1942, 0, 0]))
print(y[years == 1942], results['100'][1][years == 1942, 0])
#+end_src
** A trend and a cycle
:PROPERTIES:
:CUSTOM_ID: tendance-cycle
:END:
To obtain a persistent cycle that is not imposed by the choice of
$\lambda$, it has to be modelled. We replace the noise $\varepsilon_t$ by a
stationary second-order autoregressive process. This is a variant of the
model of Clark (/Quarterly Journal of Economics/, 1987), whose trend also
has a shock on the level:
\begin{equation*}
y_t = \mu_t + \psi_t,\qquad
\mu_t = \mu_{t-1} + \beta_{t-1},\qquad
\beta_t = \beta_{t-1} + \zeta_t,\qquad
\psi_t = \phi_1\psi_{t-1} + \phi_2\psi_{t-2} + u_t,
\end{equation*}
with \(u_t \sim \mathcal N(0, \sigma_u^2)\). The state is
\(\alpha_t = (\mu_t, \beta_t, \psi_t, \psi_{t-1})'\), the measurement is
perfect ($H = 0$) and
\begin{equation*}
Z = \begin{pmatrix} 1 & 0 & 1 & 0 \end{pmatrix},\quad
T = \begin{pmatrix}
1 & 1 & 0 & 0\\
0 & 1 & 0 & 0\\
0 & 0 & \phi_1 & \phi_2\\
0 & 0 & 1 & 0
\end{pmatrix},\quad
R = \begin{pmatrix} 0 & 0\\ 1 & 0\\ 0 & 1\\ 0 & 0 \end{pmatrix},\quad
Q = \begin{pmatrix} \sigma_\zeta^2 & 0\\ 0 & \sigma_u^2 \end{pmatrix}.
\end{equation*}
The initialisation is mixed: diffuse for the trend, stationary, through the
Lyapunov equation, for the cycle. We concentrate the likelihood with respect
to $\sigma_u^2$. To guarantee the stationarity of the cycle,
$(\phi_1, \phi_2)$ are parameterised by the partial autocorrelations
$r_1, r_2 \in (-1, 1)$, with $\phi_2 = r_2$ and \(\phi_1 = r_1(1 - r_2)\)
(Barndorff-Nielsen and Schou, /Journal of Multivariate Analysis/, 1973),
themselves written \(r_i = \tanh(x_i)\) with $x_i$ real.
#+begin_src python :session kalman-en :exports code :results none
def trend_cycle(s2_zeta, phi1, phi2, s2_u):
Z = np.array([[1.0, 0.0, 1.0, 0.0]])
T = np.array([[1.0, 1.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, phi1, phi2],
[0.0, 0.0, 1.0, 0.0]])
R = np.array([[0.0, 0.0],
[1.0, 0.0],
[0.0, 1.0],
[0.0, 0.0]])
return Z, T, R, np.zeros((1, 1)), np.diag([s2_zeta, s2_u])
def init_trend_cycle(T, R, Q):
P0 = np.zeros((4, 4))
P0[:2, :2] = kappa*np.eye(2)
P0[2:, 2:] = solve_discrete_lyapunov(T[2:, 2:], (R @ Q @ R.T)[2:, 2:])
return np.zeros(4), P0
def parameters(x):
r1, r2 = np.tanh(x[1]), np.tanh(x[2])
return np.exp(x[0]), r1*(1 - r2), r2
def logLc_trend_cycle(x, y):
Z, T, R, H, Q = trend_cycle(*parameters(x), 1.0)
res = kalman_filter(y, Z, T, R, H, Q, *init_trend_cycle(T, R, Q))
return concentrated_loglik(res, d=2)
#+end_src
The wars make it impossible to estimate this model on the full sample: the
cycle absorbs the falls and rebounds, its dominant autoregressive root is
$0.95$ and the variance of the slope becomes almost zero, so that the model
gets close to a random walk with drift. We therefore estimate it over
1950--2018, from four starting points, because the likelihood of unobserved
components models often has several local maxima.
#+begin_src python :session kalman-en :exports code :results none
y50, years50 = y[years >= 1950], years[years >= 1950]
best = None
for x0 in ([-2.0, 0.5, 0.0], [-5.0, 1.0, -0.5], [0.0, 0.2, 0.2], [-1.0, 1.5, -0.8]):
opt = minimize(lambda x: -logLc_trend_cycle(x, y50)[0], x0, method='Nelder-Mead',
options={'xatol': 1e-8, 'fatol': 1e-10, 'maxiter': 5000})
print(x0, -opt.fun)
if best is None or opt.fun < best.fun:
best = opt
logL_tc, s2_u = logLc_trend_cycle(best.x, y50)
q_zeta, phi1, phi2 = parameters(best.x)
roots = np.roots([1, -phi1, -phi2])
print(q_zeta*s2_u, phi1, phi2, s2_u, np.abs(roots), 2*np.pi/np.abs(np.angle(roots[0])))
logL_st50 = logLc_smooth_trend(estimate_lambda(y50), y50)[0]
print(logL_tc, logL_st50, chi2.sf(2*(logL_tc - logL_st50), 2))
#+end_src
One of the four optimisations stops at a local maximum where the first
partial autocorrelation of the cycle tends to one; the other three converge
to the same point. The estimates are $\hat\phi_1 = 0.807$ and
$\hat\phi_2 = -0.306$: the roots of the autoregressive polynomial are
complex, with modulus $0.55$, and the cycle has a period of $8.3$ years.
The standard deviation of the cycle shocks is $1.03$ points, that of the
slope of the trend $0.40$ points. The model clearly improves on the
log-likelihood of the smooth trend model estimated over the same period,
which goes from $-126.4$ to $-119.1$: the likelihood ratio statistic is
$14.6$ for the two restrictions $\phi_1 = \phi_2 = 0$, a p-value of
$0.0007$.
#+begin_src python :session kalman-en :exports code :results none
Z_tc, T_tc, R_tc, H_tc, Q_tc = trend_cycle(q_zeta*s2_u, phi1, phi2, s2_u)
res_tc = kalman_filter(y50, Z_tc, T_tc, R_tc, H_tc, Q_tc, *init_trend_cycle(T_tc, R_tc, Q_tc))
a_tc, P_tc = kalman_smoother(res_tc, T_tc)
hp_distance = lambda l: np.sqrt(np.mean((hp_filter(y50, l) - a_tc[:, 0])**2))
opt = minimize_scalar(lambda x: hp_distance(np.exp(x)), bounds=(np.log(0.1), np.log(1e4)), method='bounded')
print(np.exp(opt.x), hp_distance(np.exp(opt.x)), hp_distance(100))
print(acf(res_tc['v'][2:, 0]/np.sqrt(res_tc['F'][2:, 0, 0]), 3))
print(a_tc[-1, 2], (y50 - hp_filter(y50, 100))[-1])
#+end_src
The standardised innovations no longer show any autocorrelation. The trend
of the model is very close to a Hodrick-Prescott trend, but not with
parameter $100$: the value of $\lambda$ that best reproduces it is $27$,
with a root mean squared difference of $0.10$ points, against $0.39$ for
$\lambda = 100$. The smoothed cycle (figure [[fig:tendance-cycle][below]])
picks up the recessions of 1975, 1993 and 2009, and puts the economy
slightly above its trend in 2018, by $0.8$ points against $1.6$ for the
Hodrick-Prescott cycle.
#+begin_src python :session kalman-en :exports none :results none
hp50 = hp_filter(y50, 100)
c, sc = a_tc[:, 2], np.sqrt(P_tc[:, 2, 2])
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
axes[0].plot(years50, y50, 'k', linewidth=0.8, label=r'$y_t$')
axes[0].plot(years50, a_tc[:, 0], 'r', linewidth=1.2, label='model trend')
axes[0].plot(years50, hp50, 'b--', linewidth=1.2, label=r'Hodrick-Prescott, $\lambda = 100$')
axes[0].set_xlabel('year')
axes[0].legend()
axes[1].plot(years50, c, 'r', linewidth=1.2, label=r'cycle $a_{\psi, t|n}$')
axes[1].fill_between(years50, c-2*sc, c+2*sc, color='r', alpha=0.2, linewidth=0)
axes[1].plot(years50, y50 - hp50, 'b--', linewidth=1, label=r'HP cycle, $\lambda = 100$')
axes[1].axhline(0, color='k', linewidth=0.5)
axes[1].set_xlabel('year')
axes[1].legend()
fig.tight_layout()
fig.savefig("kalman-tendance-cycle.svg", transparent=True)
#+end_src
#+CAPTION: *Trend and cycle model estimated over 1950--2018. Left, smoothed trend and Hodrick-Prescott trend with $\lambda = 100$. Right, smoothed cycle with a band of plus or minus two conditional standard deviations, and Hodrick-Prescott cycle.*
#+LABEL: fig:tendance-cycle
[[file:kalman-tendance-cycle.svg]]
** Forecasts
:PROPERTIES:
:CUSTOM_ID: previsions-pib
:END:
Finally, we compare the forecasts from 2019 to 2028 of two models estimated
over 1950--2018: the smooth trend model with $\lambda = 100$, the variance
$\sigma_\varepsilon^2$ being estimated, and the trend and cycle model.
#+begin_src python :session kalman-en :exports code :results none
h = 10
s2_100_50 = logLc_smooth_trend(np.log(1/100), y50)[1]
model_st = smooth_trend(s2_100_50, s2_100_50/100)
res_st = kalman_filter(y50, *model_st, np.zeros(2), kappa*np.eye(2))
fc = {'st': forecast(res_st['a_filt'][-1], res_st['P_filt'][-1], *model_st, h),
'tc': forecast(res_tc['a_filt'][-1], res_tc['P_filt'][-1], Z_tc, T_tc, R_tc, H_tc, Q_tc, h)}
for name, (y_fc, F_fc) in fc.items():
print(name, y_fc[0] - y50[-1], np.sqrt(F_fc[[0, 4, 9]]))
print(res_st['a_filt'][-1][1], res_tc['a_filt'][-1][1], np.sqrt(s2_100_50/100), np.sqrt(q_zeta*s2_u))
#+end_src
With $\lambda = 100$, the forecast for 2019 is $1.0$ point below the 2018
level: the cycle, assumed to have no memory, disappears immediately and the
forecast starts from the filtered trend, which it extends with a slope of
$0.60$ points per year. The model with a cycle forecasts instead a growth
of $0.8$ points in 2019, since the cycle fades only gradually, followed by
a slope of $1.04$ points. The forecast standard deviations are $2.2$, $3.3$
and $5.6$ points at one, five and ten years with $\lambda = 100$, against
$1.4$, $4.9$ and $10.2$ points with the cycle (figure
[[fig:previsions][below]]). The apparent long-run precision of the first
model comes from the small variance it imposes on the slope of the trend,
$\sigma_\zeta = 0.17$ points against $0.40$: fixing $\lambda$ also means
fixing the uncertainty about future growth.
#+begin_src python :session kalman-en :exports none :results none
years_fc = np.arange(years50[-1] + 1, years50[-1] + h + 1)
window = years50 >= 1990
fig, axes = plt.subplots(1, 2, figsize=(12, 4), sharey=True)
for ax, name, title in ((axes[0], 'st', r'smooth trend, $\lambda = 100$'),
(axes[1], 'tc', 'trend and AR(2) cycle')):
y_fc, F_fc = fc[name]
ax.plot(years50[window], y50[window], 'k', linewidth=1)
for z, alpha in ((1.645, 0.15), (0.674, 0.3)):
ax.fill_between(years_fc, y_fc - z*np.sqrt(F_fc), y_fc + z*np.sqrt(F_fc),
color='b', alpha=alpha, linewidth=0)
ax.plot(years_fc, y_fc, 'b', linewidth=1.2)
ax.set_title(title)
ax.set_xlabel('year')
fig.tight_layout()
fig.savefig("kalman-previsions.svg", transparent=True)
#+end_src
#+CAPTION: *Forecasts of the logarithm of GDP per capita (multiplied by $100$) from 2019 to 2028, with 50% and 90% forecast intervals, for the smooth trend model with $\lambda = 100$ (left) and for the trend and cycle model (right), estimated over 1950--2018.*
#+LABEL: fig:previsions
[[file:kalman-previsions.svg]]
At the end of the application, the Hodrick-Prescott filter appears as a
special case of an explicit model, whose likelihood assesses the
assumptions. For French GDP per capita, it rejects the usual value of the
smoothing parameter, prefers a random walk with drift to the smooth trend
model over the long run, and, since 1950, finds an eight-year cycle once
the cycle is modelled explicitly.
* Exercises
:PROPERTIES:
:CUSTOM_ID: exercices
:END:
#+NAME: ex-ar2-mesure
#+BEGIN_exercice
A stationary second-order autoregressive process is observed with
measurement error: $y_t = x_t + \varepsilon_t$, with
\(x_t = \phi_1x_{t-1} + \phi_2x_{t-2} + \eta_t\), where $\varepsilon_t$ and
$\eta_t$ are independent Gaussian white noises, with variances
$\sigma_\varepsilon^2$ and $\sigma_\eta^2$. Write the model in state space
form and explain how to initialise the filter. Simulate $n = 200$
observations with $\phi_1 = 1.2$, $\phi_2 = -0.5$ and
$\sigma_\varepsilon = \sigma_\eta = 1$, then compare the root mean squared
errors of $y_t$, of the filtered state and of the smoothed state as
estimators of $x_t$.
#+END_exercice
#+ATTR_HTML: :class corrige
#+BEGIN_details
#+BEGIN_summary
Solution
#+END_summary
The state is $\alpha_t = (x_t, x_{t-1})'$, and
\begin{equation*}
Z = \begin{pmatrix} 1 & 0 \end{pmatrix},\quad
T = \begin{pmatrix} \phi_1 & \phi_2\\ 1 & 0 \end{pmatrix},\quad
R = \begin{pmatrix} 1\\ 0 \end{pmatrix},\quad
H = \sigma_\varepsilon^2,\quad
Q = \sigma_\eta^2.
\end{equation*}
The roots of the polynomial $1 - 1.2z + 0.5z^2$ have modulus
$\sqrt 2 > 1$: the process is stationary, and the filter is initialised
with its stationary distribution, $a_0 = 0$ and $P_0$ the solution of the
Lyapunov equation.
#+begin_src python :session kalman-en :exports code :results none
phi_1, phi_2, n_ex = 1.2, -0.5, 200
x = np.zeros(n_ex + 100)
for t in range(2, n_ex + 100):
x[t] = phi_1*x[t-1] + phi_2*x[t-2] + rng.standard_normal()
x = x[100:]
y_ex = x + rng.standard_normal(n_ex)
Z = np.array([[1.0, 0.0]])
T = np.array([[phi_1, phi_2],
[1.0, 0.0]])
R = np.array([[1.0],
[0.0]])
H, Q = np.eye(1), np.eye(1)
P0 = solve_discrete_lyapunov(T, R @ Q @ R.T)
res_ex = kalman_filter(y_ex, Z, T, R, H, Q, np.zeros(2), P0)
a_ex, _ = kalman_smoother(res_ex, T)
for estimator in (y_ex, res_ex['a_filt'][:, 0], a_ex[:, 0]):
print(np.sqrt(np.mean((estimator - x)**2)))
#+end_src
The root mean squared errors are $1.03$ for the observation, $0.78$ for the
filtered state and $0.72$ for the smoothed state. Since the stationary
variance of $x_t$ is $3.7$, the signal dominates the noise, and filtering
gains less than in the local level model of the
[[#illustration][illustration]] section. The first $100$ simulated values
are discarded so that $x_t$ is drawn from its stationary distribution.
#+END_details
#+NAME: ex-arima022
#+BEGIN_exercice
Show that in the smooth trend model, the second difference $\Delta^2y_t$ is
an MA(2) process, and compute its autocorrelations as functions of
\(q = \sigma_\zeta^2/\sigma_\varepsilon^2\). Compare them, for
$\lambda = 100$ and for $\hat\lambda$, with the sample autocorrelations of
the second difference of the logarithm of French GDP per capita. What do
you conclude?
#+END_exercice
#+ATTR_HTML: :class corrige
#+BEGIN_details
#+BEGIN_summary
Solution
#+END_summary
Since $\Delta^2\mu_t = \zeta_{t-1}$,
\begin{equation*}
\Delta^2y_t = \zeta_{t-1} + \varepsilon_t - 2\varepsilon_{t-1} + \varepsilon_{t-2}.
\end{equation*}
The autocovariances are \(\gamma_0 = \sigma_\zeta^2 + 6\sigma_\varepsilon^2\),
\(\gamma_1 = -4\sigma_\varepsilon^2\), \(\gamma_2 = \sigma_\varepsilon^2\)
and $\gamma_k = 0$ for $k \geq 3$: $\Delta^2 y_t$ is an MA(2), and $y_t$ an
ARIMA(0,2,2), with autocorrelations
\begin{equation*}
\rho_1 = -\frac{4}{6 + q},\qquad \rho_2 = \frac{1}{6 + q},\qquad \rho_k = 0 \text{ for } k \geq 3.
\end{equation*}
#+begin_src python :session kalman-en :exports code :results none
print(acf(np.diff(y, 2), 4))
for l in (100, lam_hat):
print(l, -4/(6 + 1/l), 1/(6 + 1/l))
#+end_src
The model implies $\rho_1 = -0.67$ and $\rho_2 = 0.17$ for $\lambda = 100$,
$\rho_1 = -0.59$ and $\rho_2 = 0.15$ for $\hat\lambda$. The sample
autocorrelations are $-0.41$, $-0.22$, $0.24$ and $0.01$ at the first four
lags. The negative sign of $\hat\rho_2$ and the positive value of
$\hat\rho_3$, both outside the band $\pm 1.96/\sqrt{197} \approx \pm 0.14$,
are incompatible with the model whatever $q$, since it imposes
$\rho_2 > 0$ and $\rho_3 = 0$: the smooth trend model is misspecified for
this series, as confirmed by the superiority of the random walk with drift.
#+END_details
#+NAME: ex-h-nul
#+BEGIN_exercice
Consider the state space representation of an AR($p$) process given in the
examples, with $H = 0$, and an arbitrary initialisation. Show that for
$t \geq p$, the filtered state is \(a_{t|t} = (y_t, \dots, y_{t-p+1})'\) and
that $P_{t|t} = 0$. Deduce that, for $t > p$, the innovation is the
forecast error \(y_t - \phi_1y_{t-1} - \dots - \phi_py_{t-p}\), with
variance $\sigma^2$, and that maximising the terms of the log-likelihood
corresponding to dates $t > p$ gives the ordinary least squares estimator.
#+END_exercice
#+ATTR_HTML: :class corrige
#+BEGIN_details
#+BEGIN_summary
Solution
#+END_summary
At each date, the observation $y_t$ equals, without error, the first
component of the state. The updating step therefore sets the variance of
this component to zero: if $F_t > 0$, the gain is
\(K_t = P_{t|t-1}Z'/F_t\) with \(F_t = Z P_{t|t-1} Z'\), and the first row
of $I - K_tZ$ is zero since \(ZK_t = 1\), so that $P_{t|t}$ has a zero first
row and a zero first column. The prediction step shifts the state: component
$j+1$ of $\alpha_{t+1}$ is component $j$ of $\alpha_t$, known exactly. By
induction, after $p$ observations, all the components of the state are past
observations, known exactly: \(a_{p|p} = (y_p, \dots, y_1)'\) and
$P_{p|p} = 0$.
For $t > p$, we then have \(a_{t|t-1} = Ta_{t-1|t-1}\), whose first
component is \(\phi_1y_{t-1} + \dots + \phi_py_{t-p}\), and
\(P_{t|t-1} = RQR'\), whose only non-zero element is the first one,
$\sigma^2$. The innovation is
\(v_t = y_t - \phi_1y_{t-1} - \dots - \phi_py_{t-p}\), with variance
$F_t = \sigma^2$, the gain is the first vector of the canonical basis and,
again, $a_{t|t} = (y_t, \dots, y_{t-p+1})'$ and $P_{t|t} = 0$. The terms of
the log-likelihood for $t > p$ are
\begin{equation*}
-\frac{n-p}{2}\log 2\pi\sigma^2 - \frac{1}{2\sigma^2}\sum_{t=p+1}^n\left(y_t - \phi_1y_{t-1} - \dots - \phi_py_{t-p}\right)^2.
\end{equation*}
For any $\sigma^2$, they are maximised in $(\phi_1, \dots, \phi_p)$ when the
sum of squares is minimal: this is the ordinary least squares estimator of
the regression of $y_t$ on its $p$ lagged values. The exact likelihood,
obtained with the stationary initialisation, adds the first $p$ terms, which
depend on the coefficients through the stationary distribution of the first
observations. This is the only difference between the two estimators,
negligible asymptotically but not in small samples (see the note on the
[[https://stephane-adjemian.fr/en/posts/ols-bias-in-an-ar1-process/][bias of the least squares estimator of an AR(1)]]).
#+END_details
#+NAME: ex-limites-hp
#+BEGIN_exercice
Find the limits of the Hodrick-Prescott trend as $\lambda$ tends to zero
and to infinity, and interpret the second one in the smooth trend model.
Check numerically on French GDP per capita, and explain why convergence is
so slow as $\lambda$ increases.
#+END_exercice
#+ATTR_HTML: :class corrige
#+BEGIN_details
#+BEGIN_summary
Solution
#+END_summary
As $\lambda$ tends to zero, $I_n + \lambda D'D$ tends to $I_n$ and the trend
to the series. As $\lambda$ tends to infinity, the penalty imposes in the
limit $D\mu = 0$, that is zero second differences: the trend is an affine
sequence $\mu_t = a + bt$, and minimising $\sum(y_t - a - bt)^2$ gives the
least squares line. In the smooth trend model, $\lambda \to \infty$
corresponds to $\sigma_\zeta^2 \to 0$: the slope is constant and the trend
deterministic, with a diffuse initial level and slope, whose estimate is
the least squares one.
#+begin_src python :session kalman-en :exports code :results none
t_ = np.arange(len(y))
line = np.polyval(np.polyfit(t_, y, 1), t_)
for l in (1e2, 1e4, 1e6, 1e8, 1e10):
print(l, np.max(np.abs(hp_filter(y, l) - line)))
D = sparse.diags([1.0, -2.0, 1.0], [0, 1, 2], shape=(len(y)-2, len(y))).toarray()
print(np.linalg.eigvalsh(D.T @ D)[:3])
#+end_src
The maximum distance to the line is $62.1$ points for $\lambda = 100$,
$36.5$ for $10^4$, $27.2$ for $10^6$, $1.10$ for $10^8$ and $0.012$ for
$10^{10}$. To understand this, decompose $y$ on an orthonormal basis of
eigenvectors of $D'D$, with eigenvalues $\nu_k$: the trend multiplies the
component of $y$ on the \(k\)-th vector by $1/(1 + \lambda\nu_k)$. The
kernel of $D$, of dimension two, is the set of affine sequences, whose
components are preserved. The others vanish only when $\lambda\nu_k$ is
large, and the smallest non-zero eigenvalue of $D'D$, which decreases like
$1/n^4$, is only $3.2\times 10^{-7}$ for $n = 199$: the corresponding
component, a very slow oscillation, is still multiplied by $0.76$ for
$\lambda = 10^6$, and $\lambda$ must be well above $3\times 10^6$ for it to
disappear.
#+END_details
#+NAME: ex-lisseur-regime-permanent
#+BEGIN_exercice
In the local level model in steady state, show that the smoothing gain is
$\bar J = -\theta$, where $\theta$ is the coefficient of the reduced form,
that the smoother reads
\(a_{t|n} = (1 + \theta)\,a_{t|t} - \theta\,a_{t+1|n}\), and that the
smoothed variance is
\(\bar P_{t|n} = \sigma_\varepsilon^2(1 + \theta)/(1 - \theta)\). Check this
result on the simulated series of the [[#illustration][illustration]]
section.
#+END_exercice
#+ATTR_HTML: :class corrige
#+BEGIN_details
#+BEGIN_summary
Solution
#+END_summary
In the steady state, $P_{t|t} = \sigma_\varepsilon^2\bar K$ and
$P_{t+1|t} = \sigma_\varepsilon^2\bar p$, hence, with $T = 1$,
\begin{equation*}
\bar J = \frac{\bar K}{\bar p} = \frac{1}{1 + \bar p} = -\theta,
\end{equation*}
by the properties of the reduced form. Since $a_{t+1|t} = a_{t|t}$, the
smoother recursion reads
\(a_{t|n} = a_{t|t} + \bar J(a_{t+1|n} - a_{t|t}) = (1 + \theta)\,a_{t|t} - \theta\,a_{t+1|n}\):
the smoother applies backwards to the filtered series an exponential
smoothing with the same constant $\bar K = 1 + \theta$. The steady-state
smoothed variance is the fixed point of
\(P = \sigma_\varepsilon^2\bar K + \theta^2(P - \sigma_\varepsilon^2\bar p)\),
that is
\begin{equation*}
\bar P_{t|n} = \sigma_\varepsilon^2\,\frac{\bar K - \theta^2\bar p}{1 - \theta^2}
= \sigma_\varepsilon^2\,\frac{(1 + \theta) + \theta(1 + \theta)}{(1 - \theta)(1 + \theta)}
= \sigma_\varepsilon^2\,\frac{1 + \theta}{1 - \theta},
\end{equation*}
using $\bar p = -(1 + \theta)/\theta$. Smoothing therefore divides the
filtered variance $\sigma_\varepsilon^2(1 + \theta)$ by $1 - \theta$.
#+begin_src python :session kalman-en :exports code :results none
q = sigma_eta**2/sigma_eps**2
theta = (np.sqrt(q**2 + 4*q) - q - 2)/2
print(sigma_eps**2*(1 + theta)/(1 - theta), P_smooth[50, 0, 0])
#+end_src
With $q = 0.25$, $\theta = -0.610$, and the formula gives $0.2425$, the
value computed by the smoother in the middle of the simulated sample,
against $0.3904$ for the filtered variance.
#+END_details