StudentT#
- class impulso.observation.StudentT(*, name='student_t', is_heavy_tailed=True, nu='infer', prior_alpha=2.0, prior_beta=0.1)[source]#
Bases:
ImpulsoModelStudent-t observation errors — y_t ~ MvT_nu(μ_t, Ω).
Heavy-tailed innovations for samples with outliers (crises, pandemic quarters, data revisions). Estimation stays fully Bayesian: the t likelihood downweights extreme observations automatically rather than requiring them to be dummied out by hand, because the t score with respect to the location is bounded and redescending — ∂ log p/∂μ = w · Ω⁻¹(y - μ) with w = (nu + n)/(nu + q) and q the squared Mahalanobis distance, so w → 0 as an observation moves far into the tail.
Ω = L Lᵀ is the scale matrix, not the covariance: E[y_t] = μ_t (nu > 1) and Cov[y_t] = nu/(nu-2) · Ω (nu > 2). The consequences of that convention — which downstream quantities change and which are exactly invariant — are set out in docs/adr/0007-student-t-errors-use-the-scale-matrix-convention.md.
- Parameters:
- name#
Discriminator key for the registry (always “student_t”).
- Type:
Literal[‘student_t’]
- nu#
Degrees of freedom. A float strictly greater than 2 fixes nu; the default “infer” estimates it from the data. Small values mean fat tails (nu ≈ 4 is aggressively robust); large values approach the Gaussian.
- Type:
float | Literal[‘infer’]
- prior_alpha#
Shape of the Gamma prior on the excess degrees of freedom nu - 2. Must be strictly greater than 1 so the prior density vanishes at the origin — see PRIOR_ALPHA_LOWER. Only used when nu=”infer”.
- Type:
Expand for references to
impulso.observation.StudentT- NU_LOWER: ClassVar[float] = 2.0#
below it the t has infinite variance, which would make innovation_covariance, density-forecast bands, and variance decompositions meaningless. Users who genuinely want infinite-variance innovations should build the PyMC model directly.
- Type:
Hard lower bound on nu. Not configurable
- PRIOR_ALPHA_LOWER: ClassVar[float] = 1.0#
Hard lower bound on prior_alpha. Gamma(alpha, ·) has zero density at the origin only for alpha > 1: at alpha = 1 the density there is the rate itself and for alpha < 1 it is unbounded. Since the prior on nu is that Gamma shifted by NU_LOWER, any alpha <= 1 piles mass against the nu = 2 infinite-variance boundary the shift exists to avoid.
- build_likelihood(name, mu, chol, observed, dims=None)[source]#
Register the multivariate Student-t likelihood in the active PyMC model.
Registers nu as a Deterministic named “nu” in both modes, so the posterior always carries the degrees of freedom and downstream code never has to know whether they were fixed or inferred. When nu=”infer”, the free random variable is the excess degrees of freedom nu_excess ~ Gamma(prior_alpha, prior_beta) and nu = 2 + nu_excess. The shift (rather than a truncation) is deliberate: Gamma(alpha, ·) has zero density at the origin for any alpha > 1 — which the validator enforces — so the prior vanishes exactly where nu/(nu-2) blows up, and the unconstrained NUTS transform is the standard positive-support one.
chol is passed straight through, so the manual Cholesky parameterisation the volatility seam builds carries over verbatim (pm.MvStudentT shares quaddist_matrix with pm.MvNormal).
- Parameters:
name (str) – Name for the observed random variable (the pipeline uses “obs”).
mu (Any) – Conditional mean tensor, shape (T, n_vars).
chol (Any) – Lower-triangular Cholesky factor of the scale matrix Ω.
observed (ndarray) – Observed endogenous matrix, shape (T, n_vars).
dims (tuple[str, ...] | None) – PyMC dims for the observed variable.
- Returns:
The registered PyMC random variable.
- Return type:
- draw_standardised_innovations(shape, rng, posterior)[source]#
Draw standardised multivariate-t innovations ξ.
Reproduces PyMC’s own MvStudentT construction: ξ = z / sqrt(g / nu) with z ~ N(0, I) and g ~ χ²_nu drawn once per observation vector, not once per component. The shared mixing variable is what makes the components dependent (though uncorrelated) and the joint law genuinely multivariate-t; drawing one χ² per component would give a product of independent t marginals instead.
rng.standard_normal is consumed first, before the χ² draw, so the Gaussian adapter’s stream stays a strict prefix of this one and seeded Gaussian forecasts are unaffected by the seam.
nu is read per posterior draw, so a model with inferred degrees of freedom yields innovations whose tail weight varies across draws.
- Parameters:
- Returns:
Standardised t innovations of shape shape, with E[ξ] = 0 and Cov[ξ] = nu/(nu-2) · I. Each marginal is exactly a standard t_nu.
- Raises:
ValueError – If the posterior carries no nu, or if its shape does not match the leading (chains, draws) of shape.
- Return type:
- model_config = {'frozen': True}#
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- variance_inflation(posterior)[source]#
Factor converting the scale matrix Ω into the innovation covariance.
nu/(nu-2), evaluated per posterior draw. Finite by construction: nu is bounded below by 2 both by the validator (fixed mode) and by the shifted Gamma prior (inferred mode).
- Parameters:
posterior (xr.Dataset) – Posterior Dataset carrying nu with shape (chains, draws).
- Returns:
Array of shape (chains, draws).
- Raises:
ValueError – If the posterior carries no nu.
- Return type: