Skip to content

Inference

Standard errors and confidence intervals

Standard errors come from the orthogonal score (see Models); intervals and p-values use the normal approximation. With several treatments, each gets its own estimate, standard error and interval.

model.summary          # coef, std err, t, P>|t|, 2.5 %, 97.5 %
model.confint(0.90)

Repeated sample splitting

The estimate depends on the random split into folds. With n_rep repetitions the reported coefficient is the median over repetitions, and the standard error accounts for the variation between them: it is computed from the median of \(\hat\theta_r + 1.96\,\widehat{\text{se}}_r\), as in DoubleML. Per-repetition results are in all_coef_ and all_se_.

dml_rs.PLR(ml_l, ml_m, n_rep=10).fit(X, y, d)

Multiplier bootstrap and simultaneous inference

With several treatments (or the many effects of a staggered DiD), pointwise intervals do not cover all effects jointly. The multiplier bootstrap gives simultaneous confidence bands and multiple-testing adjustment:

model.bootstrap(method="normal", n_boot=1000)   # also "Bayes" and "wild"
model.confint(joint=True)                        # simultaneous bands
model.p_adjust("romano-wolf")                    # also "holm" and "bonferroni"

Combining estimates

Estimates from fits on the same observations and sample splits can be added, subtracted, scaled and stacked, as DoubleML's DoubleMLFramework. The scores are combined the same way, so standard errors and intervals account for the correlation between the estimates.

plr = dml_rs.PLR(ml_l, ml_m).fit(X, y, d, folds=folds)
irm = dml_rs.IRM(ml_g, ml_m_clf).fit(X, y, d, folds=folds)
diff = plr.framework - irm.framework   # also +, c * fw, -fw
diff.summary
both = dml_rs.concat([plr, irm])       # both estimates
both.bootstrap().confint(joint=True)   # simultaneous bands
both.linear_combination([[0.5, 0.5]], names=["average"])
let diff = (&plr - &irm)?;                      // also &a + &b, c * &fit, -&fit
let both = DmlFit::concat(&[plr, irm])?;
let average = both.linear_combination(array![[0.5, 0.5]].view(), vec!["average".into()])?;

A Framework has the fitted-model API (summary, confint, bootstrap, p_adjust, sensitivity_analysis). For the sensitivity analysis the bias bounds add up with absolute weights, as in DoubleML. framework.pval is computed from the aggregated t-statistic, as a DoubleML model's pval. DoubleML's DoubleMLFramework.pvals instead takes the median of the per-repetition p-values; the two agree with a single repetition.

Clustered data

When observations are grouped (pupils in schools, workers in firms) and share unobserved shocks, pass the cluster labels. Whole clusters are assigned to folds, \(\theta\) solves a fold-weighted score and the variance sums the score within clusters (Chiang, Kato, Ma and Sasaki, 2022), as in DoubleML. Two columns give two-way clustering (e.g. firm and year).

model.fit(X, y, d, cluster=df["school"])                # one-way
model.fit(X, y, d, cluster=df[["firm", "year"]])        # two-way
model.n_clusters_

Labels can be of any type. With clustered data, the multiplier bootstrap is not available (as in DoubleML). In Rust: DmlData::with_clusters and with_two_way_clusters.

Sensitivity analysis

How strong would an omitted confounder have to be to change the conclusion? Following Chernozhukov, Cinelli, Newey, Sharma and Syrgkanis (2022), a confounder is described by cf_y (the share of the outcome's residual variation it explains), cf_d (its effect on the Riesz representer) and their correlation rho.

model.sensitivity_analysis(cf_y=0.04, cf_d=0.04, rho=1.0)
print(model.sensitivity_summary)     # bounds, confidence bounds, robustness values
model.sensitivity_benchmark(benchmarking_set=["age"])   # calibrate against an observed covariate
model.sensitivity_plot(benchmarks={"cf_y": [0.03], "cf_d": [0.02], "name": ["age"]})

The robustness value RV is the confounding strength (with cf_y = cf_d) that would move the estimate to the null hypothesis; RVa the strength that would make it insignificant. Available for PLR, IRM, APO and DiD models, including every ATT(g, t) of a staggered DiD and its aggregations (aggregate(...).aggregated_frameworks.sensitivity_analysis()), and for combinations of these. See the sensitivity example.

sensitivity_plot draws the bound of the effect (value="theta") or of its confidence interval ("ci") as contours over a grid of cf_d and cf_y (matplotlib, installed separately), marking the unadjusted estimate, the scenario of the last sensitivity_analysis and any benchmarks, as DoubleML's sensitivity_plot. sensitivity_contours returns the grid for other plotting libraries.

Learner diagnostics

nuisance_loss_ and evaluate_learners report how well the nuisances were learned, on held-out data; see Learners.