statspai.gmm¶
gmm ¶
Dynamic panel GMM estimators for StatsPAI.
Provides: - Arellano-Bond (1991) first-differenced GMM - Blundell-Bond (1998) system GMM - Arellano-Bond test for serial correlation (AR(1)/AR(2)) - Hansen/Sargan test for overidentifying restrictions
xtabond ¶
xtabond(data: DataFrame, y: str, x: Optional[Sequence[str]] = None, id: str = 'id', time: str = 'time', lags: int = 1, gmm_lags: Tuple[int, Optional[int]] = (2, None), method: str = 'difference', twostep: bool = False, robust: bool = True, alpha: float = 0.05, predetermined: Optional[Sequence[str]] = None, endogenous: Optional[Sequence[str]] = None, predetermined_lags: Optional[Tuple[Optional[int], Optional[int]]] = None, endogenous_lags: Optional[Tuple[Optional[int], Optional[int]]] = None, collapse: bool = False, time_dummies: bool = False, orthogonal: bool = False, cluster: Optional[str] = None, steps: Optional[object] = None, iter_tol: float = 1e-10, iter_maxiter: int = 100, ah_instrument: str = 'levels', constant: Optional[bool] = None) -> CausalResult
Arellano-Bond / Blundell-Bond dynamic panel GMM estimator.
Equivalent to Stata's xtabond / xtabond2.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
Balanced or unbalanced panel in long format. |
required |
y
|
str
|
Dependent variable. |
required |
x
|
list of str
|
Strictly exogenous regressors. Entered in first differences both
as regressors and as their own (standard) instruments. Accepts
Stata lag-operator syntax — |
None
|
id
|
str
|
Unit identifier. |
'id'
|
time
|
str
|
Time period variable. Treated as an ordinal sequence: the sorted distinct values define consecutive periods, so a missing period (gap) is recognised, but non-integer / irregularly-spaced codes are collapsed to their rank order. |
'time'
|
lags
|
int
|
Number of lags of Y to include (ρ₁ Y_{t-1} + ... + ρ_p Y_{t-p}). |
1
|
gmm_lags
|
tuple(min, max)
|
Range of lags of Y (in levels) used as GMM instruments. |
(2, None)
|
method
|
str
|
The extra level moments are an additional assumption (the deviations of the initial conditions from the long-run mean must be uncorrelated with α_i). Report the difference-in-Hansen test for the level instruments before relying on them.
|
'difference'
|
twostep
|
bool
|
Use two-step GMM with the efficient weight matrix. When
|
False
|
robust
|
bool
|
Heteroskedasticity-robust standard errors (Windmeijer-corrected
for two-step). When |
True
|
alpha
|
float
|
Significance level. |
0.05
|
predetermined
|
list of str
|
Regressors that are predetermined (weakly exogenous):
E[x_{is} ε_{it}] = 0 for s ≤ t but not for s > t. Their own lags
from |
None
|
endogenous
|
list of str
|
Regressors correlated with the contemporaneous error. Their lags
from |
None
|
predetermined_lags
|
tuple(min, max)
|
Instrument lag windows for the two classes above. Lags are absolute, i.e. counted from the equation period, which
is |
None
|
endogenous_lags
|
tuple(min, max)
|
Instrument lag windows for the two classes above. Lags are absolute, i.e. counted from the equation period, which
is |
None
|
collapse
|
bool
|
Collapse the block-diagonal instrument sets — one column per lag
distance instead of one per (period, distance) pair. This is
Roodman's (2009) remedy for instrument proliferation: the
uncollapsed count grows as O(T²), which overfits the endogenous
regressor, biases the estimate toward the within estimator, and
pushes the Hansen p-value toward an uninformative 1.0. Matches
|
False
|
orthogonal
|
bool
|
Use Arellano-Bover (1995) forward orthogonal deviations instead of first
differences for the transformed equation: subtract from each
observation the mean of its available future ones, scaled so the
transformed errors stay serially uncorrelated. Prefer it on gappy
panels — first differencing destroys the equations on both sides of
a hole, forward deviations only the one at the hole. Matches
|
False
|
time_dummies
|
bool
|
Add period dummies (first period dropped) as regressors and as their own standard instruments. Roodman (2009) recommends these as a default: they absorb common shocks, which is what makes the no-cross-sectional-dependence assumption behind the moment conditions plausible. |
False
|
cluster
|
str
|
Column to cluster the standard errors on, instead of the panel unit.
The moment conditions are summed within a unit by construction, so
the cluster must be at least as coarse as the unit (industry,
region, cohort) and constant within it; a finer variable raises.
Only the meat of the sandwich re-groups — the one-step weight
|
None
|
steps
|
int or {iterated, cue}
|
Generalises
Passing both |
None
|
iter_tol
|
(float, int)
|
Convergence tolerance on the maximum coefficient change, and the
iteration cap, for |
1e-10
|
iter_maxiter
|
(float, int)
|
Convergence tolerance on the maximum coefficient change, and the
iteration cap, for |
1e-10
|
ah_instrument
|
(levels, differences)
|
Which Anderson-Hsiao instrument to use for |
'levels'
|
constant
|
bool
|
Include an intercept. Defaults to |
None
|
Returns:
| Type | Description |
|---|---|
CausalResult
|
The Sargan statistic uses |
Examples:
>>> import numpy as np
>>> import pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> rows = []
>>> for firm in range(40): # build a dynamic panel
... alpha = rng.normal(0, 1)
... y_prev = rng.normal(0, 1)
... for t in range(8):
... capital = rng.normal(0, 1)
... labor = rng.normal(0, 1)
... y = (0.5 * y_prev + 0.3 * capital + 0.2 * labor
... + alpha + rng.normal(0, 1))
... rows.append({'firm': firm, 'year': 2000 + t, 'output': y,
... 'capital': capital, 'labor': labor})
... y_prev = y
>>> df = pd.DataFrame(rows)
>>> # Arellano-Bond (difference GMM)
>>> result = sp.xtabond(df, y='output', x=['capital', 'labor'],
... id='firm', time='year')
>>> print(result.summary())
>>> # Two-step with Windmeijer-corrected SEs
>>> result = sp.xtabond(df, y='output', x=['capital', 'labor'],
... id='firm', time='year', twostep=True)
>>> # Blundell-Bond system GMM (use when the series is persistent)
>>> result = sp.xtabond(df, y='output', x=['capital', 'labor'],
... id='firm', time='year', method='system',
... twostep=True, collapse=True)
>>> # Lag operators, a predetermined regressor, and collapsed instruments
>>> result = sp.xtabond(df, y='output', x=['l(0/1).capital'],
... predetermined=['labor'], id='firm', time='year',
... collapse=True)
Notes
Arellano-Bond (1991): First-differences the equation to remove fixed effects α_i, then uses lagged levels Y_{i,t-2}, Y_{i,t-3}, ... as a block-diagonal set of GMM instruments for ΔY_{i,t-1}.
No constant / time trend is included (unlike Stata's default
xtabond, which adds a _cons via a level moment). This matches
Stata's xtabond ..., noconstant; the reported ρ / β coefficients
are identical to Stata's _cons run when the series has no drift.
Missing values are handled per variable, not per row. A covariate
that is unobserved at period t costs only the equations that need it;
y_{i,t} remains available as a GMM instrument and as a lag source.
(Before v1.21 a listwise dropna deleted the whole row, which
amputated the instrument set whenever a lagged regressor was used — on
abdata that moved ρ̂ from 0.849 to 0.660. See MIGRATION.md.)
Balanced vs gapped panels. Coefficients, standard errors, the
Sargan/Hansen tests and the AR(1)/AR(2) tests are validated to machine
precision against Stata for balanced panels, ragged-but-gap-free panels,
and panels with interior gaps alike. A warning is emitted on gapped
panels, but it is an efficiency advisory only: first differencing loses
two equations per hole where forward orthogonal deviations lose one, so
orthogonal=True is usually preferable there.
When cross-checking a gapped panel against xtabond2, write the
instrument set on the level — gmm(y, lag(a b)), matching
gmm_lags=(a, b) — rather than on a lagged expression such as
gmm(L.y, lag(a-1 b-1)). The two are the same moment set on a
gap-free panel and different once the panel has holes, because Stata
materialises L.y row by row before xtabond2 lags it again.
Key diagnostics:
- AR(1) test: Should reject (expected in first differences).
- AR(2) test: Should NOT reject (validates instrument exogeneity).
- Sargan / Hansen test: Should NOT reject (overidentification).
Sargan (one-step) is not robust to heteroskedasticity; prefer the
two-step Hansen J when that is a concern.
- Instrument count: reported in model_info['n_instruments'];
a warning fires when it reaches the number of units.
See Roodman (2009, Stata Journal) for practical guidance.
References
Arellano, M. and Bond, S. (1991). Some tests of specification for panel data: Monte Carlo evidence and an application to employment equations. Review of Economic Studies. [@arellano1991some] Roodman, D. (2009). How to do xtabond2: An introduction to difference and system GMM in Stata. Stata Journal. [@roodman2009xtabond]
xtdpdsys ¶
xtdpdsys(data: DataFrame, y: str, x: Optional[Sequence[str]] = None, id: str = 'id', time: str = 'time', lags: int = 1, gmm_lags: Tuple[int, Optional[int]] = (2, None), twostep: bool = False, robust: bool = True, alpha: float = 0.05, **kwargs) -> CausalResult
Blundell-Bond (1998) system GMM for dynamic panels.
Thin alias for sp.xtabond(..., method='system'), named after Stata's
xtdpdsys so the estimator is discoverable under the name applied
researchers already use. Every keyword of :func:xtabond is accepted.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
As in :func: |
required |
y
|
DataFrame
|
As in :func: |
required |
x
|
DataFrame
|
As in :func: |
required |
id
|
DataFrame
|
As in :func: |
required |
time
|
DataFrame
|
As in :func: |
required |
lags
|
DataFrame
|
As in :func: |
required |
gmm_lags
|
DataFrame
|
As in :func: |
required |
twostep
|
DataFrame
|
As in :func: |
required |
robust
|
DataFrame
|
As in :func: |
required |
alpha
|
DataFrame
|
As in :func: |
required |
**kwargs
|
Forwarded to :func: |
{}
|
Returns:
| Type | Description |
|---|---|
CausalResult
|
As :func: |
Notes
System GMM stacks the level equation alongside the first-differenced one and instruments it with lagged differences. It is the right default when the dependent variable is persistent — the case in which the lagged levels that difference GMM relies on are weak instruments and the estimate collapses toward (or past) unity.
The extra moments buy that power at the price of an extra assumption: the deviation of each unit's initial condition from its long-run mean must be uncorrelated with the fixed effect. Test it — the level instruments have their own difference-in-Hansen statistic.
Validated to machine precision against xtabond2 ..., robust
(one-step, two-step Windmeijer, and collapsed) on the abdata panel;
see tests/reference_parity/test_dynpanel_abdata_parity.py.
Examples:
>>> import statspai as sp
>>> res = sp.xtdpdsys(df, y='n', x=['w', 'k'], id='id',
... time='year', twostep=True, collapse=True)
References
Blundell, R. and Bond, S. (1998). Initial conditions and moment restrictions in dynamic panel data models. Journal of Econometrics. [@blundell1998initial] Roodman, D. (2009). How to do xtabond2. Stata Journal. [@roodman2009xtabond]
gmm ¶
gmm(moment_fn: Callable[[ndarray, Optional[DataFrame]], Any], theta0: ndarray, data: Optional[DataFrame] = None, W: Optional[ndarray] = None, method: str = 'twostep', se: str = 'robust', maxiter: int = 200, tol: float = 1e-08, param_names: Optional[List[str]] = None, alpha: float = 0.05, jacobian: Optional[Callable[[ndarray, Optional[DataFrame]], Any]] = None, vcov: str = 'mds', cluster: Optional[Any] = None, hac_bandwidth: Optional[int] = None, center: bool = False) -> EconometricResults
General GMM estimator for arbitrary moment conditions.
Minimizes Q(theta) = gbar(theta)' W gbar(theta) where
gbar(theta) = (1/n) sum_i g_i(theta).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
moment_fn
|
callable
|
|
required |
theta0
|
ndarray
|
Starting values. Also the expansion point for the closed form when the moments are affine. |
required |
data
|
DataFrame
|
Passed through to |
None
|
W
|
ndarray
|
Weighting matrix |
None
|
method
|
(onestep, twostep, iterative, cue)
|
|
'onestep'
|
se
|
(robust, unadjusted)
|
|
'robust'
|
maxiter
|
int
|
|
200
|
tol
|
float
|
|
1e-8
|
param_names
|
list of str
|
|
None
|
alpha
|
float
|
|
0.05
|
jacobian
|
callable
|
|
None
|
vcov
|
(mds, iid, hac, cluster)
|
Estimator for the moment covariance |
'mds'
|
cluster
|
array - like
|
Group labels, one per moment row. Required when |
None
|
hac_bandwidth
|
int
|
Bartlett bandwidth for |
None
|
center
|
bool
|
Centre the moments before forming |
False
|
Returns:
| Type | Description |
|---|---|
EconometricResults
|
|
Examples:
>>> import statspai as sp
>>> import numpy as np
>>> import pandas as pd
>>> rng = np.random.default_rng(0)
>>> n = 400
>>> z1, z2, u = rng.normal(size=n), rng.normal(size=n), rng.normal(size=n)
>>> x1 = 0.7 * z1 + 0.5 * z2 + u + rng.normal(size=n)
>>> y = 1.0 + 2.0 * x1 + u + rng.normal(size=n)
>>> df = pd.DataFrame({'y': y, 'x1': x1, 'z1': z1, 'z2': z2})
>>>
>>> def moment_fn(theta, data):
... y, X, Z = data['y'].values, data[['x1']].values, data[['z1', 'z2']].values
... X_full = np.column_stack([np.ones(len(y)), X])
... resid = y - X_full @ theta
... Z_full = np.column_stack([np.ones(len(y)), Z])
... return resid[:, np.newaxis] * Z_full
>>>
>>> result = sp.gmm(moment_fn, theta0=np.zeros(2), data=df,
... param_names=['_cons', 'x1'])
>>> bool(result is not None)
True
>>> result.diagnostics['n_iter'] # affine moments -> closed form
0
xtlsdvc ¶
xtlsdvc(data: DataFrame, y: str, x: Optional[Sequence[str]] = None, id: str = 'id', time: str = 'time', initial: str = 'ab', bias_order: int = 2, alpha: float = 0.05, bootstrap: int = 0, seed: Optional[int] = None) -> CausalResult
Bias-corrected LSDV (LSDVC) estimator for dynamic panels.
Equivalent to Stata's xtlsdvc (Bruno 2005).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
Panel in long format. |
required |
y
|
str
|
Dependent variable. Its first lag is added automatically — do not
pass a hand-built lag in |
required |
x
|
list of str
|
Strictly exogenous regressors. Accepts the same lag-operator syntax
as :func: |
None
|
id
|
str
|
Unit and period identifiers. |
'id'
|
time
|
str
|
Unit and period identifiers. |
'id'
|
initial
|
('ab', 'ah', 'bb')
|
Consistent estimator used to evaluate the bias expression:
Arellano-Bond, Anderson-Hsiao, or Blundell-Bond system GMM. The
correction is only as good as this input, and the three can disagree
materially on a persistent series — |
'ab'
|
bias_order
|
(1, 2, 3)
|
How many terms of the Bun-Kiviet expansion to subtract: |
1
|
alpha
|
float
|
Significance level. |
0.05
|
bootstrap
|
int
|
Number of parametric-bootstrap replications for the standard errors.
The reported analytic standard errors are the LSDV ones and do not
account for the bias correction, exactly as in Stata's
|
0
|
seed
|
int
|
Seed for the bootstrap. |
None
|
Returns:
| Type | Description |
|---|---|
CausalResult
|
|
Notes
The bias expression is the Bun-Kiviet/Bruno one, evaluated per unit on
the balanced period grid with a selection matrix marking the observed
rows (so unbalanced panels are handled as in Bruno 2005). Validated
against Stata's xtlsdvc at all three bias orders and all three
initial estimators.
When to prefer this over GMM. Small N with small T — the
regime where the GMM instrument count rivals the number of units and
the Hansen test stops being informative. LSDVC uses no instruments at
all, so it cannot be undone by instrument proliferation; the price is
that it assumes strict exogeneity and homoskedasticity, which GMM does
not.
Examples:
>>> import statspai as sp
>>> res = sp.xtlsdvc(df, y='n', x=['w', 'k'], id='id',
... time='year', initial='bb', bootstrap=200)
References
Kiviet, J.F. (1995). Journal of Econometrics 68(1), 53-78. [@kiviet1995bias] Bruno, G.S.F. (2005). Economics Letters 87(3), 361-366. [@bruno2005approximating]