ConditionalForecastResult#

class cultivars.forecast.conditional.ConditionalForecastResult(*, names, steps, conditions, unconditional, mean, paths, implied_shocks, modesty, modesty_pvalue, n_conditions, source, parameter_uncertainty)[source]#

A forecast of the whole system given stated future paths of some of it.

Waggoner and Zha (1999): the future is written in moving-average form,

\[y_{T+h} = \mu_h + \sum_{j=0}^{h-1} \Psi_j P\, \varepsilon_{T+h-j}, \qquad \varepsilon_t \sim \mathcal{N}(0, I_k),\]

with \(P\) the lower Cholesky factor of the innovation covariance, so that each conditioned cell is one linear restriction on the stacked future shocks, \(R \varepsilon = r\), and the forecast is the system’s law given those restrictions: the minimum-norm shock path that delivers the conditions at the center,

\[\varepsilon^\ast = R^\top (R R^\top)^{-1} r,\]

and the conditional Gaussian of the remaining shock directions around it, \(\varepsilon \mid R\varepsilon = r \sim \mathcal{N}\bigl( \varepsilon^\ast,\ I - R^\top (R R^\top)^{-1} R\bigr)\). Every conditioned cell is met exactly on every path; the free cells spread over what the conditions leave undetermined. Under a sampled result every retained parameter draw contributes its own conditional paths, so the bands carry parameter uncertainty as well.

The record also says whether the scenario is one the model believes. Under the model \(\|\varepsilon^\ast\|^2\) is \(\chi^2\) on the number of conditions \(q\), so modesty \(= \|\varepsilon^\ast\|^2 / q\) is one when the scenario needs shocks of typical size and large when it needs shocks the model deems improbable (Leeper & Zha, 2003); the paths of a scenario like that describe a future agents would notice was engineered.

Variables:
  • names – Series labels.

  • steps – Horizons ahead.

  • conditions – (steps, k) conditioned values, nan where free.

  • unconditional – (steps, k) mean path with no conditions imposed (averaged over draws under a sampled result).

  • mean – (steps, k) conditional mean path.

  • paths – (S, steps, k) conditional predictive paths; conditioned cells equal their conditions on every path.

  • implied_shocks – (steps, k) minimum-norm standardized shocks that deliver the conditions, in the orthogonalized (impact matrix) basis; averaged over draws under a sampled result.

  • modesty – ||eps*||**2 of the minimum-norm shocks relative to the number of conditions – one when the scenario needs shocks of typical size, large when it needs shocks the model deems improbable (Leeper & Zha, 2003).

  • modesty_pvalue – Upper-tail chi-squared probability of ||eps*||**2 on n_conditions degrees of freedom.

  • n_conditions – Cells conditioned.

  • source – The result forecast.

  • parameter_uncertainty – Whether the paths mix over parameter draws.

Note

Conditions are hard: each conditioned cell is met exactly, so the 16-84% band of a conditioned cell is degenerate at the condition. Interval (soft) conditions, which would widen it, are not implemented. The record satisfies the PredictiveResult protocol through forecast_paths(), so a fan chart, a density score, or a model combination reads it like any other density forecaster, except that the horizon is fixed at construction.

See also

References

Waggoner, D. F., & Zha, T. (1999). Conditional forecasts in dynamic multivariate models. Review of Economics and Statistics, 81(4), 639-651.

Leeper, E. M., & Zha, T. (2003). Modest policy interventions. Journal of Monetary Economics, 50(8), 1673-1700.

Example

A two-variable VAR with the rate held at one for two quarters:

>>> import numpy as np
>>> from cultivars.multivariate.reduced_form.vector_autoregression import VAR
>>> rng = np.random.default_rng(0)
>>> y = np.zeros((200, 2))
>>> for t in range(1, 200):
...     y[t] = np.array([[0.5, 0.2], [0.1, 0.6]]) @ y[t - 1] + rng.standard_normal(2)
>>> res = VAR(y, order=1, names=("gdp", "rate")).fit()
>>> scenario = conditional_forecast(res, 4, {"rate": [1.0, 1.0, None, None]}, seed=0)
>>> scenario.n_conditions, scenario.paths.shape, scenario.parameter_uncertainty
(2, (1000, 4, 2), False)
>>> scenario.conditioned[:, 1]
array([ True,  True, False, False])
>>> bool(np.allclose(scenario.paths[:, :2, 1], 1.0))
True
>>> round(scenario.modesty, 2), round(scenario.modesty_pvalue, 3)
(1.38, 0.251)

The same model asked to hold the rate at eight, a scenario it finds implausible:

>>> ambitious = conditional_forecast(res, 4, {"rate": [8.0] * 4}, seed=0)
>>> round(ambitious.modesty, 1), bool(ambitious.modesty_pvalue < 1e-10)
(25.8, True)

Attributes#

names

Series labels, in the result's column order.

steps

Horizons ahead, \(H\); the horizon the paths were simulated for.

conditions

(steps, k) conditioned values, nan where a cell is free.

unconditional

(steps, k) mean path with no conditions imposed, \(\mu_h\).

mean

(steps, k) conditional mean path, paths.mean(axis=0).

paths

(S, steps, k) conditional predictive paths.

implied_shocks

(steps, k) minimum-norm standardized shocks \(\varepsilon^\ast\).

modesty

\(\|\varepsilon^\ast\|^2 / q\), the shock norm per condition.

modesty_pvalue

Upper-tail \(\chi^2_q\) probability of \(\|\varepsilon^\ast\|^2\).

n_conditions

Cells conditioned, \(q\), the count of finite entries in conditions.

source

The class name of the result forecast, for the summary title.

parameter_uncertainty

Whether the paths mix over parameter draws (a sampled result) or hold them fixed.

conditioned

(steps, k) mask of conditioned cells, ~isnan(conditions).

k_endog

Series forecast, \(k\), the last axis of every array.

n_draws

Conditional paths, \(S\), the first axis of paths.

Methods#

forecast_paths([steps, seed])

The conditional paths, in the shape every scorer and fan chart reads.

quantiles([levels])

(len(levels), steps, k) pointwise quantiles of the conditional paths.

summary()

The estimation summary, renderable as text, HTML, or a dataframe.