Causal Inference V(b) — Dynamic Panels (R companion)¶

plm::pgmm — difference and system GMM on Arellano & Bond's employment data¶

plm::pgmm (Croissant & Millo) is R's reference implementation of dynamic-panel GMM, and its documentation example is the Arellano–Bond (1991) UK employment panel we use here. This companion reproduces the Python notebook: plm(model="pooling") and plm(model="within") give the upward/downward-biased Bond bracket, and pgmm(transformation="d") / pgmm(transformation="ld") give difference and system GMM, with the Sargan/Hansen and AR(2) diagnostics. The from-scratch Python estimates matched these to the third decimal.

1. The lagged dependent variable and the OLS–FE bracket¶

The dynamic employment equation regresses log employment on its own lag plus log wage, capital, and output, with firm effects. Pooled OLS over-states persistence (firm effect left in the error); fixed effects under-states it (Nickell bias). With no known truth, the consistent estimate must lie between them — Bond's bracket.

In [1]:
suppressMessages(library(plm)); data(EmplUK, package="plm")
cat(sprintf("Arellano-Bond panel: %d UK firms, %d-%d, %d firm-years (unbalanced)\n",
            length(unique(EmplUK$firm)), min(EmplUK$year), max(EmplUK$year), nrow(EmplUK)))
pd<-pdata.frame(EmplUK, index=c("firm","year"))
ols<-plm(log(emp)~lag(log(emp),1)+log(wage)+log(capital)+log(output), data=pd, model="pooling")
fe <-plm(log(emp)~lag(log(emp),1)+log(wage)+log(capital)+log(output), data=pd, model="within")
rho_ols<-coef(ols)["lag(log(emp), 1)"]; rho_fe<-coef(fe)["lag(log(emp), 1)"]
cat(sprintf("\n  persistence rho (coef on lagged log-employment):\n"))
cat(sprintf("    pooled OLS = %.3f   biased UP\n", rho_ols))
cat(sprintf("    FE within  = %.3f   biased DOWN (Nickell)\n", rho_fe))
cat(sprintf("    => Bond bracket: consistent estimate in [%.2f, %.2f]\n", rho_fe, rho_ols))
options(repr.plot.width=7.5, repr.plot.height=4.4)
bp<-barplot(c(`pooled OLS`=rho_ols, `FE within`=rho_fe), col=c("#c53030","#dd6b20"), ylab="persistence rho",
            main="The two workhorses bracket the truth", ylim=c(0,1))
rect(0.2, rho_fe, 2.5, rho_ols, col=rgb(47,133,90,30,max=255), border=NA)
text(bp, c(rho_ols,rho_fe)+0.03, sprintf("%.2f",c(rho_ols,rho_fe)))
Warning message:
"package 'plm' was built under R version 4.6.1"
Arellano-Bond panel: 140 UK firms, 1976-1984, 1031 firm-years (unbalanced)
  persistence rho (coef on lagged log-employment):
    pooled OLS = 0.932   biased UP
    FE within  = 0.514   biased DOWN (Nickell)
    => Bond bracket: consistent estimate in [0.51, 0.93]
No description has been provided for this image

2. Difference GMM — pgmm(transformation = "d")¶

