---
title: "Testing the last observation for instability"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Testing the last observation for instability}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  fig.width = 6,
  fig.height = 4.2,
  out.width = "100%"
)
set.seed(20260621)
```

```{r setup}
library(proxymix)
```

# The end-of-sample question

A model is fitted to a series and one more observation arrives. Is it
consistent with the model, or has something just changed? When the suspect
window is tiny -- a single point, or a handful -- the usual structural-break
tests (Chow, sup-Wald) are undefined, because the parameters of the post-break
regime cannot be estimated from so few observations. `gmm_eos_test()` answers
the question in exactly this regime by scoring the last `m` one-step filter
innovations and calibrating the score, following Andrews (2003).

# The statistic

Run the Gaussian-sum filter implied by the operator calculus over the series.
At each step it returns a one-step predictive mean and an innovation covariance
$S_t$, so the standardised innovation
$z_t = S_t^{-1/2}(y_t - C\hat{x}_{t\mid t-1})$ is standard normal under a
correct, stable, linear-Gaussian model. The statistic sums the last `m` squared
standardised innovations,
$$ \mathrm{EoS}_m = \sum_{t = n-m+1}^{n} z_t^{\top} z_t, $$
which a break inflates. Two calibrations are offered. `method = "chisq"` refers
the statistic to a $\chi^2$ distribution on `m` times the observation dimension
degrees of freedom, exact when the innovations are Gaussian. `method =
"andrews"` is the distribution-free subsampling test of Andrews (2003): the
statistic's rank among the in-sample overlapping `m`-blocks of the same
innovations gives the p-value, which stays calibrated when the innovations are
not Gaussian.

# A worked example

Take a local-level model, a random walk observed with noise, and simulate a
stable series from it.

```{r model}
prior       <- gmm(weights = 1, means = list(0), covariances = list(matrix(10)))
dynamics    <- list(A = matrix(1), Q = matrix(0.04))   # the state random walk
measurement <- list(C = matrix(1), R = matrix(1))      # the noisy observation

n <- 120
level <- cumsum(c(0, rnorm(n - 1, 0, sqrt(0.04))))
y_stable <- level + rnorm(n, 0, 1)
```

A stable series is not flagged: the last observation sits where the filter
expects it.

```{r stable}
gmm_eos_test(prior, dynamics, measurement, y_stable, m = 1L, method = "andrews")
```

Now copy the series and push the final observation away by five standard
deviations of the observation noise.

```{r break}
y_break <- y_stable
y_break[n] <- y_break[n] + 5
gmm_eos_test(prior, dynamics, measurement, y_break, m = 1L, method = "andrews")
```

The break inflates the statistic and the test rejects. The picture shows why:
the broken point falls far outside the band the filter predicts for it.

```{r figure, fig.alt = "A local-level series with its final observation pushed five standard deviations away; the broken point sits far outside the path of the underlying state."}
plot(y_stable, type = "l", col = "grey60", xlab = "time", ylab = "observation",
     main = "End-of-sample break in the last observation")
lines(level, col = "#1b7837", lwd = 2)
points(n, y_break[n], pch = 19, col = "#b2182b")
legend("topleft", c("observed (stable)", "underlying state", "broken final point"),
       col = c("grey60", "#1b7837", "#b2182b"), lwd = c(1, 2, NA),
       pch = c(NA, NA, 19), bty = "n")
```

# Robustness to non-Gaussian noise

The two calibrations agree when the innovations are Gaussian. They part company
when the observation noise is heavy-tailed: the parametric $\chi^2$ reference is
then mis-specified, while the subsampling test, which judges the end-of-sample
block only against the other blocks of the same series, keeps its size. The
`method = "andrews"` option is the one to reach for when normality is in doubt.

# Scope

The test is defined for a fitted linear-Gaussian state-space model, supplied
exactly as for `gmm_filter()`: a single-component `gmm` prior, a `dynamics` list
with the state-transition and process-noise matrices, and a `measurement` list
with the observation and observation-noise matrices. The small-`m` regime
(`m = 1, 2, 3`) is the point of the test, and `m` must be smaller than the
series length.

# Reference

Andrews, D. W. K. (2003). End-of-Sample Instability Tests. *Econometrica*,
71(6), 1661--1694.
