canova_hansen#

cultivars.diagnostics.seasonality.canova_hansen(endog, *, period, trend='c', lags=0, bandwidth=None, n_draws=5000, grid=2000, seed=None)[source]#

Canova-Hansen test of stationary seasonality against a seasonal unit root.

The series is regressed on the trend terms, \(s - 1\) trigonometric seasonal regressors, and lags lags of itself; under the null the seasonal coefficients are constant, under the alternative those at the frequency tested follow a random walk, and the Lagrange multiplier statistic for that alternative is

\[L = T^{-2} \sum_{t = 1}^{T} \hat F_t^\top\, \hat\Omega^{-1}\, \hat F_t, \qquad \hat F_t = \sum_{i \le t} f_i\, \hat e_i,\]

with \(f_i\) the seasonal columns of the frequency under test, \(\hat e_i\) the residuals, and \(\hat\Omega\) the Bartlett long-run covariance of the scores. Its limit is the generalized von Mises law, the integral of a squared \(q\)-dimensional Brownian bridge, with \(q\) the number of columns tested: one at the Nyquist frequency, two at each harmonic, \(s - 1\) jointly. The p-values come from n_draws simulated bridges, whose 5% point at \(q = 1\) is KPSS’s 0.463. Rejection lies in the upper tail. The joint test is the headline and the per-frequency tests say where the instability sits.

Parameters:
  • endog (ArrayLike) – The series, (T,), at least a few full cycles long.

  • period (int) – Seasonal period \(s\), even and at least 2.

  • trend (str) – "n", "c" or "ct".

  • lags (int) – Lags of the series among the regressors, at least 0, which absorb short-run dynamics that would otherwise inflate the statistic.

  • bandwidth (int | None) – Bartlett lags of the long-run covariance, at least 0; default Newey-West’s \(\lfloor 4 (T / 100)^{2/9} \rfloor\).

  • n_draws (int) – Simulated bridges behind each p-value, at least 500.

  • grid (int) – Points on which each bridge is simulated, at least 100.

  • seed (int | Generator | None) – Seed or generator for the simulation.

Return type:

SeasonalUnitRootTest

Returns:

The joint SeasonalUnitRootTest, with the Nyquist and each harmonic frequency as companions, in that order; its lags field is the kernel bandwidth and its method names the kernel and the lag count.

Raises:
  • SpecificationError – If the period is odd or below 2, the trend, lags, bandwidth, or simulation sizes are unusable, or the series is too short.

  • NumericalError – If a value is not finite or a long-run covariance is singular.

Note

The test is the seasonal analogue of KPSS and inherits its habits: the long-run variance is estimated under the null, so a bandwidth too short for the residual autocorrelation over-rejects and one too long loses power, and a series with a unit root at frequency zero should be differenced first, since the statistic is for the seasonal frequencies alone. Lags of the series are the paper’s remedy for short-run dynamics and are preferred to a wider bandwidth.

See also

  • hegy() – the same frequencies with a unit root under the null; read the two together.

  • kpss() – the zero-frequency test this one generalizes.

  • SeasonalUnitRootTest – the record.

References

Canova, F., & Hansen, B. E. (1995). Are seasonal patterns constant over time? A test for seasonal stability. Journal of Business & Economic Statistics, 13(3), 237-252.

Kwiatkowski, D., Phillips, P. C. B., Schmidt, P., & Shin, Y. (1992). Testing the null hypothesis of stationarity against the alternative of a unit root. Journal of Econometrics, 54(1-3), 159-178.

Busetti, F., & Harvey, A. (2003). Seasonality tests. Journal of Business & Economic Statistics, 21(3), 420-436.

Example

A fixed quarterly cycle keeps stationary seasonality; a seasonal random walk rejects it, jointly and at each frequency:

>>> import numpy as np
>>> rng = np.random.default_rng(0)
>>> t = np.arange(300)
>>> stable = np.cos(np.pi * t / 2) + rng.standard_normal(300)
>>> verdict = canova_hansen(stable, period=4, seed=0)
>>> verdict.reject(), verdict.method
(False, 'Bartlett kernel, 0 lag(s) of y')
>>> y = rng.standard_normal(200)
>>> for s in range(4, 200):
...     y[s] = y[s - 4] + rng.standard_normal()
>>> verdict = canova_hansen(y, period=4, seed=0)
>>> verdict.reject(), [c.reject() for c in verdict.companions]
(True, [True, True])