Arellano–Bond difference GMM first-differences away the firm effect and instruments the differenced lag with lagged levels lag(log(emp), 2:99). On this persistent series the lagged-level instruments are weak, and the estimate falls below the FE bound — the warning sign that motivates system GMM. (The plm documentation's canonical two-lag specification is shown alongside the simple one-lag spec that matches the Python from-scratch estimator.)

In [2]:
dif <- pgmm(log(emp) ~ lag(log(emp),1) + log(wage) + log(capital) + log(output)
            | lag(log(emp), 2:99), data=EmplUK, effect="individual", model="onestep", transformation="d")
rho_dif<-coef(dif)["lag(log(emp), 1)"]
# canonical AB(1991) 2-lag specification (plm doc example)
ab91 <- pgmm(log(emp) ~ lag(log(emp),1:2) + lag(log(wage),0:1) + lag(log(capital),0:2) + lag(log(output),0:1)
             | lag(log(emp), 2:99), data=EmplUK, effect="twoways", model="twosteps")
cat(sprintf("difference GMM (one-step, 1-lag spec): rho = %.3f  (Python from-scratch 0.34)\n", rho_dif))
cat(sprintf("  falls below FE (%.2f) -> weak instruments on a persistent series\n", rho_fe))
cat(sprintf("canonical AB(1991) 2-lag two-step spec: rho on lag1 = %.3f (the textbook 0.47-0.63 range)\n", coef(ab91)["lag(log(emp), 1:2)1"]))
difference GMM (one-step, 1-lag spec): rho = 0.341  (Python from-scratch 0.34)
  falls below FE (0.51) -> weak instruments on a persistent series
canonical AB(1991) 2-lag two-step spec: rho on lag1 = 0.645 (the textbook 0.47-0.63 range)

3. System GMM — pgmm(transformation = "ld") — and diagnostics¶

System GMM adds the levels equation with lagged-difference instruments (transformation = "ld"), which stay informative under persistence and pull the estimate back into the bracket. We report the Sargan/Hansen over-identification test and the AR(2) test (Arellano–Bond mtest): AR(1) is expected in differenced residuals, AR(2) should not reject, and the Sargan test should not strongly reject (watch for instrument proliferation).

In [3]:
sys <- pgmm(log(emp) ~ lag(log(emp),1) + log(wage) + log(capital) + log(output)
            | lag(log(emp), 2:99), data=EmplUK, effect="individual", model="onestep", transformation="ld")
rho_sys<-coef(sys)["lag(log(emp), 1)"]
cat(sprintf("system GMM (Blundell-Bond, one-step): rho = %.3f  (Python from-scratch 0.64)\n", rho_sys))
cat(sprintf("  -> restored into the Bond bracket [%.2f, %.2f]\n", rho_fe, rho_ols))
cat(sprintf("\ndiagnostics (system GMM):\n"))
cat(sprintf("  AR(1) test p = %.3f  (expected to reject -- first-order corr in differences)\n", mtest(sys, order=1)$p.value))
cat(sprintf("  AR(2) test p = %.3f  (should NOT reject -> lagged-level instruments valid)\n", mtest(sys, order=2)$p.value))
cat(sprintf("  Sargan test p = %.3f  (over-identification; over-rejects under many instruments)\n", sargan(sys)$p.value))
options(repr.plot.width=8.7, repr.plot.height=4.4)
vals<-c(`pooled OLS`=rho_ols, `FE`=rho_fe, `diff-GMM`=rho_dif, `system GMM`=rho_sys)
bp<-barplot(vals, col=c("#c53030","#dd6b20","#6b46c1","#2f855a"), ylab="persistence rho", ylim=c(0,1),
            main="System GMM restores an in-bracket persistence estimate")
rect(0.2, rho_fe, 5, rho_ols, col=rgb(47,133,90,30,max=255), border=NA)
text(bp, vals+0.03, sprintf("%.2f",vals))
system GMM (Blundell-Bond, one-step): rho = 0.658  (Python from-scratch 0.64)
  -> restored into the Bond bracket [0.51, 0.93]
diagnostics (system GMM):
  AR(1) test p = 0.007  (expected to reject -- first-order corr in differences)
  AR(2) test p = 0.670  (should NOT reject -> lagged-level instruments valid)
  Sargan test p = 0.003  (over-identification; over-rejects under many instruments)
No description has been provided for this image

4. Summary¶

plm::pgmm reproduced the dynamic-panel story on Arellano & Bond's own data: pooling/within gave the upward/downward-biased Bond bracket (0.51–0.93); difference GMM (transformation="d", 0.34) fell below FE — weak instruments on a persistent series; and system GMM (transformation="ld", 0.66) restored the estimate into the bracket, with a clean AR(2) test. The Python from-scratch estimators matched to the third decimal (diff 0.34, system 0.64).

Guidance unchanged: use system GMM for short, persistent dynamic panels, and always report AR(2), the Sargan/Hansen test, and the instrument count (collapse or cap the lag depth against instrument proliferation). Cross-links: the weak-instrument pathology mirrors the IV notebook (subsection 3); the within transformation is from the panel/fixed-effects notebook (5a); the many-moment GMM machinery connects to Double ML orthogonality (subsection 9).