VAR#

class impulso.spec.VAR(*, lags, max_lags=None, prior='minnesota', volatility='constant', exog_prior_scale=100.0, error_dist='gaussian')[source]#

Bases: ImpulsoBaseModel

Immutable VAR model specification.

VAR specifies the reduced-form model — lag order, coefficient prior, volatility process, and observation error distribution. Nothing here says which shock is which: structural meaning is layered on afterwards, by applying an identification scheme to the FittedVAR that fit returns.

Parameters:
  • lags (int | Literal['aic', 'bic', 'hq'])

  • max_lags (int | None)

  • prior (Literal['minnesota'] | ~impulso.priors.MinnesotaPrior | ~impulso.protocols.Prior)

  • volatility (Literal['constant', 'sv'] | ~impulso.volatility.Constant | ~impulso.sv.spec.StochasticVolatility | ~impulso.protocols.PyMCVolatilityProcess)

  • exog_prior_scale (Annotated[float, Gt(gt=0)])

  • error_dist (Literal['gaussian', 'student_t'] | ~impulso.observation.Gaussian | ~impulso.observation.StudentT | ~impulso.protocols.ErrorDistribution)

lags#

Fixed lag order (int >= 1) or selection criterion string.

Type:

int | Literal[‘aic’, ‘bic’, ‘hq’]

max_lags#

Upper bound for automatic selection. Only valid with string lags.

Type:

int | None

prior#

Prior shorthand string or Prior protocol instance.

Type:

Literal[‘minnesota’] | impulso.priors.MinnesotaPrior | impulso.protocols.Prior

volatility#

Volatility shorthand string or PyMCVolatilityProcess protocol instance.

Type:

Literal[‘constant’, ‘sv’] | impulso.volatility.Constant | impulso.sv.spec.StochasticVolatility | impulso.protocols.PyMCVolatilityProcess

exog_prior_scale#

Tightness of the prior on the exogenous coefficients B_exog, read in contribution space: one prior standard deviation moves an endogenous variable by this many of its own AR(1) residual standard deviations when the regressor moves by one of its own. The default of 100 is deliberately loose — deterministic and exogenous terms are conventionally left near-uninformative (the conjugate engine uses Vc = 10e6 on the intercept), and the prior’s job here is to stop the scale of the regressor from silently setting the answer, not to shrink. Lower it to shrink B_exog towards zero. Applies only to VAR.fit; prior governs the lag coefficients.

Type:

float

error_dist#

Observation error distribution — shorthand string (“gaussian”, the default, or “student_t”) or an ErrorDistribution protocol instance. The string form takes the adapter’s defaults, so error_dist=”student_t” infers the degrees of freedom; pass StudentT(nu=5.0) to fix them. Heavy- tailed errors are rejected in combination with time-varying volatility. Governs the exogenous block only; prior governs the lag coefficients. Both VAR.fit and VAR.prior_predictive build the same graph, so it applies to either.

Type:

Literal[‘gaussian’, ‘student_t’] | impulso.observation.Gaussian | impulso.observation.StudentT | impulso.protocols.ErrorDistribution

Expand for references to impulso.spec.VAR

The Minnesota Prior / Usage

Writing a Custom Prior / Using your custom prior

Granger Causality and Toda-Yamamoto / Query a fitted model

Granger Causality and Toda-Yamamoto / Toda-Yamamoto for integrated systems / The manual route

Heavy-Tailed Observation Errors / Fitting

Heavy-Tailed Observation Errors / Reading the posterior for nu / Is it worth it? Comparing against the Gaussian fit

Choosing Lag Order / Fixed lag order

Prior and Posterior Predictive Checks / Checking the prior before you fit

Impulso

The conjugate VAR: fast Bayesian estimation / When to reach for ConjugateVAR instead of the NUTS VAR / Scope

Probabilistic Forecasts

The Minnesota Prior, From Scratch

Model Checks and Validation

Identification in structural VARs: Cholesky vs sign restrictions / A case study using U.S. monetary policy data / Impulse Response Function

Oil supply news with an external instrument / How the announcement surprise identifies one shock

Fitting Your First Bayesian VAR

Counterfactuals, conditional forecasts, and structural scenarios / “What if” analysis in the style of Antolín-Díaz, Petrella & Rubio-Ramírez (2021) / The Lucas critique still applies

Stochastic volatility: modelling time-varying uncertainty / Model / Stochastic volatility inside a VAR

