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.

Angelopoulos and Bates (2022)

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.

Hypothesis

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

Note

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
────────────────────────────────────────────────────────────────────
Results

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?

Warning

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_ratio for some conformal prediction methods, but predictions are made on the full dataset

  • Obtaining 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

  1. Conformal model fitting: The conformal-wrapped learners ml_l and ml_m are trained to obtain prediction intervals. Users may specify n_folds and n_rep for K-fold cross-fitting (default n_folds=1 fits on the full dataset).

  2. Conformal predictions: A lower and upper bound are obtained for each prediction, guaranteeing the user-defined coverage level (e.g., 95%).

  3. 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))\).

  4. 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).

  5. 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.

  6. 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.

  7. 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)