cultivars.multivariate.large_dim.factor_augmented#

The factor-augmented VAR: a few shocks, answered by hundreds of series.

A small VAR cannot say what a policy shock does to the hundreds of series a policymaker actually watches, and a VAR on hundreds of series cannot be estimated. The factor-augmented VAR threads the needle (Bernanke, Boivin and Eliasz, 2005): a wide informational panel is summarized by a handful of principal-component factors, a VAR runs on those factors augmented with the observed variables of interest, and an observation equation maps everything the small system produces back onto every series,

\[\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}\]

so a shock identified in the \(r + m\) dimensional system has a response on all \(N\) panel series through \(\Lambda\).

The design is composition, on the pattern the functional VAR set: the factor dynamics are a VAR on the augmented block, riding on the result as factors with its entire reduced-form surface intact. Because that result is a closed system, the entire structural layer applies to it unchanged – recursive, sign, narrative, proxy, heteroskedasticity, any of them – and FactorAugmentedSVAR translates whatever was identified into responses of all \(N\) panel series. That translation is rotation-invariant: principal components recover the factor space only up to rotation, but loadings and factors rotate together, so panel responses to an identified shock do not depend on the arbitrary factor basis.

Two commitments shape the surface. First, the estimation subtlety that is load-bearing for policy analysis is handled the way the original paper handles it, and its absence is disclosed. Principal components of the full panel absorb the observed variables’ contemporaneous influence, which contaminates a recursive identification that orders those variables last. Declaring the slow series – those that do not react within the period – triggers the Bernanke-Boivin-Eliasz cleaning, \(\hat F_t = C_t - \hat b_Y' Y_t\) with \(\hat b_Y\) from the regression of the full-panel components on the slow-panel components and \(Y_t\); skipping the declaration puts a warning on the summary rather than papering over it. Second, nothing structural happens here. The result is reduced-form – a VAR, loadings and a fit – and every identifying assumption is made explicitly, afterwards, in cultivars.multivariate.structural, where it is named and auditable.

Layout. FAVAR validates the panel through _validate_wide_panel and the observed block through validate_exog_matrix from _core, standardizes, extracts with principal_components, cleans by least squares, and fits the scores with VAR. FAVARResult carries the loadings, the scores and the standardization, and takes the summary from _SummaryMixin. The factor model without an observed block is dynamic_factor; the identification step is factor_augmented.

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. (2002). Forecasting using principal components from a large number of predictors. Journal of the American Statistical Association, 97(460), 1167-1179.

Example

A panel whose fast half reacts to the policy shock within the period and whose slow half does not. With the slow series declared, a recursive identification ordering the rate last recovers that structure in the impact responses of the panel:

>>> import numpy as np
>>> from cultivars.multivariate.structural.factor_augmented import FactorAugmentedSVAR
>>> rng = np.random.default_rng(0)
>>> n = 400
>>> factor = np.zeros(n)
>>> for t in range(1, n):
...     factor[t] = 0.7 * factor[t - 1] + rng.standard_normal()
>>> shock = rng.standard_normal(n)
>>> policy = 0.5 * factor + shock
>>> lam = rng.uniform(0.5, 1.5, 30)
>>> slow_half = np.outer(factor, lam[:15]) + 0.5 * rng.standard_normal((n, 15))
>>> fast_half = np.outer(factor, lam[15:]) + 0.5 * rng.standard_normal((n, 15))
>>> fast_half += 0.8 * np.outer(shock, rng.uniform(0.5, 1.5, 15))
>>> panel = np.column_stack([slow_half, fast_half])
>>> names = tuple(f"s{i}" for i in range(15)) + tuple(f"q{i}" for i in range(15))
>>> res = FAVAR(
...     panel, policy, n_factors=1, order=1, panel_names=names,
...     slow=names[:15], observed_names=("rate",),
... ).fit()
>>> res.cleaned, res.factors.names
(True, ('f1', 'rate'))
>>> svar = FactorAugmentedSVAR(res).identify()
>>> svar.shock_names, svar.irf(8).shape
(('f1', 'rate'), (9, 30, 2))
>>> impact = np.abs(svar.impact[:, 1])
>>> bool(impact[15:].mean() > 10 * impact[:15].mean())
True

Classes#

FAVAR

Factor-augmented VAR over a wide informational panel, BBE (2005).

FAVARResult

A fitted factor-augmented vector autoregression.