Cycle#

class pymc_extras.statespace.models.structural.Cycle(name: str | None = None, cycle_length: int | None = None, estimate_cycle_length: bool = False, dampen: bool = False, innovations: bool = True, observed_state_names: list[str] | None = None, share_states: bool = False)[source]#

A component for modeling longer-term cyclical effects

Supports both univariate and multivariate time series. For multivariate time series, each endogenous variable gets its own independent cycle component with separate cosine/sine states and optional variable-specific innovation variances.

Parameters:
  • name (str) – Name of the component. Used in generated coordinates and state names. If None, a descriptive name will be used.

  • cycle_length (int, optional) – The length of the cycle, in the calendar units of your data. For example, if your data is monthly, and you want to model a 12-month cycle, use cycle_length=12. You cannot specify both cycle_length and estimate_cycle_length.

  • estimate_cycle_length (bool, default False) – Whether to estimate the cycle length. If True, an additional parameter, cycle_length will be added to the model. You cannot specify both cycle_length and estimate_cycle_length.

  • dampen (bool, default False) – Whether to dampen the cycle by multiplying by a dampening factor \(\rho\) at every timestep. If true, an additional parameter, dampening_factor will be added to the model.

  • innovations (bool, default True) – Whether to include stochastic innovations in the strength of the seasonal effect. If True, an additional parameter, sigma_{name} will be added to the model. For multivariate time series, this is a vector (variable-specific innovation variances).

  • observed_state_names (list[str], optional) – Names of the observed state variables. For univariate time series, defaults to ["data"]. For multivariate time series, specify a list of names for each endogenous variable.

  • share_states (bool, default False) – Whether latent states are shared across the observed states. If True, there will be only one set of latent states, which are observed by all observed states. If False, each observed state has its own set of latent states. This argument has no effect if k_endog is 1.

Notes

The cycle component is very similar in implementation to the frequency domain seasonal component, expect that it is restricted to n=1. The cycle component can be expressed:

\[\begin{split}\begin{align} \gamma_t &= \rho \gamma_{t-1} \cos \lambda + \rho \gamma_{t-1}^\star \sin \lambda + \omega_{t} \\ \gamma_{t}^\star &= -\rho \gamma_{t-1} \sin \lambda + \rho \gamma_{t-1}^\star \cos \lambda + \omega_{t}^\star \\ \lambda &= \frac{2\pi}{s} \end{align}\end{split}\]

Where \(s\) is the cycle_length. [1] recommend that this component be used for longer term cyclical effects, such as business cycles, and that the seasonal component be used for shorter term effects, such as weekly or monthly seasonality.

Unlike a FrequencySeasonality component, the length of a Cycle can be estimated.

Multivariate Support: For multivariate time series with k endogenous variables, the component creates: - 2k states (cosine and sine components for each variable) - Block diagonal transition and selection matrices - Variable-specific innovation variances (optional) - Proper parameter shapes: (k, 2) for initial states, (k,) for innovation variances

Examples

Univariate Example: Estimate a business cycle with length between 6 and 12 years:

from pymc_extras.statespace import structural as st
import pymc as pm
import pytensor.tensor as pt
import pandas as pd
import numpy as np

data = np.random.normal(size=(100, 1))

# Build the structural model
grw = st.LevelTrend(order=1, innovations_order=1)
cycle = st.Cycle(
    "business_cycle", cycle_length=12, estimate_cycle_length=False, innovations=True, dampen=True
)
ss_mod = (grw + cycle).build()

# Estimate with PyMC
with pm.Model(coords=ss_mod.coords) as model:
    P0 = pm.Deterministic('P0', pt.eye(ss_mod.k_states), dims=ss_mod.param_dims['P0'])

    initial_level_trend = pm.Normal('initial_level_trend', dims=ss_mod.param_dims['initial_level_trend'])
    sigma_level_trend = pm.HalfNormal('sigma_level_trend', dims=ss_mod.param_dims['sigma_level_trend'])

    business_cycle = pm.Normal("business_cycle", dims=ss_mod.param_dims["business_cycle"])
    dampening = pm.Beta("dampening_factor_business_cycle", 2, 2)
    sigma_cycle = pm.HalfNormal("sigma_business_cycle", sigma=1)

    ss_mod.build_statespace_graph(data)
    idata = pm.sample(
        nuts_sampler="nutpie", nuts_sampler_kwargs={"backend": "JAX", "gradient_backend": "JAX"}
    )

Multivariate Example: Model cycles for multiple economic indicators with variable-specific innovation variances:

# Multivariate cycle component
cycle = st.Cycle(
    name='business_cycle',
    cycle_length=12,
    estimate_cycle_length=False,
    innovations=True,
    dampen=True,
    observed_state_names=['gdp', 'unemployment', 'inflation']
)
ss_mod = cycle.build()

with pm.Model(coords=ss_mod.coords) as model:
    P0 = pm.Deterministic("P0", pt.eye(ss_mod.k_states), dims=ss_mod.param_dims["P0"])
    # Initial states: shape (3, 2) for 3 variables, 2 states each
    business_cycle = pm.Normal('business_cycle', dims=ss_mod.param_dims["business_cycle"])

    # Dampening factor: scalar (shared across variables)
    dampening = pm.Beta("dampening_factor_business_cycle", 2, 2)

    # Innovation variances: shape (3,) for variable-specific variances
    sigma_cycle = pm.HalfNormal(
        "sigma_business_cycle", dims=ss_mod.param_dims["sigma_business_cycle"]
    )

    ss_mod.build_statespace_graph(data)
    idata = pm.sample(
        nuts_sampler="nutpie", nuts_sampler_kwargs={"backend": "JAX", "gradient_backend": "JAX"}
    )

References

__init__(name: str | None = None, cycle_length: int | None = None, estimate_cycle_length: bool = False, dampen: bool = False, innovations: bool = True, observed_state_names: list[str] | None = None, share_states: bool = False)[source]#

Methods

__init__([name, cycle_length, ...])

build([name, filter_type, verbose, mode])

Build a StructuralTimeSeries statespace model from the current component(s)

make_and_register_data(name, shape[, dtype])

Helper function to create a pytensor symbolic variable and register it in the _name_to_data dictionary

make_and_register_variable(name, shape[, dtype])

Helper function to create a pytensor symbolic variable and register it in the _name_to_variable dictionary

make_symbolic_graph()

populate_component_properties()

set_coords()

Set default coordinate specifications.

set_data_info()

Set default data specifications.

set_parameters()

Set component parameter specifications.

set_shocks()

Set default shock specifications based on the number of sources of innovations in the component.

set_states()

Set default state specification based on number of states and endogenous variables in the component.

Attributes

coords

data_names

exog_names

k_endog

k_posdef

k_states

n_timesteps

needs_exog_data

observed_state_names

param_dims

param_info

param_names

shock_names

state_names