statspai.dml¶
dml ¶
Double/Debiased Machine Learning module for StatsPAI.
Implements the Chernozhukov et al. (2018) framework with separate per-model estimator classes (PLR / IRM / PLIV / IIVM) sharing a common cross-fitting infrastructure.
Public entry points:
- :func:
dml— dispatcher, selects the model viamodel=string. - :class:
DoubleML— legacy façade, delegates to a per-model class. - :class:
DoubleMLPLR, :class:DoubleMLIRM, :class:DoubleMLPLIV, :class:DoubleMLIIVM— direct per-model entry points.
References
Chernozhukov, V., Chetverikov, D., Demirer, M., Duflo, E., Hansen, C., Newey, W., and Robins, J. (2018). "Double/Debiased Machine Learning for Treatment and Structural Parameters." Econometrics Journal, 21(1), C1-C68. [@chernozhukov2018double]
DoubleML ¶
Legacy façade. Prefer the per-model classes directly for new code:
:class:DoubleMLPLR, :class:DoubleMLIRM, :class:DoubleMLPLIV,
:class:DoubleMLIIVM. Kept for backward compatibility.
DoubleMLPLR ¶
Bases: _DoubleMLBase
Partially linear regression DML (continuous or binary D, no IV).
DoubleMLIRM ¶
Bases: _DoubleMLBase
Interactive regression DML — binary D, ATE via AIPW.
DoubleMLPLIV ¶
Bases: _DoubleMLBase
Partially linear IV DML — endogenous D with continuous/binary Z.
DoubleMLIIVM ¶
Bases: _DoubleMLBase
Interactive IV DML — binary D, binary Z, LATE via Wald.
DMLAveragingResult ¶
Bases: CausalResult
CausalResult extended with per-candidate and weight details.
Attributes stored in model_info:
candidates— list of candidate labels.theta_k— per-candidate :math:\hat\theta(only meaningful for the non-stacking weight rules).se_k— per-candidate SE.mse_k— per-candidate nuisance risk (g + m).weights— averaging or stacking weights.weights_g/weights_m— separate stacking weights per nuisance underweight_rule="short_stacking".weight_rule— how the weights were computed.
DMLPanelResult
dataclass
¶
Output of :func:dml_panel.
Attributes:
| Name | Type | Description |
|---|---|---|
estimate |
float
|
Debiased treatment effect β̂. |
se |
float
|
Cluster-robust SE at the unit level. |
ci_lower, ci_upper |
float
|
|
p_value |
float
|
|
t_stat |
float
|
|
n_units |
int
|
|
n_obs |
int
|
|
n_folds |
int
|
|
include_time_fe |
bool
|
|
ml_g_name |
str
|
Short name of the outcome nuisance learner. |
ml_m_name |
str
|
Short name of the treatment nuisance learner. |
method |
str
|
Always |
diagnostics |
dict
|
Populated with |
DMLSensitivityResult
dataclass
¶
Output of :func:dml_sensitivity.
Attributes:
| Name | Type | Description |
|---|---|---|
estimate |
float
|
Original DML point estimate. |
se |
float
|
Original DML standard error. |
rv_q |
float
|
Robustness value at the user's q-threshold (default |
rv_qa |
float
|
Confounder strength needed to push the (1-α)·100% lower CI
across zero. Strictly less than or equal to |
bias_bound |
float
|
Maximum |bias| under the user-specified |
adjusted_estimate_low |
float
|
|
adjusted_estimate_high |
float
|
Bias-adjusted estimate range under the (cf_y, cf_d) scenario. |
benchmarks |
DataFrame
|
For each benchmark covariate |
s |
float
|
Scaling factor :math: |
q |
float
|
|
alpha |
float
|
|
method |
str
|
Always |
plot ¶
Plot bias-contour grid for hypothetical (cf_y, cf_d) pairs.
Plots the |bias|/|θ̂| contour as a function of the confounder
strength on Y and D. The user can read off the RV directly.
Mirrors the sensemakr plotting convention.
DMLDiagnostics
dataclass
¶
Bundled DML diagnostics returned by :func:dml_diagnostics.
plot ¶
Render a 2×2 publication-style diagnostic panel.
Top-left : overlap histogram (propensity for IRM, |D-resid| for PLR). Top-right : score-density histogram with N(0,σ̂²) overlay. Bottom-left: balance bar chart (corr with each residualised covariate). Bottom-right: Q-Q plot of standardised score vs N(0,1).
dml ¶
dml(data: DataFrame, y: str, treat: str, covariates: List[str], model: str = 'plr', instrument: Optional[Union[str, List[str]]] = None, ml_g: Optional[Any] = None, ml_m: Optional[Any] = None, ml_r: Optional[Any] = None, n_folds: int = 5, n_rep: int = 1, alpha: float = 0.05, random_state: int = 42, sample_weight: Optional[Any] = None) -> CausalResult
Estimate causal effect using Double/Debiased Machine Learning.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
|
required |
y
|
str
|
Outcome variable. |
required |
treat
|
str
|
Treatment variable. |
required |
covariates
|
list of str
|
High-dimensional controls X. |
required |
model
|
str
|
DML model:
|
'plr'
|
instrument
|
str
|
Scalar instrument Z. Required for |
None
|
ml_g
|
sklearn estimator or str
|
Learners for outcome / treatment / instrument nuisance. Pass any scikit-learn-compatible estimator, or one of the convenience string aliases (case-insensitive):
For binary treatment ( |
None
|
ml_m
|
sklearn estimator or str
|
Learners for outcome / treatment / instrument nuisance. Pass any scikit-learn-compatible estimator, or one of the convenience string aliases (case-insensitive):
For binary treatment ( |
None
|
ml_r
|
sklearn estimator or str
|
Learners for outcome / treatment / instrument nuisance. Pass any scikit-learn-compatible estimator, or one of the convenience string aliases (case-insensitive):
For binary treatment ( |
None
|
n_folds
|
int
|
|
5
|
n_rep
|
int
|
Repeated cross-fitting splits (median aggregation). |
1
|
alpha
|
float
|
|
0.05
|
random_state
|
int
|
Seed for the cross-fitting fold assignment. Repeat splits use
|
42
|
sample_weight
|
ndarray | Series | str
|
Per-observation weights (e.g., survey/probability weights). Pass
either a 1-D array of length |
None
|
Returns:
| Type | Description |
|---|---|
CausalResult
|
|
Examples:
>>> # Partially Linear Regression
>>> result = sp.dml(df, y='wage', treat='training',
... covariates=['age', 'edu', 'exp'])
>>> # Interactive Regression (binary treatment, ATE)
>>> result = sp.dml(df, y='wage', treat='D', covariates=X_cols,
... model='irm')
dml_model_averaging ¶
dml_model_averaging(data: DataFrame, y: str, treat: str, covariates: Sequence[str], candidates: Optional[List[Tuple[Any, Any, str]]] = None, n_folds: int = 5, seed: int = 0, weight_rule: str = 'short_stacking', alpha: float = 0.05, sample_weight: Optional[Any] = None) -> DMLAveragingResult
Model-averaging / stacking DML-PLR estimator.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
|
required |
y
|
str
|
Outcome column. |
required |
treat
|
str
|
Continuous-or-binary treatment column. |
required |
covariates
|
list of str
|
Covariate columns |
required |
candidates
|
list of (ml_g, ml_m, label)
|
Candidate nuisance learners. |
None
|
n_folds
|
int
|
Cross-fitting folds per candidate. |
5
|
seed
|
int
|
|
0
|
weight_rule
|
('short_stacking', 'single_best', 'inverse_risk', 'equal')
|
How to combine candidate nuisance predictions or estimates.
For the non-stacking rules ( |
"short_stacking"
|
alpha
|
float
|
Two-sided CI level. |
0.05
|
sample_weight
|
ndarray | Series | str
|
Per-observation weights. If supplied, every nuisance fit uses
|
None
|
Returns:
| Type | Description |
|---|---|
DMLAveragingResult
|
With the weighted :math: |
Examples:
>>> import statspai as sp
>>> r = sp.dml_model_averaging(df, y="y", treat="d",
... covariates=[f"x{j}" for j in range(10)])
>>> r.summary()
>>> r.model_info["weights_g"] # CLS stacking weights for ℓ̂(X) = E[Y|X]
{"lasso": 0.42, "ridge": 0.0, "rf": 0.0, "gbm": 0.58}
>>> r.model_info["weights_m"] # CLS stacking weights for m̂(X) = E[D|X]
{"lasso": 0.0, "ridge": 0.31, "rf": 0.0, "gbm": 0.69}
dml_panel ¶
dml_panel(data: DataFrame, y: str, treat: str, covariates: List[str], *, unit: str, time: Optional[str] = None, ml_g: Optional[Any] = None, ml_m: Optional[Any] = None, n_folds: int = 5, alpha: float = 0.05, include_time_fe: bool = False, binary_treatment: bool = False, seed: int = 0, sample_weight: Optional[Any] = None) -> DMLPanelResult
Long-panel Double/Debiased ML with unit FE and cluster-robust SE.
Estimates β in
.. math::
Y_{it} = \alpha_i + \lambda_t + \beta D_{it} + g(X_{it}) + \varepsilon_{it}
by (1) within-transforming y, treat and covariates to
absorb unit (and optionally time) fixed effects; (2) running
cross-fit PLR on the demeaned data with folds that split units;
(3) computing the Neyman-orthogonal score and cluster-robust SE at
the unit level.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
Long-format panel; must contain |
required |
y
|
str
|
Column names. |
required |
treat
|
str
|
Column names. |
required |
covariates
|
list of str
|
High-dimensional controls X_it. Pass |
required |
unit
|
str
|
Unit identifier column. Used both for FE absorption and cross-fit fold assignment. |
required |
time
|
str
|
Time identifier column; required when |
None
|
ml_g
|
sklearn-style estimators
|
Nuisance learners. Default: GradientBoosting with 200 trees,
depth 3, lr 0.05 — same convention as :func: |
None
|
ml_m
|
sklearn-style estimators
|
Nuisance learners. Default: GradientBoosting with 200 trees,
depth 3, lr 0.05 — same convention as :func: |
None
|
n_folds
|
int
|
Cross-fit folds over units. Must be >= 2 and <= n_units. |
5
|
alpha
|
float
|
|
0.05
|
include_time_fe
|
bool
|
If True, also subtract time means (two-way within transform). |
False
|
binary_treatment
|
bool
|
Deprecated. Previously routed binary D through a classifier
path that mixed within-demeaned features with raw {0,1} labels —
the resulting propensity had no clean interpretation as
:math: |
False
|
seed
|
int
|
RNG seed for fold assignment. |
0
|
sample_weight
|
ndarray | Series | str
|
Per-observation weights (e.g., survey/probability weights). Pass
either a 1-D array of length |
None
|
Returns:
| Type | Description |
|---|---|
class:`DMLPanelResult`
|
|
Notes
The cluster-robust SE follows Liang-Zeger 1986 at the unit level (the coarser of the two dimensions): stacked scores are summed within unit before squaring. This is the appropriate clustering level when shocks at higher frequencies than the unit are plausibly correlated (cf. Cameron-Miller 2015 §3.2).
Identification requires no unobserved time-varying confounders —
only time-invariant unit heterogeneity + high-dim observed X_it.
Violations of strict exogeneity of D (e.g. dynamic-feedback) are
not handled here; use :func:sp.msm or :func:sp.gformula_ice.
Examples:
dml_sensitivity ¶
dml_sensitivity(result, q: float = 1.0, cf_y: Optional[float] = None, cf_d: Optional[float] = None, benchmark_covariates: Optional[Sequence[str]] = None, k_y: float = 1.0, k_d: float = 1.0) -> DMLSensitivityResult
Compute DML-OVB sensitivity for a fitted DML CausalResult.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
result
|
CausalResult
|
From :func: |
required |
q
|
float
|
Bias threshold as a fraction of |θ̂|. |
1.0
|
cf_y
|
float
|
Hypothesized partial-R² of an unobserved confounder with the residualised outcome and treatment. If both are given, the report includes a bias bound and adjusted-estimate range. |
None
|
cf_d
|
float
|
Hypothesized partial-R² of an unobserved confounder with the residualised outcome and treatment. If both are given, the report includes a bias bound and adjusted-estimate range. |
None
|
benchmark_covariates
|
list of str
|
Subset of the original covariates to benchmark against. For each
|
None
|
k_y
|
float
|
Multipliers for the benchmark strengths. |
1.0
|
k_d
|
float
|
Multipliers for the benchmark strengths. |
1.0
|
Returns:
| Type | Description |
|---|---|
DMLSensitivityResult
|
|
dml_diagnostics ¶
dml_diagnostics(result, clip: float = 0.02) -> DMLDiagnostics
Build a :class:DMLDiagnostics report from a DML CausalResult.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
result
|
CausalResult
|
Result returned by :func: |
required |
clip
|
float
|
For IRM-style overlap: count units with propensity within
|
0.02
|
Returns:
| Type | Description |
|---|---|
DMLDiagnostics
|
|