cultivars.spectral#

Frequency-domain views of fitted systems and data, filters, cycle dating, and wavelets.

Everything in the time domain has a reading by frequency, and this package is where that reading is taken. The workflow runs from the data toward a model and back. A nonparametric spectrum says where a series’ variance sits, before any model is fitted; a filter isolates the band that matters – the business cycle, by the Burns-Mitchell convention of six to thirty-two quarters – or removes the trend so that what remains can be dated into peaks and troughs and compared across series for concordance; a fitted closed system’s own spectral density, coherence, phase and Geweke causality then say what the model claims at each frequency, on the same axes as the data’s estimate, so the two can be overlaid and their disagreement read as misspecification where it occurs; and the wavelet transforms add the date axis, for the question of whether a comovement held throughout the sample or only in part of it.

The package has one rule that every module keeps: a number from the data and a number from a model are never the same type. The periodogram estimators return SpectrumEstimate with degrees of freedom and a resolution; the model’s density returns SpectralDensityResult with neither, because it has none – it is a closed form of the fitted parameters and inherits their every error. Both carry the \(1/2\pi\) normalization and the same grid on \([0, \pi]\), so overlaying them is a plot and not a conversion, and each result states its own limits: the rows a filter lost or will revise, the cone of influence, the surrogate level a coherence must beat, the boundary coefficients a wavelet variance drops.

Each module is a leaf: names are imported from the module, not from this package. The filters and estimators take arrays, so any series can be filtered, dated, or estimated; the model-implied views take a fitted closed reduced-form result – a VAR, a VARMA, an error-correction model in levels – and re-estimate nothing.

Layout. periodogram is the nonparametric estimate: MultitaperSpectrum, DaniellSpectrum and WelchSpectrum. density is the model’s: SpectralDensity, with coherence, partial coherence, gain and phase on its result; causality decomposes the same matrix into Geweke’s directed measures, SpectralCausality and its conditional form. filters holds the trend-cycle definitions – HodrickPrescottFilter, ButterworthFilter, HamiltonFilter, BeveridgeNelsonDecomposition – and band_pass the band isolators, BaxterKingFilter and ChristianoFitzgeraldFilter. cycles dates the result, TurningPoints, and measures concordance() between chronologies. wavelets is MODWT and WaveletCoherence. The frequency-domain long-memory estimators that read the spectrum’s behaviour at the origin live with the other diagnostics in long_memory.

Example

An AR(2) with a cycle near 17 observations driving a second series. The spectrum finds the period, the band-pass isolates it, the dating counts its turns, and the fitted model’s density agrees with the data’s estimate and attributes the causality to the first series:

>>> import numpy as np
>>> from cultivars.multivariate.reduced_form.vector_autoregression import VAR
>>> rng = np.random.default_rng(0)
>>> y = np.zeros((400, 2))
>>> for t in range(2, 400):
...     y[t, 0] = 1.5 * y[t - 1, 0] - 0.7 * y[t - 2, 0] + rng.standard_normal()
...     y[t, 1] = 0.5 * y[t - 1, 0] + rng.standard_normal()
>>> est = periodogram.MultitaperSpectrum(y).compute()
>>> power = est.spectrum("y1")
>>> round(float(est.periods()[1 + int(np.argmax(power[1:]))]), 1)
16.7
>>> cycle = band_pass.BaxterKingFilter(low=6, high=32).filter(y[:, 0])
>>> turns = cycles.TurningPoints().date(cycle.cycle[:, 0])
>>> turns.n_cycles, int(np.median(np.diff(turns.peaks)))
(32, 11)
>>> fit = VAR(y, order=2).fit()
>>> model = density.SpectralDensity(fit, n_frequencies=len(est.frequencies)).compute()
>>> agreement = np.corrcoef(np.log(power[1:]), np.log(model.spectrum("y1")[1:]))[0, 1]
>>> bool(agreement > 0.95)
True
>>> geweke = causality.SpectralCausality(fit).compute()
>>> to_second = geweke.band("y1", "y2", low_period=6.0, high_period=32.0)
>>> to_first = geweke.band("y2", "y1", low_period=6.0, high_period=32.0)
>>> bool(to_second > 1.0), round(to_first, 2)
(True, 0.0)

Modules#

band_pass

Band-pass filters: the business-cycle component, with the costs stated.

causality

Geweke's causality by frequency: when one variable's history matters.

cycles

Business-cycle chronology: turning points dated from the data, and how two chronologies agree.

density

The spectral density of a fitted system: the same model, read by frequency.

filters

Trend-cycle filters: four answers to "what is the trend", each with its bill.

periodogram

Nonparametric spectra: the data's own frequency-domain story, model-free.

wavelets

Wavelet analysis: the MODWT multiresolution decomposition and Morlet wavelet coherence.