cultivars.multivariate.reduced_form.panel#

The panel vector autoregression: one set of dynamics, many units, pooled by least squares.

A panel VAR estimates

\[y_{it} = \mu_i + \sum_{l=1}^{p} A_l\, y_{i,t-l} + u_{it}, \qquad u_{it} \sim (0, \Sigma_u), \qquad i = 1, \dots, N,\]

on \(N\) units observed for \(T_i\) periods each, with the autoregressive matrices and the innovation covariance shared across units and only the intercept allowed to differ. The payoff is sample: a system that would be unidentifiable on one short unit is estimated on \(\sum_i (T_i - p)\) rows, and the inherited reduced-form surface – coefficient tables, Granger tests, impulse responses, variance decompositions – comes with it unchanged. The price is the pooling assumption itself, which is strong rather than technical: heterogeneous dynamics estimated as if common do not average to the mean dynamics.

Two commitments shape the surface. First, the unit boundary is respected everywhere. Lags are built inside each unit and stacked afterwards, never the other way round – stacking first would quietly regress each unit’s first observation on the previous unit’s last, which produces a number rather than an error – and the result’s residual matrix carries the unit spans, so every residual-history diagnostic reads within a unit and the forecast is unit-specific by construction: the slopes are pooled but the intercept and the lag history are not, so there is no single path to return and forecast() asks which unit. Second, the biases are named on the summary rather than left for the reader to remember. With unit dummies the estimator is least-squares dummy variables and the lag coefficients carry the Nickell bias, of order \(1/T\) in the shortest unit, downward for a positive own-lag, and unaffected by the number of units; the summary prints the shortest \(T\) next to the warning. Without them, heterogeneous levels load onto the lags and the pooled slopes are biased toward persistence, which on a panel of units with different means can nearly double an own-lag coefficient. The GMM-in-differences estimators that avoid both are not implemented here; this module is the honest least-squares baseline they are compared against.

Layout. PanelVAR is a thin fit() over _PanelVectorAutoRegressionModel in _internals, which validates the panel through validate_panel in _core – a balanced (n_units, nobs, k) array or a sequence of (nobs_i, k) arrays – refuses a pooled constant alongside unit dummies, builds each unit’s deterministic block and lag matrix separately in _design with the trend restarting inside every unit, and stacks them for the base’s least squares and Gaussian moments. PanelVARResult is a full _VectorResult with the summary, comparison, inference and propagation mixins, overriding _sample_blocks so the inference layer never crosses a unit, and adding the per-unit accessors, the unit effects and the unit-specific forecast. The single-unit model is vector_autoregression; the many-unit system that links heterogeneous units instead of pooling them is closed_global.

References

Holtz-Eakin, D., Newey, W., & Rosen, H. S. (1988). Estimating vector autoregressions with panel data. Econometrica, 56(6), 1371-1395.

Nickell, S. (1981). Biases in dynamic models with fixed effects. Econometrica, 49(6), 1417-1426.

Arellano, M., & Bond, S. (1991). Some tests of specification for panel data: Monte Carlo evidence and an application to employment equations. Review of Economic Studies, 58(2), 277-297.

Canova, F., & Ciccarelli, M. (2013). Panel vector autoregressive models: A survey. In VAR Models in Macroeconomics – New Developments and Applications (Advances in Econometrics, Vol. 32, pp. 205-246). Emerald.

Example

Six units of unequal length share one bivariate autoregression around unit means that differ by several standard deviations. The fixed-effects fit recovers the common slopes up to the Nickell bias and forecasts each unit from its own intercept and history; the fit without unit effects reads the level differences as persistence, which is why "unit" is the default:

>>> import numpy as np
>>> rng = np.random.default_rng(0)
>>> A = np.array([[0.5, 0.2], [0.1, 0.4]])
>>> units = []
>>> for i in range(6):
...     mu = rng.normal(0.0, 2.0, size=2)
...     y = np.zeros((80 + 10 * i, 2))
...     y[0] = mu
...     for t in range(1, y.shape[0]):
...         y[t] = mu + A @ (y[t - 1] - mu) + rng.standard_normal(2)
...     units.append(y)
>>> fixed = PanelVAR(units, order=1).fit()
>>> fixed.n_units, fixed.nobs, fixed.min_time_dimension, fixed.is_stable
(6, 624, 80, True)
>>> bool(np.abs(fixed.coefficients[0] - A).max() < 0.1)
True
>>> pooled = PanelVAR(units, order=1, effects="none", trend="c").fit()
>>> bool(np.abs(pooled.coefficients[0] - A).max() > 0.3)
True
>>> fixed.forecast(2, unit="unit4").shape, len(fixed.unit_effects)
((2, 2), 6)

Classes#

PanelVAR

Panel vector autoregression with pooled slopes and unit fixed effects.

PanelVARResult

A fitted panel vector autoregression with pooled slopes.