SpectralDensityResult#

class cultivars.spectral.density.SpectralDensityResult(*, names, frequencies, density)[source]#

A fitted system’s spectral density matrix, and the readings off it.

The matrix is the closed form

\[f(\omega) = \frac{1}{2\pi}\, \Psi(\omega)\, \Sigma_u\, \Psi(\omega)^\ast, \qquad \Psi(\omega) = \Bigl[I - \sum_{l=1}^{p} A_l e^{-i\omega l}\Bigr]^{-1},\]

evaluated on a grid; every reading here is a function of its entries. All frequencies are radians per observation on [0, pi]; the process is real, so the negative half-circle is the conjugate mirror. The density carries the \(1 / 2\pi\) normalization: integrating it over \([-\pi, \pi]\) returns the model’s unconditional autocovariance at lag zero, so twice the integral of a diagonal entry over \([0, \pi]\) is that variable’s variance.

Variables:
  • names – Variable labels, indexing the matrix axes.

  • frequencies – The (n,) grid on [0, pi].

  • density – The (n, k, k) complex Hermitian density matrix.

Note

Every number is a statement about the fitted model, not about the data: the density inherits whatever the time-domain fit got wrong, and a spectral peak here is where the model puts variance. A near-integrated system has a density that diverges at frequency zero, legitimately, and the summary’s peak search skips that point for that reason. The grid is uniform, so the long periods are coarsely resolved: with 256 points on \([0, \pi]\) the spacing is about \(\pi / 255\), which at the business-cycle end (periods 6 to 32) is twenty or so points and at periods above 100 only two or three.

See also

References

Hamilton, J. D. (1994). Time Series Analysis, chapter 6. Princeton University Press.

Priestley, M. B. (1981). Spectral Analysis and Time Series. Academic Press.

Example

A rate that feeds into output one period ahead; the spectrum integrates to the model’s variance, the two are coherent at low frequencies, and the phase says the rate leads:

>>> import numpy as np
>>> from cultivars.multivariate.reduced_form.vector_autoregression import VAR
>>> rng = np.random.default_rng(0)
>>> y = np.zeros((500, 2))
>>> for t in range(1, 500):
...     y[t, 0] = 0.5 * y[t - 1, 0] + 0.4 * y[t - 1, 1] + rng.standard_normal()
...     y[t, 1] = 0.5 * y[t - 1, 1] + rng.standard_normal()
>>> res = VAR(y, order=1, names=("gdp", "rate")).fit()
>>> density = SpectralDensity(res, n_frequencies=64).compute()
>>> density.density.shape, density.k_endog
((64, 2, 2), 2)
>>> variance = 2.0 * np.trapezoid(density.spectrum("gdp"), density.frequencies)
>>> bool(abs(variance - y[:, 0].var()) < 0.05)
True
>>> density.coherence("gdp", "rate")[[1, -1]].round(2)
array([0.35, 0.07])
>>> bool(density.phase("gdp", "rate")[1] < 0.0)
True

Attributes#

names

Variable labels, indexing both matrix axes of density.

frequencies

(n,) uniform grid from 0 to \(\pi\), endpoints included.

density

(n, k, k) complex Hermitian density, \(\Psi \Sigma_u \Psi^\ast / 2\pi\).

k_endog

Number of variables, \(k\), the size of each matrix axis.

Methods#

coherence(first, second)

Squared coherence between two variables, in [0, 1].

gain(effect, cause)

The gain of the regression of effect on cause by frequency.

partial_coherence(first, second)

Squared partial coherence, conditioning on all other variables.

periods()

Each grid frequency as a period, in observations per cycle.

phase(effect, cause)

The phase of the cross-spectrum f[effect, cause], in radians.

spectrum(name)

One variable's power spectrum.

summary()

The estimation summary, renderable as text, HTML, or a dataframe.