Arellano and Bond’s employment equations¶
Download the script: arellano_bond.py
Arellano and Bond (1991) estimated a dynamic labor demand equation for 140
UK firms over 1976–1984. These are Stata’s abdata, which the script
downloads from Stata Press (they are not distributed with dpd-stream;
reading them needs pandas):
import io
import urllib.request
import pandas as pd
import polars as pl
URL = "https://www.stata-press.com/data/r18/abdata.dta"
req = urllib.request.Request(URL, headers={"User-Agent": "dpd-stream example"})
with urllib.request.urlopen(req) as resp:
raw = io.BytesIO(resp.read())
d = (pl.from_pandas(pd.read_stata(raw)).rename({"id": "firm"})
.with_columns(pl.col("firm").cast(pl.Int64), pl.col("year").cast(pl.Int64)))
Roodman (2009) uses the same data and specifications to introduce xtabond2. dpd-stream reproduces his tables to the digits printed (pp. 111–118).
The variables are logs:
Variable |
Log of |
|---|---|
|
employment |
|
the wage |
|
capital |
|
industry output |
Difference GMM, one step (Table 4, column a1)¶
This is xtabond2’s
xtabond2 n L.n L2.n w L.w L(0/2).(k ys) yr*, gmm(L.n) iv(w L.w L(0/2).(k ys) yr*) noleveleq robust:
YR = " + ".join(f"yr{y}" for y in range(1976, 1985))
ab = dpd_stream("n ~ l(n, 1:2) + w + l(w, 1) + k + l(k, 1:2) + ys + l(ys, 1:2) + " + YR
+ " | gmm(l(n, 1)) + iv(w + l(w, 1) + k + l(k, 1:2) + ys + l(ys, 1:2) + "
+ YR + ")", d, panel=("firm", "year"), steps=1)
Dynamic panel-data estimation, one-step difference GMM (dpd_stream) vcov: robust
observations: 611 groups: 140 obs per group: min 4, avg 4.36, max 6 instruments: 41
Wald test of all coefficients: chi2(16) = 1727.45, p = 0.0000
Arellano-Bond test for AR(1) in first differences: z = -3.60, p = 0.0003
Arellano-Bond test for AR(2) in first differences: z = -0.52, p = 0.6058
Sargan test of overid. restrictions: chi2(25) = 67.59, p = 0.0000 (not robust)
Hansen test of overid. restrictions: chi2(25) = 31.38, p = 0.1767 (robust)
Difference-in-Hansen tests of exogeneity of instrument subsets:
iv(w + l(w, 1) + k + l(k, 1:2) + ys + l(ys, 1:2) + yr1976 + ... + yr1984)
excluding group: chi2(11) = 12.01, p = 0.3632 difference: chi2(14) = 19.37, p = 0.1511
Underidentification (Kleibergen-Paap rk LM, robust): chi2(26) = 32.66, p = 0.1723 weak identification (KP rk Wald F): 2.24
...
dropped as collinear: ['yr1976', 'yr1977', 'yr1984']
│ l(n, 1) ┆ 0.686226 ┆ 0.144594 ┆ ...
│ l(n, 2) ┆ -0.085358 ┆ 0.056016 ┆ ...
│ w ┆ -0.607821 ┆ 0.178205 ┆ ...
│ l(w, 1) ┆ 0.392624 ┆ 0.167993 ┆ ...
│ k ┆ 0.356846 ┆ 0.05902 ┆ ...
Roodman’s output (p. 111) has the same numbers:
L1.n: .6862261 (SE .1445943);
the AR tests −3.60 and −0.52;
Sargan 67.59 and Hansen 31.38;
excluding the group, 12.01; the difference, 19.37.
The year dummies are dropped as collinear as in Stata (1976, 1977 and 1984).
Two steps (column a2)¶
gmm(n, 2:.) is the same instrument set as gmm(l(n, 1)), that is, lags 2
and deeper of n. The two-step fit has Windmeijer-corrected standard errors:
│ l(n, 1) ┆ 0.628709 ┆ 0.193414 ┆ ...
│ l(n, 2) ┆ -0.065188 ┆ 0.04505 ┆ ...
│ w ┆ -0.52576 ┆ 0.154611 ┆ ...
These are the article’s column (a2). On R plm’s copy of the data
(EmplUK, in double precision, where Stata’s is float), they also match
plm’s pgmm example to every digit: the estimates, its vcovHC errors
(Windmeijer’s), the Sargan/Hansen statistic and the AR tests.
System GMM (Blundell and Bond)¶
Wages and capital are endogenous, instrumented GMM-style, and the year
dummies instrument the levels equation. This is xtabond2’s
gmmstyle(L.(n w k)) ivstyle(yr*, equation(level)) robust small:
bb = dpd_stream("n ~ l(n, 1) + w + l(w, 1) + k + l(k, 1) + " + YR
+ " | gmm(l(n, 1) + l(w, 1) + l(k, 1)) + iv(" + YR + ", eq='level')",
d, panel=("firm", "year"), system=True, steps=1, small=True)
Dynamic panel-data estimation, one-step system GMM (dpd_stream) vcov: robust
observations: 891 groups: 140 obs per group: min 6, avg 6.36, max 8 instruments: 113
Arellano-Bond test for AR(1) in first differences: z = -5.46, p = 0.0000
Arellano-Bond test for AR(2) in first differences: z = -0.25, p = 0.8038
Hansen test of overid. restrictions: chi2(100) = 110.70, p = 0.2183 (robust)
GMM instruments for levels
excluding group: chi2(79) = 84.33, p = 0.3200 difference: chi2(21) = 26.37, p = 0.1927
│ l(n, 1) ┆ 0.935605 ┆ 0.026569 ┆ ...
│ Intercept ┆ 0.552201 ┆ 0.197162 ┆ ...
Two numbers differ from the article, and both because of changes in xtabond2 since 2009. dpd-stream follows the current version, 3.7.2:
The Sargan statistic (157.54; the article has 186.90). σ² now divides by the 751 observations of the differenced equation, not the 891 of the levels equation.
The model F (6359; the article has 1154). The test now includes the constant.
With the old conventions undone, both agree. tests/test_published.py
checks this.
Instrument proliferation. 113 instruments for 140 groups is the proliferation Roodman (2009b) warns about. dpd-stream warns when the instruments outnumber the groups, as xtabond2 does.
The script
"""Arellano and Bond (1991) and Blundell and Bond (1998), as in Roodman's
(2009) xtabond2 article, on the UK firm data (Stata's abdata).
python examples/arellano_bond.py
The data are downloaded from Stata Press (they are not distributed with
dpd-stream; reading them needs pandas): n, w, k and ys are the logs of
employment, wages, capital and industry output. The results reproduce the
article's tables (Roodman 2009, pp. 111-118) to the digits printed.
"""
import io
import urllib.request
import pandas as pd
import polars as pl
from dpd_stream import dpd_stream
URL = "https://www.stata-press.com/data/r18/abdata.dta"
req = urllib.request.Request(URL, headers={"User-Agent": "dpd-stream example"})
with urllib.request.urlopen(req) as resp:
raw = io.BytesIO(resp.read())
d = (pl.from_pandas(pd.read_stata(raw)).rename({"id": "firm"})
.with_columns(pl.col("firm").cast(pl.Int64), pl.col("year").cast(pl.Int64)))
YR = " + ".join(f"yr{y}" for y in range(1976, 1985))
# ---- Arellano and Bond's Table 4, column (a1): one-step difference GMM
ab = dpd_stream("n ~ l(n, 1:2) + w + l(w, 1) + k + l(k, 1:2) + ys + l(ys, 1:2) + " + YR
+ " | gmm(l(n, 1)) + iv(w + l(w, 1) + k + l(k, 1:2) + ys + l(ys, 1:2) + "
+ YR + ")", d, panel=("firm", "year"), steps=1, verbose=False)
ab.summary()
# ---- the same, two-step: column (a2), with Windmeijer-corrected errors
ab2 = dpd_stream("n ~ l(n, 1:2) + w + l(w, 1) + k + l(k, 1:2) + ys + l(ys, 1:2) + " + YR
+ " | gmm(n, 2:.) + iv(w + l(w, 1) + k + l(k, 1:2) + ys + l(ys, 1:2) + "
+ YR + ")", d, panel=("firm", "year"), verbose=False)
print(ab2.tidy().head(4))
# ---- Blundell and Bond: system GMM, wages and capital endogenous
bb = dpd_stream("n ~ l(n, 1) + w + l(w, 1) + k + l(k, 1) + " + YR
+ " | gmm(l(n, 1) + l(w, 1) + l(k, 1)) + iv(" + YR + ", eq='level')",
d, panel=("firm", "year"), system=True, steps=1, small=True, verbose=False)
bb.summary()