NSSDNResult.decompose#

NSSDNResult.decompose(endog=None, *, exog=None, time=None, scaled=False)[source]#

Every path of the decomposition, for the fitted sample or a new one.

With no arguments this is the fitted sample’s decomposition, the same arrays the result carries as attributes, laid out as one dictionary. With endog the trained network is run over a new series under teacher forcing from its learned initial state \(h_0\), which is how the paper draws the full-sample figures: training and holdout concatenated, the network never refitted.

Parameters:
  • endog (ArrayLike | None) – A series to run the trained network over under teacher forcing; None returns the fitted sample’s paths. At least two observations.

  • exog (ArrayLike | None) – (len(endog), p) covariates for endog; required when the fit used covariates and refused otherwise.

  • time (ArrayLike | None) – (len(endog),) strictly increasing grid for endog; the integers when None.

  • scaled (bool) – Return the paths in training units (the scale the paper’s figures are drawn in, \([-1, 1]\) under min-max scaling) rather than on the original scale. observed is transformed to match.

Return type:

dict[str, NDArray[double]]

Returns:

Arrays keyed observed, emission, trend, oscillation, amplitude_raw, amplitude, effective_amplitude, frequency, phase, phase_offset, gate, components, state, each with \(r\) rows (the length of the series under the filtered alignment, one fewer under the predicted one) and the column counts of the corresponding attributes.

Raises:
  • DimensionError – If endog is not one-dimensional or too short, or if exog or time is misaligned with it, or if the covariate width differs from the fit’s.

  • NumericalError – If any input contains non-finite values.

  • SpecificationError – If time is not strictly increasing.

Example

The paper’s trend figure is on the training scale:

>>> import numpy as np
>>> y = np.sin(0.2 * np.arange(120.0)) + 5.0
>>> res = NSSDN(y, k=2, state_dim=8, schedule=(Stage(1e-2, 20),), seed=0).fit()
>>> paths = res.decompose(scaled=True)
>>> bool(paths["trend"].max() <= 1.0 + 1e-6), paths["trend"].shape
(True, (120,))

Running over a longer sample does not refit; the first rows reproduce the fitted sample’s paths:

>>> longer = np.concatenate([y, np.sin(0.2 * np.arange(120.0, 150.0)) + 5.0])
>>> out = res.decompose(longer)
>>> out["emission"].shape, bool(np.allclose(out["emission"][:120], res.emission))
((150,), True)