Standard errors¶
Download the script: inference.py
Why the two-step errors need a correction. Two-step GMM is
asymptotically efficient, but its conventional standard errors are badly
biased downward in finite samples. Windmeijer (2005) derived a correction
for the uncertainty in the first-step estimate that the weight matrix uses.
It is the default, vcov="robust": clustered by panel, and
Windmeijer-corrected for two-step. with_vcov("iid") gives the uncorrected
two-step errors, and every two-step fit carries its one-step results.
two = dpd_stream(F, df, panel=("id", "year"))
two.se # Windmeijer-corrected
two.with_vcov("iid").se # uncorrected
two.onestep.se # one-step, robust
two-step, Windmeijer-corrected: [0.0095 0.0145 0.0095]
two-step, uncorrected: [0.0092 0.014 0.0093]
one-step, robust: [0.0093 0.0143 0.0095]
Clustering¶
vcov={"CRV1": "cluster"} clusters by any variable. As with xtabond2’s
cluster(), the clusters also define the two-step weight matrix, so they
change the two-step estimates, not only the standard errors.
Several variables, as in {"CRV1": "firm+year"}, cluster by every
combination with inclusion and exclusion (Cameron, Gelbach and Miller
2011).
clustered by cluster: [0.0123 0.0193 0.0084] {'cluster': 60}
one-step, two-way (cluster, year): [0.0332 0.0469 0.0117] (l(y, 1), x, w)
Two-way clustering with few clusters. With few clusters in one dimension (years, typically), multi-way clustering is fragile, in xtabond2 as here:
Its part of the moment covariance has the rank of its cluster count. So a two-step weight matrix built on it is singular, and the estimates unreliable.
The inclusion–exclusion variance can be negative for coefficients that vary only in that dimension, here the (absorbed) year effects.
Clustering a one-step fit avoids the first problem.
Small-sample statistics¶
small=True gives t and F statistics, with xtabond2’s degrees of freedom
and small-sample factors:
│ l(y, 1) ┆ 0.49296 ┆ 0.009475 ┆ 52.027339 ┆ 0.0 │
...
{'statistic': 3315.6, 'pvalue': 0.0, 'df1': 2, 'df2': 3000, 'distribution': 'F'}
The script
"""Standard errors: one-step and two-step, Windmeijer's correction,
clustering (one- and two-way), small-sample statistics.
python examples/inference.py
"""
from dpd_stream import dpd_stream
from dpd_stream.simulate import simulate_dynamic
df = simulate_dynamic(3_000, 8, rho=(0.5,), n_clusters=60, heteroskedastic=True, seed=4)
P = ("id", "year")
F = "y ~ l(y, 1) + x + w | year | gmm(y, 2:4) + gmm(x, 1:3) + iv(w)"
two = dpd_stream(F, df, panel=P, verbose=False)
print("two-step, Windmeijer-corrected:", two.se.round(4))
print("two-step, uncorrected: ", two.with_vcov("iid").se.round(4))
print("one-step, robust: ", two.onestep.se.round(4))
# clustering defines the two-step weight matrix too (as xtabond2's cluster())
cl = dpd_stream(F, df, panel=P, vcov={"CRV1": "cluster"}, verbose=False)
print("clustered by cluster:", cl.se.round(4), cl.n_clusters)
# two-way clustering: with few clusters in one dimension (8 years here) the
# two-step weight matrix it defines is singular, so cluster a one-step fit,
# whose weight does not depend on it
tw = dpd_stream(F, df, panel=P, steps=1, vcov={"CRV1": "cluster+year"}, verbose=False)
print("one-step, two-way (cluster, year):", tw.se[:3].round(4), "(l(y, 1), x, w)")
sm = dpd_stream(F, df, panel=P, small=True, verbose=False)
print(sm.tidy())
print(sm.wald_test(keep="x|w"))