cultivars.multivariate.structural.factor_augmented#

Structural identification on a factor block: one shock, hundreds of answers.

A factor-augmented VAR is two equations,

\[\begin{split}\begin{pmatrix} F_t \\ Y_t \end{pmatrix} = \sum_{i=1}^{p} A_i \begin{pmatrix} F_{t-i} \\ Y_{t-i} \end{pmatrix} + u_t, \qquad x_t = \Lambda^F F_t + \Lambda^Y Y_t + e_t,\end{split}\]

and identifying it is identifying the first: the factor VAR is a closed reduced form like any other, and any scheme in this package factorizes its \(u_t = B \varepsilon_t\). What earns this row its own model is what happens next. The observation equation maps the identified factor-space responses \(\Psi_h B\) onto every series in the informational panel as \(\Lambda \Psi_h B\), in each series’ own units, which is the entire reason the FAVAR exists – one identification, answered by hundreds of responses – and the map is rotation-invariant, because principal components recover the factor space only up to rotation while loadings and factors rotate together (Bernanke, Boivin and Eliasz 2005).

Two commitments shape the surface. First, the declaration lives in one place. By default the model runs the paper’s own scheme – recursive, with the observed block ordered last, so the policy shock moves no factor within the period (the panel still responds on impact through \(\Lambda^Y\), and the result says so). Any other scheme rides in through the same door: identify the factor VAR with whichever model states the restriction you believe, and hand the result here to be mapped; the model checks by identity that it was built from this FAVAR’s own factor VAR and then only maps it, so the restriction note on the summary is the scheme’s own. Second, the condition the default scheme depends on is disclosed rather than assumed. The recursive ordering is clean only if the factors were purged of the observed block’s contemporaneous influence – the slow-variable cleaning of the reduced form – and a result built on an uncleaned fit carries a contamination note on every summary.

Layout. FactorAugmentedSVAR is an _IdentificationModel from _internals whose source is favar.factors, the VARResult on the augmented block; it delegates the default scheme to RecursiveSVAR and packages the result with the reduced form into FactorAugmentedSVARResult, which owns the observation-equation map through the FAVAR’s loadings and standardization scales. The reduced form is factor_augmented; the dependency runs one way, structural on large-dimensional, never back. The same packaging pattern over regimes is MarkovSwitchingSVAR.

References

Bernanke, B. S., Boivin, J., & Eliasz, P. (2005). Measuring the effects of monetary policy: A factor-augmented vector autoregressive (FAVAR) approach. Quarterly Journal of Economics, 120(1), 387-422.

Stock, J. H., & Watson, M. W. (2016). Dynamic factor models, factor-augmented vector autoregressions, and structural vector autoregressions in macroeconomics. In Handbook of Macroeconomics (Vol. 2A, pp. 415-525). Elsevier.

Example

Thirty indicators on one factor and a policy rate that reacts to it, with half the panel declared slow so the recursive default is clean. The policy shock leaves the factor untouched on impact and still moves the panel through the observed block’s own loading; the factor shock reaches every series with the sign and size of its loading:

>>> import numpy as np
>>> from cultivars.multivariate.large_dim.factor_augmented import FAVAR
>>> rng = np.random.default_rng(0)
>>> factor = np.zeros(300)
>>> for t in range(1, 300):
...     factor[t] = 0.7 * factor[t - 1] + rng.standard_normal()
>>> weights = rng.uniform(0.5, 1.5, 30)
>>> panel = np.outer(factor, weights) + 0.3 * rng.standard_normal((300, 30))
>>> policy = 0.4 * factor + rng.standard_normal(300)
>>> names = [f"x{i + 1}" for i in range(30)]
>>> res = FAVAR(
...     panel, policy[:, None], n_factors=1, order=1,
...     slow=names[:15], panel_names=names, observed_names=("ffr",),
... ).fit()
>>> svar = FactorAugmentedSVAR(res).identify()
>>> res.cleaned, svar.shock_names, svar.irf(12).shape
(True, ('f1', 'ffr'), (13, 30, 2))
>>> float(svar.factor_irf(0)[0][0, 1]), bool(np.abs(svar.impact[:, 1]).max() > 0)
(0.0, True)
>>> bool(abs(np.corrcoef(svar.impact[:, 0], weights)[0, 1]) > 0.99)
True

Classes#

FactorAugmentedSVAR

Structural identification on a factor block, Bernanke-Boivin-Eliasz (2005).

FactorAugmentedSVARResult

An identified factor system, answered at the scale of the panel.