Structural Shocks in the Atmosphere

build_in_model(endog, exog, n_lags, endog_names, exog_names=None, endog_scales=None, intercept_equations=None, latent_names=(), latent_init_sigma=1.0)[source]#

Register this VAR specification into the active PyMC model.

The public counterpart of _build_pymc_model: where that wrapper opens a fresh model and converts a VARData into arrays, this method takes the arrays directly and registers the intercept, lag coefficients, (optional) exogenous coefficients, volatility latents and observation likelihood into whichever pymc.Model is active on entry (pymc.modelcontext(None)). _build_pymc_model routes through this method too, so fit and prior_predictive share the same code path with a caller embedding a VAR inside a larger PyMC model — a marketing-mix model with a VAR-shaped baseline, say.

Symbolic endog: endog may be a PyTensor variable instead of a numpy array, e.g. a pm.Data container the caller registered. The observed block’s likelihood is then error_dist.logp wrapped in a pm.Potential named “obs” (a symbolic value cannot be an RV’s observed), with the same density the numpy path’s observed RV contributes. A Potential is not a random variable: it is invisible to both sample_prior_predictive and sample_posterior_predictive, so the symbolic path’s observations cannot be predicted. Pass a plain tensor: for a pymc.dims.Data container, its .values. The numpy-only steps are skipped: endog_scales is required, since ar1_residual_sd needs concrete data, and the volatility process gets data=None instead of OLS pre-fit residuals, which only Constant volatility accepts, as it ignores them. Callers resolve string lag-selection criteria (e.g. via select_lag_order) before calling this method — it always takes a concrete integer n_lags.

Latent series: latent_names declares endogenous series that have no data. They come first in endog_names, and endog holds only the observed columns that follow them. Their paths are generated inside the model, non-centred: latent_init puts a Normal(0, latent_init_sigma) prior on each latent series’ first n_lags values, latent_innovations holds standard-normal innovations z of shape (T - n_lags, n_latent), and for t >= n_lags a scan runs the latent equations of the VAR, latent_t = c + sum_l A_l[lat, :] full_{t-l} + B_exog[lat] x_t + L[lat, lat] z_t, where full stacks the latent path with the observed columns. The path, shape (T, n_latent) including the initial values, is registered as the Deterministic “latent” and returned as handles.latent, its columns ordered like handles.latent_names. Because the latent series lead the Cholesky ordering, the observed block’s likelihood conditional on z is exact: resid_obs - L[obs, lat] z ~ MvN(0, L[obs, obs] L[obs, obs]’), evaluated with error_dist.logp on that sub-block and registered as a pm.Potential named “obs”. This is the non-centred design of prototype/REPORT.md: passing a free latent column as symbolic endog instead gives a funnel in the latent innovation scale. Latent series need Gaussian errors and an endog_scales entry, since they have no data to compute a scale from. latent_init, latent_innovations and latent carry no dims (their time axis has no coordinate), so no new coordinate is registered for them. latent_init_sigma sets only the start of the path; the VAR is the latent series’ only prior after that.

Latent stationarity: an explosive draw of the latent equations’ coefficients makes the generated path explode over the sample and can freeze a chain. Three things guard against it. The pm.Potential “latent_stationarity” is 0 when the spectral radius of the companion matrix of the latent-on-latent block (the latent rows’ coefficients on latent lags, over all n_lags) is below 1 and -inf otherwise. The observed series are data, not generated, so this block alone decides whether the path explodes. B stays a single Normal, so the posterior is its prior truncated to the stationary region of the latent block; with a latent own-lag prior mean near 1 the posterior can press against that boundary and give divergences. The prior sets that mean: MinnesotaPrior(own_lag_mean=…) takes one entry per series, so a caller modelling a stationary deviation passes 0 for the latent series and 1 for the random-walk observed ones. And the model’s initial point puts every latent equation inside the stationary region: own first lag at its prior mean when that is inside the region, otherwise 0.5, and every other coefficient in the latent rows 0. PyMC sets an initial value for B as a whole, so the observed rows start at their prior mean, which is PyMC’s default start for them anyway. PyMC’s jitter+adapt_diag start moves this by up to +-1; a jittered start outside the region has -inf log density, and PyMC redraws it (jitter_max_retries). A Potential is not a random variable, so pm.sample_prior_predictive ignores “latent_stationarity”: prior-predictive latent paths are drawn from the untruncated prior and can still explode.

