Choosing instruments¶
Download the script: instruments.py
The risk of many instruments. Every lag of every instrumenting variable,
in every period, is a column. With 10 periods, gmm(y, 2:.) alone is 36
columns. Too many instruments overfit the endogenous variables, and the
Hansen test loses its power to reject: its p-values drift toward 1 (Roodman
2009b).
What to compare. The usual remedies are to:
limit the lags;
collapse the instruments (one per lag);
replace them by their principal components.
Then look at what changes: the instrument count, the estimates, the Hansen and AR(2) tests, and the model and moment selection criteria (MMSC; lower is better).
base = "y ~ l(y, 1) + x + w | year"
specs = {
"all lags": "gmm(y, 2:.) + gmm(x, 1:.) + iv(w)",
"lags 2-3": "gmm(y, 2:3) + gmm(x, 1:2) + iv(w)",
"collapsed": "gmm(y, 2:., collapse=True) + gmm(x, 1:., collapse=True) + iv(w)",
}
for name, inst in specs.items():
r = dpd_stream(f"{base} | {inst}", df, panel=("id", "year"))
...
┌───────────┬──────────┬──────────┬─────────────┬──────────┬──────────┬─────────────┐
│ spec ┆ rho ┆ se ┆ instruments ┆ hansen_p ┆ ar2_p ┆ mmsc_bic │
╞═══════════╪══════════╪══════════╪═════════════╪══════════╪══════════╪═════════════╡
│ all lags ┆ 0.595532 ┆ 0.004602 ┆ 89 ┆ 0.486069 ┆ 0.540762 ┆ -748.768287 │
│ lags 2-3 ┆ 0.593107 ┆ 0.005802 ┆ 40 ┆ 0.743126 ┆ 0.546 ┆ -283.593002 │
│ collapsed ┆ 0.591788 ┆ 0.005324 ┆ 26 ┆ 0.96834 ┆ 0.5477 ┆ -152.372356 │
└───────────┴──────────┴──────────┴─────────────┴──────────┴──────────┴─────────────┘
(The true ρ is 0.6.)
Forward orthogonal deviations¶
The problem with gaps. First differences lose two observations at every
gap in a panel: the one missing, and the next, which has no difference.
Forward orthogonal deviations (transform="fod", xtabond2’s orthogonal)
subtract the mean of all later observations, so they lose only one.
fd: 29,519 observations, rho = 0.6017 (0.0081)
fod: 31,902 observations, rho = 0.6013 (0.0058)
Principal components¶
pca=True replaces the GMM-style instruments by the principal components of
their correlation matrix. It keeps those with eigenvalues of 1 or more, as
xtabond2 does; components= fixes the number instead. The
Kaiser–Meyer–Olkin statistic measures how well the instruments suit the
reduction.
pca: 11 principal components (KMO 0.904); rho = 0.5752 (0.0198)
Instruments as objects¶
The formula’s gmm() and iv() terms can also be passed as Gmm and Iv
objects. That is convenient in a loop:
from dpd_stream import Gmm, Iv
for b in (3, 4, 5):
r = dpd_stream("y ~ l(y, 1) + x + w | year", df, panel=("id", "year"),
gmm=[Gmm("y", lags=(2, b)), Gmm("x", lags=(1, b - 1))], iv=[Iv("w")])
lags 2-3: rho = 0.5931, Hansen p = 0.743, difference-in-Hansen p (x's instruments) = 0.818
lags 2-4: rho = 0.5934, Hansen p = 0.355, difference-in-Hansen p (x's instruments) = 0.689
lags 2-5: rho = 0.5931, Hansen p = 0.410, difference-in-Hansen p (x's instruments) = 0.581
The suboptions¶
Suboption |
Effect |
|---|---|
|
lags a to b; |
|
one instrument per lag and variable (or |
|
which equation the instruments are for, in system GMM; |
|
system GMM: the levels instruments get their own difference-in-Hansen test |
|
levels-equation instruments are not differenced |
|
backward orthogonal deviations of the variable, to pair with |
|
IV-style: one column per variable, transformed for the transformed equation unless |
The script
"""Choosing instruments: lag limits, collapsing, forward orthogonal
deviations, principal components, and the tests that guide the choice.
python examples/instruments.py
Too many instruments overfit the endogenous variables and weaken the
Hansen test (Roodman 2009b); this compares instrument sets on one panel.
"""
import polars as pl
from dpd_stream import Gmm, Iv, dpd_stream
from dpd_stream.simulate import simulate_dynamic
df = simulate_dynamic(5_000, 10, rho=(0.6,), seed=2)
P = ("id", "year")
base = "y ~ l(y, 1) + x + w | year"
specs = {
"all lags": "gmm(y, 2:.) + gmm(x, 1:.) + iv(w)",
"lags 2-3": "gmm(y, 2:3) + gmm(x, 1:2) + iv(w)",
"collapsed": "gmm(y, 2:., collapse=True) + gmm(x, 1:., collapse=True) + iv(w)",
}
rows = []
for name, inst in specs.items():
r = dpd_stream(f"{base} | {inst}", df, panel=P, verbose=False)
rows.append(dict(spec=name, rho=r.beta[0], se=r.se[0], instruments=r.n_instruments,
hansen_p=r.hansen["p"], ar2_p=r.ar_test(2)["p"],
mmsc_bic=r.mmsc["bic"]))
print(pl.DataFrame(rows))
# ---- forward orthogonal deviations keep more observations with gaps
gappy = simulate_dynamic(5_000, 10, rho=(0.6,), p_gap=0.1, seed=3)
for tr in ("fd", "fod"):
r = dpd_stream(f"{base} | gmm(y, 2:4) + gmm(x, 1:3) + iv(w)", gappy, panel=P,
transform=tr, verbose=False)
print(f"{tr}: {r.n_obs:,} observations, rho = {r.beta[0]:.4f} ({r.se[0]:.4f})")
# ---- principal components of the GMM-style instruments (xtabond2's pca)
r = dpd_stream(f"{base} | gmm(y, 2:.) + gmm(x, 1:.) + iv(w)", df, panel=P, pca=True,
verbose=False)
print(f"pca: {r.pca['components']} principal components (KMO {r.pca['kmo']:.3f}); "
f"rho = {r.beta[0]:.4f} ({r.se[0]:.4f})")
# ---- the same specification as objects, e.g. built in a loop
for b in (3, 4, 5):
r = dpd_stream(base, df, panel=P, gmm=[Gmm("y", lags=(2, b)), Gmm("x", lags=(1, b - 1))],
iv=[Iv("w")], verbose=False)
print(f"lags 2-{b}: rho = {r.beta[0]:.4f}, Hansen p = {r.hansen['p']:.3f}, "
f"difference-in-Hansen p (x's instruments) = "
f"{[d for d in r.diff_hansen if 'x' in d['group']][0]['difference_p']:.3f}")