SetIdentifiedSVARResult.irf#

SetIdentifiedSVARResult.irf(horizon=20, *, cumulative=False)[source]#

Exact pointwise response bounds to the restricted shock.

Parameters:
  • horizon (int) – Largest lead to return.

  • cumulative (bool) – Bound the running sums instead, for differenced data and level questions – the cumulated response is also linear in the rotation column, so the bounds stay exact.

Return type:

NDArray[double]

Returns:

An array of shape (horizon + 1, k, 2); entry [h, i] is the (lower, upper) pair for variable i’s response at lead h to one standard deviation of the restricted shock. Read the bounds pointwise: no single admissible rotation traces an edge of the box.

Raises:

NumericalError – If no rotation column satisfies the restrictions – unreachable after a successful identify(), which has already evaluated the impact bounds.

Example

The true impact column lies inside the bounds at every lead, and the cumulative bound at lead one is at most the sum of the two pointwise upper bounds, because one rotation need not attain both:

>>> import numpy as np
>>> from cultivars.multivariate.reduced_form.vector_autoregression import VAR
>>> rng = np.random.default_rng(0)
>>> B = np.array([[1.0, 0.5], [0.3, 1.0]])
>>> A = np.array([[0.5, 0.1], [0.0, 0.4]])
>>> eps = rng.standard_normal((402, 2))
>>> y = np.zeros((402, 2))
>>> for t in range(1, 402):
...     y[t] = A @ y[t - 1] + B @ eps[t]
>>> res = VAR(y, order=1, names=("gdp", "infl")).fit()
>>> sset = SetIdentifiedSVAR(res, {"gdp": "+", "infl": "+"}, horizons=(0, 1)).identify()
>>> bounds = sset.irf(4)
>>> bounds.shape
(5, 2, 2)
>>> bounds[1].round(3).tolist()
[[0.002, 0.636], [0.059, 0.361]]
>>> true = np.einsum("hik,k->hi", res.ma_representation(4), B[:, 0])
>>> bool(np.all((bounds[..., 0] <= true) & (true <= bounds[..., 1])))
True
>>> running = sset.irf(1, cumulative=True)
>>> bool(np.all(running[1, :, 1] <= bounds[0, :, 1] + bounds[1, :, 1] + 1e-12))
True