An example of Double Machine Learning using Conformal Prediction
This notebook illustrates an early-stage demonstration of the potential for the use of conformal predictions in the double machine learning framework.
The main motivation for bringing conformal predictions into double machine learning framework is to:
Propagate uncertainty in nuisance model predictions to causal inference
Reduce computational burden by avoiding cross-fitting
The below introduces some of this motivation in more detail, and shows a particular example using simulated data (from make_plr_CCDDHNR2018()).
What is Conformal Prediction?
Conformal prediction (a.k.a. conformal inference) is a user-friendly paradigm for creating statistically rigorous uncertainty sets/intervals for the predictions of such models. Critically, the sets are valid in a distribution-free sense: they possess explicit, non-asymptotic guarantees even without distributional assumptions or model assumptions.
Why Conformal Double Machine Learning?
Sample splitting, typically in the form of cross-fitting, is one of the key features of standard Frequentist Double Machine Learning (FDML) which aims to solve the issue of over-fitting bias.
Cross-fitting alleviates the "potential dependence between nuisance estimates and parts of the data used for estimating the target parameter" (Ahrens et al (2025)).
As stated in Ahrens et al (2025), p 4:
"...Because \(\hat{\eta}\) is an estimator, it is itself a random function of the data. \(\hat{\eta}\) is thus generally correlated with the observations \(\{W_i\}_{i=1}^n\) also used in the estimating equation \(\frac{1}{n}\sum_{i=1}^n m(W_i; \theta, \hat{\eta})\). When this dependence is strong, for example due to "overfitting", it may generate large differences between \(\frac{1}{n}\sum_{i=1}^n m(W_i; \theta, \hat{\eta})\) and \(\frac{1}{n}\sum_{i=1}^n m(W_i; \theta, \eta_0)\), which results in poor performance of \(\hat{\theta}\)."
In practice, however, there are a few issues that cross-fitting does not resolve:
First, in the presence of large data, a practical issue is that cross-fitting can be computationally costly as it requires fitting a model at least 1 time for each fold of the cross-validation set.
Second, and more fundamentally, a theoretical issue is that cross-fitting does not account for uncertainty in the predictions \(\hat{\eta}\), but rather treats them as point estimates. Any uncertainty in these point estimates is not propagated into the causal inference for \(\theta\). Thus, FDML estimates of the causal parameter, \(\hat{\theta}\), often do not have good coverage - e.g., using simulated data where the true causal effect is known, FDML often leads to 95% confidence intervals which include the true effect less than 95% of the time.
As shown in this notebook, however, over-fitting bias can be alleviated without cross-fitting! If we instead think of our estimates for \(\hat{\eta}\) as following some joint probability distribution, we can simply fit one time, using one holdout set for the conformal prediction calibration; we can then directly account for the uncertainty in our nuisance estimates, and propagate this uncertainty through to the final inference stage.
By sampling from the joint probability distribution for each prediction from the nuisance models, the hypothesis is that we can maintain Neyman orthogonality and avoid over-fitting bias, and make better inference decisions by improving uncertainty quantification.
begin
using DoubleML
using ConformalPrediction # This triggers loading of DoubleMLConformalExt
using MLJ
using StableRNGs
using Random
using DataFrames
end
Load MLJ models
Let's experiment with EvoTrees, RandomForest, and Symbolic Regression.
begin # loading MLJ models
EvoTreeRegressor = @load EvoTreeRegressor pkg = EvoTrees verbosity = 0
RandomForestRegressor = @load RandomForestRegressor pkg = BetaML verbosity = 0
SRRegressor = @load SRRegressor pkg = SymbolicRegression verbosity = 0
end;
Ensure the DoubleMLConformalExt is accessible
Below, we get the extension for estimating Conformal Double Machine Learning models.
This is implemented as a package extension as it remains experimental.
const Ext = Base.get_extension(DoubleML, :DoubleMLConformalExt)
DoubleMLConformalExt
Data generation
The below is specifically a counter-example to show where standard DML may fail in terms of coverage, where conformal DML may succeed. A large-scale assessment across multiple random seeds would be needed for a more comprehensive evaluation of the performance of the different methods.
begin
seed = 1330
rng = StableRNG(seed)
true_alpha = 0.5
n_obs = 500
dim_x = 200
data = make_plr_CCDDHNR2018(n_obs; dim_x = dim_x, alpha = true_alpha, rng = rng)
end
DoubleMLData{Float32, Vector{Float32}}(Float32[0.18091363, 2.067306, 3.664946, -1.274438, -0.3135039, -0.7701451, -2.0895598, -0.19495754, 2.1093516, -0.400096 … 1.4303278, 0.6266101, 1.8354708, 2.851835, 0.23475152, 2.9664168, 1.3354713, 1.1360786, 0.43573543, 0.62686586], Float32[-0.17868796, 1.9909229, 0.7343608, 0.198705, 1.2223384, -0.38353926, -3.2097826, 0.17862228, 1.5148652, 0.044839736 … -0.27187628, -0.27182662, 1.4056993, 1.7070878, 1.8723645, 0.25217316, 0.6925375, 1.0002018, 0.71817034, -0.09409587], Float32[-0.111322954 0.7321712 … 1.3219721 -0.57778966; 1.667957 0.704824 … -0.1738328 -1.7231665; … ; -0.5558633 -0.78059614 … 0.23636128 -1.588617; -0.48422313 -0.51050246 … -0.9031398 -0.49341583], 500, 200, :y, :d, [:X1, :X2, :X3, :X4, :X5, :X6, :X7, :X8, :X9, :X10 … :X191, :X192, :X193, :X194, :X195, :X196, :X197, :X198, :X199, :X200])
Estimating a Conformal Double Machine Learning (CDML) model
begin
Random.seed!(seed)
# Set the coverage for the nuisance models.
coverage = 0.95
ml_l = conformal_model(
RandomForestRegressor(rng = rng);
method = :simple_inductive,
coverage = coverage
)
ml_m = conformal_model(
RandomForestRegressor(rng = rng);
method = :simple_inductive,
coverage = coverage
)
# Create and fit conformal model
model_conformal = Ext.DoubleMLPLRConformal(data, ml_l, ml_m; n_mc_samples = 1_000)
@time Ext.fit!(model_conformal, rng = rng, verbose = 0)
coeftable(model_conformal)
end
──────────────────────────────────────────────────────────────────── Estimate Std. Error z value Pr(>|z|) Lower 95.0% Upper 95.0% ──────────────────────────────────────────────────────────────────── d 0.574494 0.0446105 12.88 <1e-37 0.486311 0.658291 ────────────────────────────────────────────────────────────────────
# Test against standard PLR model
begin
Random.seed!(seed)
model = DoubleMLPLR(
data,
RandomForestRegressor(rng = rng),
RandomForestRegressor(rng = rng);
n_folds = 5,
n_rep = 1
)
@time DoubleML.fit!(model; verbose = 0)
coeftable(model)
end
──────────────────────────────────────────────────────────────────── Estimate Std. Error z value Pr(>|z|) Lower 95.0% Upper 95.0% ──────────────────────────────────────────────────────────────────── d 0.577848 0.0362414 15.94 <1e-56 0.506816 0.64888 ────────────────────────────────────────────────────────────────────
The above example shows that the conformal model ran in roughly 4-5x faster than the standard model that uses 5-fold cross-fitting. In addition, the conformal model includes the true causal effect, whereas the standard model does not.
As noted above, this is a bit of a contrived example. In anecdotal testing, the conformal model appears to give better empirical coverage. However, more extensive testing is needed to properly evaluate the performance of the different methods.
How does Conformal Double Machine Learning work?
This package, and the implementation of Conformal Double Machine Learning, remain experimental.
Currently, the main implementation of CDML (DoubleMLPLRConformal) works by:
Training the conformal models without cross-fitting. Users may specify
train_ratiofor some conformal prediction methods, but predictions are made on the full datasetObtaining conformal predictions (i.e, a tuple of a lower and upper bound for each prediction). These conformal predictions guaranteed a user-defined coverage level (e.g., 95%).
Use Monte Carlo sampling from conformal prediction intervals to propagate uncertainty, using Beta(2,2) marginals with Gaussian copula to account for correlation between the uncertainties in predictions for the outcome \(\hat{l}(x)\) and treatment \(\hat{m}(x)\).
Alternative CDML variant using the Unscented Transform (UT)
Monte Carlo sampling can be computationally intensive and stochastic. Instead of MC, this variant propagates the uncertainty in the nuisance parameters deterministically through the DML2 score function using the unscented transform at second-order accuracy.
The UT variant of the CDML model (DoubleMLPLRConformalUT) currently works as follows:
Workflow
Conformal model fitting: The conformal-wrapped learners
ml_landml_mare trained to obtain prediction intervals. Users may specifyn_foldsandn_repfor K-fold cross-fitting (defaultn_folds=1fits on the full dataset).Conformal predictions: A lower and upper bound are obtained for each prediction, guaranteeing the user-defined coverage level (e.g., 95%).
Uncertainty modeling: Prediction errors within each interval are modeled with Beta(2,2) marginals. The correlation between the errors in \(\hat{l}(x)\) and \(\hat{m}(x)\) is estimated from residuals and its uncertainty is represented via the Fisher z-transform\(z \sim N(\hat{z}, 1/(n-3))\).
Closed-form moment propagation: For any fixed correlation, the means and covariances of the aggregated score statistics \((\bar{A}, \bar{B})\) are computed in closed form (O(n)) using a Gaussian copula (Isserlis cross-moments).
2D Unscented Transform: The score function is propagated via the standard 2D UT (5 sigma points), yielding a mean that corrects for Jensen bias and a variance that captures second-order effects.
Correlation uncertainty integration: The UT is evaluated at Gauss-Hermite quadrature points (default 3, optional 5) over the Fisher z-transform distribution. The final result mixes these components by the law of total variance.
Combined inference: The reported coefficient is the UT mean. The reported standard error combines the standard DML sampling SE with the conformal-only UT variance: \(SE_{total} = \sqrt{SE_{DML}^2 + Var_{UT}}\).
# Bonus: using the Unscented Transform for uncertainty propagation
begin
Random.seed!(seed)
# Create and fit conformal model (UT)
model_conformal_ut = Ext.DoubleMLPLRConformalUT(
data,
conformal_model(
RandomForestRegressor(rng = rng);
method = :simple_inductive,
coverage = coverage
),
conformal_model(
RandomForestRegressor(rng = rng);
method = :simple_inductive,
coverage = coverage
);
n_gh = 5
)
@time Ext.fit!(model_conformal_ut, rng = rng, verbose = 0)
coeftable(model_conformal_ut)
end
──────────────────────────────────────────────────────────────────── Estimate Std. Error z value Pr(>|z|) Lower 95.0% Upper 95.0% ──────────────────────────────────────────────────────────────────── d 0.573169 0.0602595 9.51 <1e-20 0.455062 0.691275 ────────────────────────────────────────────────────────────────────
# Comparing:
coeftable(model)
──────────────────────────────────────────────────────────────────── Estimate Std. Error z value Pr(>|z|) Lower 95.0% Upper 95.0% ──────────────────────────────────────────────────────────────────── d 0.577848 0.0362414 15.94 <1e-56 0.506816 0.64888 ────────────────────────────────────────────────────────────────────
coeftable(model_conformal)
──────────────────────────────────────────────────────────────────── Estimate Std. Error z value Pr(>|z|) Lower 95.0% Upper 95.0% ──────────────────────────────────────────────────────────────────── d 0.574494 0.0446105 12.88 <1e-37 0.486311 0.658291 ────────────────────────────────────────────────────────────────────
coeftable(model_conformal_ut)
──────────────────────────────────────────────────────────────────── Estimate Std. Error z value Pr(>|z|) Lower 95.0% Upper 95.0% ──────────────────────────────────────────────────────────────────── d 0.573169 0.0602595 9.51 <1e-20 0.455062 0.691275 ────────────────────────────────────────────────────────────────────
DoubleMLPLRConformalUT (Unscented Transform uncertainty propagation)
===========================================================
Conformal method: simple_inductive
Coverage: 0.95
UT parameters: α=1.0, β=2.0, κ=1.0
GH quadrature: 5 points
Training: Without cross-fitting)
Sampling: Deterministic (no MC)
Results:
Coefficient: 0.5732
Std. Error: 0.0603 (combined)
95.0% CI: [0.4551, 0.6913]
l-m correlation: 0.532 (±0.045 in z-space)
Variance decomposition:
Standard DML: θ=0.5714, SE=0.0453
Conformal (UT): θ=0.5732, SE=0.0398
Difference: 0.0018 (UT - standard DML)