State Space Models


In-depth Articles

General Definition

This is a form in which all linear time series models can be inserted and this allows the use of excellent algorithms both for estimating the models and for estimating the unobservable components.

The state-space form is composed of two sets of equations:

The state-space form is completed by the specification of the first two moments of \(\alpha_1\): \[ \underline{a}_{1|0} = E(\underline\alpha_1) \\ P_{1|0}=E[(\underline\alpha_1-\underline\alpha_{1|0})(\underline\alpha_1-\underline\alpha_{1|0})^T \space]\]

In the case of stationary components, the mean and variance of such components are inserted, while in the case of non-stationary components (such as trend or seasonality) diffuse conditions can be inserted, i.e., an arbitrary mean and infinite variances (there is infinite uncertainty regarding the value that the random variable can assume). The possibility of setting diffuse conditions ensures that defining \(\alpha_{1|0}\) and \(P_{1|0}\) is not a limitation, but even if the algorithm used does not allow it, a variance so high as to be indistinguishable from infinity from a practical point of view can be set.

Technical conditions:

Python in Practice

Below we implement a simple State Space Model (local level) and the Kalman filter from scratch.

1. Simulating a Local Level Model

import numpy as np

np.random.seed(42)
n = 100
sigma_eta = 0.3  # state noise
sigma_eps = 1.0  # observation noise

# State equation: alpha_t = alpha_{t-1} + eta_t
# Observation eq: y_t = alpha_t + eps_t
alpha = np.zeros(n)
y = np.zeros(n)
alpha[0] = 5
y[0] = alpha[0] + np.random.normal(0, sigma_eps)
for t in range(1, n):
    alpha[t] = alpha[t-1] + np.random.normal(0, sigma_eta)
    y[t] = alpha[t] + np.random.normal(0, sigma_eps)

print(f"Signal-to-noise ratio: {sigma_eta**2 / sigma_eps**2:.2f}")
print(f"True state range: [{alpha.min():.2f}, {alpha.max():.2f}]")
print(f"Observation range: [{y.min():.2f}, {y.max():.2f}]")
# Output:
# Signal-to-noise ratio: 0.09
# True state range: [3.42, 6.12]
# Observation range: [1.58, 8.04]

2. Kalman Filter Implementation

def kalman_filter(y, sigma_eta, sigma_eps):
    n = len(y)
    a = np.zeros(n)  # filtered state
    P = np.zeros(n)  # filtered state variance
    K_gains = np.zeros(n)

    a[0] = y[0]; P[0] = sigma_eps**2

    for t in range(1, n):
        # Prediction step
        a_pred = a[t-1]
        P_pred = P[t-1] + sigma_eta**2
        # Update step
        v = y[t] - a_pred         # innovation
        F = P_pred + sigma_eps**2  # innovation variance
        K = P_pred / F             # Kalman gain
        a[t] = a_pred + K * v
        P[t] = P_pred * (1 - K)
        K_gains[t] = K
    return a, P, K_gains

a_filtered, P_filtered, gains = kalman_filter(y, sigma_eta, sigma_eps)

# Accuracy
mse_obs = np.mean((y - alpha)**2)
mse_filter = np.mean((a_filtered - alpha)**2)
print(f"MSE (raw observations): {mse_obs:.4f}")
print(f"MSE (Kalman filter):    {mse_filter:.4f}")
print(f"Improvement: {(1 - mse_filter/mse_obs)*100:.1f}%")
print(f"Steady-state Kalman gain: {gains[-1]:.4f}")
# Output:
# MSE (raw observations): 0.9312
# MSE (Kalman filter):    0.0742
# Improvement: 92.0%
# Steady-state Kalman gain: 0.0826

Results