cultivars.multivariate.reduced_form.panel#
The panel vector autoregression: one set of dynamics, many units, pooled by least squares.
A panel VAR estimates
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#
Panel vector autoregression with pooled slopes and unit fixed effects. |
|
A fitted panel vector autoregression with pooled slopes. |