Nesting: open a pm.Model(name=prefix) before calling this method and every free random variable, Deterministic and the likelihood it registers come out named prefix::… — ordinary PyMC nested- model behaviour (see the “Nested pm.Model(name=prefix)” section of prototype/REPORT.md). Coordinates are the one exception: PyMC does not prefix coords, so add_coords below always lands on the root model, shared by every nested submodel. Embed at most one VAR’s variable labelling per model — two VARs with different endog_names/exog_names embedded in the same model will collide on var/coeff/exog (identical labels are shared silently; different labels raise ValueError). “time” is a coordinate too, so it is subject to the same sharing: two VARs embedded in the same model must agree on its length (see “Time coordinate” below) — checked explicitly, because add_coords alone only rejects a duplicate coordinate whose values differ, not one whose length happens to differ while its (unlabelled) content still matches.

Time coordinate: the likelihood is registered with dims=(“time”, “var”), and PyMC requires an observed multivariate RV’s named dims to already be coordinates on the model — unlike a free RV, it will not silently auto-register them. If the active model does not already carry a “time” coordinate, this method adds a plain positional one (range(T_eff)). _build_pymc_model pre-registers “time” from data.index before calling this method, so fit and prior_predictive keep real dates; a caller invoking this method directly gets the positional fallback unless it registers “time” itself first. If the active model already carries a “time” coordinate — this VAR’s own wrapper, or a second VAR embedded in the same model — and its length does not match this call’s number of likelihood rows (T - n_lags), this method raises ValueError rather than silently reusing the wrong length; equal length is fine regardless of the actual values. A symbolic endog is handled the same way when its static shape knows T. When it does not, as for pm.Data or pytensor.shared, whose value can be swapped for another length, no “time” coordinate is registered or checked: the pm.Potential likelihood carries no dims.

Parameters:
  • endog (np.ndarray | pt.TensorVariable) – Endogenous data, shape (T, n_vars): a numpy array, or a 2-D PyTensor variable (see “Symbolic endog” above). With latent series, only the observed columns, shape (T, n_vars - n_latent).

  • exog (ndarray | None) – Optional exogenous regressors, shape (T, n_exog). None if the model has no exogenous block.

  • n_lags (int) – Lag order. Always a concrete integer — resolving a string selection criterion is the caller’s job.

  • endog_names (Sequence[str]) – Names for each endogenous variable, length n_vars, in VAR order: latent series first, then the observed columns of endog. Labels the var/var1/var2/coeff coordinates.

  • exog_names (Sequence[str] | None) – Names for each exogenous column, length n_exog. Required when exog is given; labels the exog coordinate.

  • endog_scales (ndarray | Sequence[float] | None) – Per-variable scale sigma, shape (n_vars,) — an array or any array-like (e.g. a plain list) accepted by np.asarray(…, dtype=float). None (the default) computes it from endog with ar1_residual_sd; required when endog is symbolic or there are latent series, and then covering every name in endog_names. The same array feeds both the prior’s Minnesota cross-lag scaling sigma_i / sigma_j (Prior.build_priors, docs/adr/0015) and the exogenous prior (_exog_prior_sigma, docs/adr/0012), so it matters even when exog is None.

  • intercept_equations (Sequence[str] | None) – Names of the endogenous equations that get an intercept, a subset of endog_names in any order. None (the default) means every equation — the same graph as before this argument existed. An excluded equation has no intercept term at all (its mu adds zero), so its series is modelled as a zero-mean deviation around a level owned elsewhere in the model. Naming every equation, in any order, is the same as None: the intercept keeps dims=”var”. A strict subset gets its own “var_intercept” coordinate, ordered like endog_names (not like this argument), and like every Impulso coordinate it is not prefixed by a nested model. An empty sequence registers no intercept variable, and the returned handles’ intercept is None. Latent series may be excluded, e.g. to model a latent series as a zero-mean deviation.

  • latent_names (Sequence[str]) – Names of the latent endogenous series (see “Latent series” above), which must be the first entries of endog_names, in the same order. Empty (the default) means no latent series and the graph described above for observed data only.

  • latent_init_sigma (float | Sequence[float]) – Standard deviation of the zero-mean Normal prior on each latent series’ first n_lags values: a scalar shared by every latent series, or one entry per latent series. Ignored without latent series.

Returns:

VARModelHandles wrapping the intercept, coefficient, volatility and likelihood variables this call registered.

