Causal Inference IX — Double/Debiased ML (R companion)¶

hdm — the double-lasso, and the 401(k) effect with valid inference¶

Double Machine Learning was pioneered in R, and its most influential concrete form is the double-lasso / double-selection estimator of Belloni, Chernozhukov & Hansen, implemented in the hdm package. It is exactly the DML recipe of the Python notebook — Neyman-orthogonal partialling-out with cross-fitted nuisances — specialized to lasso nuisance learners, which makes it fast, transparent, and equipped with valid post-selection confidence intervals.

  • hdm::rlassoEffect — the effect of a treatment on an outcome, partialling out high-dimensional controls by lasso ("partialling out" and "double selection" variants);
  • hdm::pension — the 401(k) data, the canonical application.

We reproduce the Python notebook's two results: on simulated data the double-lasso recovers a known effect that naive OLS misses, and on the 401(k) data it cuts the confounded naive gap to the same ~USD 9,000 causal effect (with flexible controls, matching the gradient-boosting DML of the Python notebook).

1. Double-lasso on a known effect¶

The partially linear model $Y=\theta T+g(X)+\varepsilon$, $T=m(X)+\nu$, with a true effect $\theta=1$ and 50 potential controls (a sparse subset actually matter). Naive OLS of $Y$ on $T$ is biased because $T$ is correlated with the confounders $X$. rlassoEffect with double selection lassos $Y$ on $X$ and $T$ on $X$, keeps the union of selected controls, and estimates $\theta$ from the residuals — recovering the truth with a valid standard error.

In [1]:
suppressMessages(library(hdm))
set.seed(0); n<-2000; p<-50; X<-matrix(rnorm(n*p),n,p)
gX<-X[,1]+0.5*X[,2]^2+X[,3]; mX<-0.7*X[,1]+0.5*X[,3]; theta<-1
T<-mX+rnorm(n,0,0.5); Y<-theta*T+gX+rnorm(n,0,0.5)
naive<-coef(lm(Y~T))["T"]
dl<-rlassoEffect(x=X, y=Y, d=T, method="double selection")
cat(sprintf("true theta = 1\n"))
cat(sprintf("naive OLS  Y~T        = %.3f   (biased: T correlates with confounders)\n", naive))
cat(sprintf("double-lasso (hdm)    = %.3f   SE %.3f   95%% CI [%.3f, %.3f]\n",
            dl$coefficients, dl$se, dl$coefficients-1.96*dl$se, dl$coefficients+1.96*dl$se))
Warning message:
"package 'hdm' was built under R version 4.6.1"
true theta = 1
naive OLS  Y~T        = 2.239   (biased: T correlates with confounders)
double-lasso (hdm)    = 0.967   SE 0.037   95% CI [0.894, 1.040]

2. The 401(k) effect on net financial assets — hdm::pension¶

The same question as the Python notebook: does 401(k) eligibility (e401) raise net financial assets (net_tfa)? A naive eligible-minus-ineligible comparison gives ~USD 19,600, heavily confounded by income and age. We give rlassoEffect a flexible control set (polynomials of age, income, education, family size, plus indicators and an income×age interaction) and let the double-lasso select among them. The debiased estimate drops to about USD 9,000 with a tight confidence interval — matching the gradient-boosting DML of the Python notebook, and showing the estimate is robust to whether the nuisances are lassos or boosted trees, provided the controls are flexible enough.

In [2]:
data(pension)
naive_k<-mean(pension$net_tfa[pension$e401==1])-mean(pension$net_tfa[pension$e401==0])
Xf<-model.matrix(~ poly(age,4)+poly(inc,4)+poly(educ,2)+poly(fsize,2)+marr+twoearn+db+pira+hown+I(inc*age), data=pension)[,-1]
po<-rlassoEffect(x=Xf, y=pension$net_tfa, d=pension$e401, method="partialling out")
ds<-rlassoEffect(x=Xf, y=pension$net_tfa, d=pension$e401, method="double selection")
cat(sprintf("naive difference (eligible - not) = %.0f  (confounded)\n", naive_k))
cat(sprintf("double-lasso, partialling out     = %.0f  SE %.0f  95%% CI [%.0f, %.0f]\n", po$coefficients, po$se, po$coefficients-1.96*po$se, po$coefficients+1.96*po$se))
cat(sprintf("double-lasso, double selection    = %.0f  SE %.0f\n", ds$coefficients, ds$se))
options(repr.plot.width=8, repr.plot.height=3.6)
vals<-c(`naive`=naive_k, `double-lasso\n(partialling out)`=po$coefficients, `double-lasso\n(double selection)`=ds$coefficients)
bp<-barplot(vals, horiz=TRUE, las=1, col=c("#a0aec0","#2f855a","#2b6cb0"), xlim=c(0,21000),
            xlab="effect of 401(k) eligibility on net financial assets (USD)", main="Naive vs double-lasso DML")
text(vals-1500, bp, sprintf("%.0f",vals), col="white", font=2)
naive difference (eligible - not) = 19559  (confounded)
double-lasso, partialling out     = 8996  SE 1216  95% CI [6612, 11379]
double-lasso, double selection    = 9046  SE 1279
No description has been provided for this image

3. Summary¶

hdm's double-lasso reproduced the Python notebook with lasso nuisances: on simulated data it recovered a known effect that naive OLS missed, and on the 401(k) data it cut the confounded naive gap of ~USD 19,600 to a credible ~USD 9,000 causal effect with a valid confidence interval — the same answer as the gradient-boosting DML, confirming the estimate is driven by the debiasing, not the particular learner (given flexible-enough controls).

The lesson: DML is a template — Neyman-orthogonal partialling-out plus cross-fitting — that any nuisance learner can fill in. hdm's double-lasso is the fast, interpretable, inference-ready instance for high-dimensional linear-in-basis controls; gradient boosting or forests (the Python notebook, econml, DoubleML) handle strongly nonlinear nuisances. Both belong in the toolkit. Cross-links: the double-lasso's variable selection is the variable-selection arc meeting causal inference; the orthogonalization is shared with the R-learner and causal forest; and cross-fitting is the sample-splitting discipline of the purged cross-validation notebook. Next, the arc closes with Causal Survival Analysis.