Raises:
  • ValueError – If the active model already carries a “time” coordinate whose length does not match this call’s number of likelihood rows (T - n_lags) — see “Time coordinate” above.

  • ValueError – If any entry of the scale — computed or supplied via endog_scales — is zero, negative or non-finite.

  • ValueError – If endog_scales does not have shape (n_vars,).

  • TypeError – If endog is a dimmed XTensorVariable rather than a plain tensor — pass its .values.

  • ValueError – If endog is symbolic and endog_scales is None, if it is not 2-D, or if its static column count differs from len(endog_names).

  • ValueError – If intercept_equations names an equation not in endog_names, or names one more than once.

  • ValueError – With latent series: if endog_names does not start with exactly latent_names, if no observed series is left, if endog’s column count is not the number of observed series, if exog has a different number of rows from endog, if endog_scales is missing or has entries for the observed series only, or if latent_init_sigma has the wrong length or a non-positive entry.

  • ValueError – On the embedded path — endog symbolic or latent series present: if self.lags is a selection criterion string (lag selection runs OLS on data), or if self.volatility is not “constant”/Constant (stochastic volatility seeds its priors from OLS residuals). With latent series specifically, also if self.error_dist is not Gaussian (a multivariate Student-t’s conditional is not a Student-t with the same nu). Raised before anything is registered into the model.

Return type:

VARModelHandles

fit(data, sampler=None)[source]#

Estimate the Bayesian VAR model.

Parameters:
  • data (VARData) – VARData instance.

  • sampler (Sampler | None) – Sampler protocol instance. Defaults to _default_sampler() (cores=1, chains=4, target_accept=0.8). Pass an explicit NUTSSampler(cores=n) to opt into parallel chains.

Returns:

FittedVAR with posterior draws.

Return type:

FittedVAR

Expand for references to impulso.spec.VAR.fit

Writing a Custom Prior / Using your custom prior

Granger Causality and Toda-Yamamoto / Query a fitted model

Granger Causality and Toda-Yamamoto / Toda-Yamamoto for integrated systems / The manual route

Heavy-Tailed Observation Errors / Fitting

Heavy-Tailed Observation Errors / Reading the posterior for nu / Is it worth it? Comparing against the Gaussian fit

Impulso

Stochastic volatility: modelling time-varying uncertainty / Model / Stochastic volatility inside a VAR

model_config = {'arbitrary_types_allowed': True, 'frozen': True}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

prior_predictive(data, *, draws=500, random_seed=None)[source]#

Simulate data from the prior, before seeing the likelihood.

Builds the same PyMC graph fit builds and calls pymc.sample_prior_predictive on it, so the prior that gets simulated is exactly the prior that gets sampled — no hand-rolled second implementation to drift out of sync.

The simulated obs paths are one-step-ahead given the observed lags: for each prior draw, y_t = c + B x_t^obs (+ B_exog z_t) + L_t eps_t where x_t^obs stacks the observed lags of data. The design matrices are baked into the graph, so this is the prior predictive of the estimation-sample conditional means, not a simulated path iterated from initial conditions. That is what arviz.plot_ppc(…, group=”prior”) expects and what makes the prior comparable to the data on the same time axis.

Note

Under volatility=”sv” the per-variable log-volatility priors are seeded from the OLS residuals of data (see StochasticVolatility.build_pymc_latent), so the “prior” is mildly data-informed in its scale. The constant-volatility default is not.

Note

PyMC returns a single chain, so the obs variable has shape (1, draws, T - n_lags, n_vars).

Parameters:
  • data (VARData) – VARData instance. Anchors the prior simulation on the real lags (and, if present, the real exogenous regressors), and fixes the lag order when lags is a selection criterion.

  • draws (int) – Number of prior draws.

  • random_seed (int | Generator | None) – Seed or Generator passed straight through to pymc.sample_prior_predictive.

Returns:

InferenceData-schema container with prior (every latent), prior_predictive (the simulated obs, dims (chain, draw, time, var)) and observed_data (the realised obs) groups.

Return type:

InferenceData

Expand for references to impulso.spec.VAR.prior_predictive

Prior and Posterior Predictive Checks / Checking the prior before you fit

property resolved_error_dist: ErrorDistribution#

Resolve string error-distribution shorthand to an ErrorDistribution instance.

property resolved_prior: Prior#

Resolve string prior shorthand to a Prior instance.

property resolved_volatility: PyMCVolatilityProcess#

Resolve string volatility shorthand to a PyMCVolatilityProcess instance.