25  TMLE and Doubly Robust Estimation

Status: Draft

v0.5

25.1 Introduction

Targeted Maximum Likelihood Estimation (TMLE) is a semiparametric efficient, double robust estimator (Laan and Rubin 2006; Laan and Rose 2011; Schuler and Rose 2017). This chapter focuses on TMLE as a method for Seeing in the Observable stratum, learning from actualised data with robustness to model misspecification.

25.2 What Is TMLE?

TMLE is a semiparametric efficient, double robust estimator that:

  • Targets the causal parameter of interest
  • Uses machine learning for flexible models
  • Provides valid inference (confidence intervals, hypothesis tests)
  • Handles complex data (high-dimensional confounders, missing data)

25.2.1 Advantages

  • Double robust: Consistent if either outcome or treatment model is correct
  • Semiparametric efficient: Optimal variance among regular asymptotically linear estimators
  • Machine learning compatible: Can use Super Learner, neural networks, etc.
  • Valid inference: Confidence intervals and hypothesis tests

TMLE is one route to Neyman-orthogonal, double-robust estimation with flexible nuisance models. The econometric sibling is double/debiased machine learning (DML): cross-fitting plus orthogonal scores for average effects under high-dimensional or nonparametric nuisances (Chernozhukov et al. 2018, 2024). This chapter stays with TMLE and the LMTP bridge; for the DML presentation of the same orthogonalisation idea (partially linear and interactive regression scores rather than a targeted fluctuation step), see (Chernozhukov et al. 2024, Ch. 9).

25.3 TMLE Algorithm

Steps:

  1. Fit initial outcome model: Estimate \(Q_0(A, L) = E[Y \mid A, L]\) using flexible methods (e.g., Super Learner (Laan et al. 2007))
  2. Fit treatment model: Estimate \(g_0(A \mid L) = P(A \mid L)\) for weights
  3. Targeted update: Update the outcome model to target the treatment effect:
  • Compute “clever covariate” \(H(A, L) = \frac{\mathbb{1}(A=a)}{g_0(A \mid L)}\)
  • Fit a logistic regression: \(\text{logit}(Q_1(A, L)) = \text{logit}(Q_0(A, L)) + \epsilon H(A, L)\)
  • Update: \(Q_1(A, L) = Q_0(A, L) + \epsilon H(A, L)\)
  1. Compute effect: Estimate treatment effect from updated model
  2. Inference: Compute standard errors and confidence intervals

25.3.1 Implementation: TMLE with TMLE.jl

Here’s how to use the TMLE.jl package for robust causal effect estimation:

# Find project root and include ensure_packages.jl
project_root = let
    current = pwd()
    while !isfile(joinpath(current, "Project.toml")) && !isfile(joinpath(current, "_quarto.yml"))
        parent = dirname(current)
        parent == current && break
        current = parent
    end
    current
end
include(joinpath(dirname(Base.active_project()), "scripts", "book_bootstrap.jl"))
include(joinpath(dirname(Base.active_project()), "scripts", "biological_showcases.jl"))

@auto_using TMLE Random Distributions MLJLinearModels MLJModels DataFrames CategoricalArrays CairoMakie Statistics StableRNGs

# Observational cohort: nutrition confounds treatment and worm burden (Ch. 05b DGP)
cohort_raw = confounded_cohort_dgp(; n = 500, rng = StableRNG(42))
df = cohort_for_tmle(cohort_raw)
nutrition = df.nutrition
worm_burden = df.worm_burden

# Step 1: Define the estimand (what we want to estimate)
# ATE: average effect of treatment on worm burden adjusting for nutrition
Ψ = ATE(
    outcome = :worm_burden,
    treatment_values = (treatment = (case = 1.0, control = 0.0),),
    treatment_confounders = (treatment = [:nutrition],)
)

# Step 2: Define models for outcome and treatment
models = Dict(
    :worm_burden => with_encoder(LinearRegressor()),
    :treatment => with_encoder(LogisticClassifier(lambda = 0)),
)

# Step 3: Create TMLE estimator
tmle = Tmle(models = models)

# Step 4: Estimate the ATE
tmle_result, cache = tmle(Ψ, df; verbosity = 0)

# Step 5: Extract ATE results
ATE_estimate = estimate(tmle_result)
ATE_CI = confint(OneSampleZTest(tmle_result))
ATE_pvalue = pvalue(OneSampleZTest(tmle_result))

# Step 6: Compute counterfactual means separately for visualization
# E[Y | do(A=1)] and E[Y | do(A=0)]
Ψ_A1 = CM(
    outcome = :worm_burden,
    treatment_values = (treatment = 1.0,),
    treatment_confounders = (treatment = [:nutrition],)
)
Ψ_A0 = CM(
    outcome = :worm_burden,
    treatment_values = (treatment = 0.0,),
    treatment_confounders = (treatment = [:nutrition],)
)

cm_A1_result, cache = tmle(Ψ_A1, df; cache = cache, verbosity = 0)
cm_A0_result, cache = tmle(Ψ_A0, df; cache = cache, verbosity = 0)

E_Y_A1 = estimate(cm_A1_result)  # E[Y | do(A=1)]
E_Y_A0 = estimate(cm_A0_result)  # E[Y | do(A=0)]

# Standard error from confidence interval
# SE = (upper - lower) / (2 * z_alpha/2) where z_0.025 ≈ 1.96
se_ATE = (ATE_CI[2] - ATE_CI[1]) / (2 * 1.96)

# Compute naive estimate for comparison
treatment_numeric = [x == 1.0 ? 1 : 0 for x in df.treatment]
naive_ATE = mean(worm_burden[treatment_numeric .== 1]) - mean(worm_burden[treatment_numeric .== 0])

println("TMLE Results (treatment → worm burden, adjust nutrition):")
println("  E[W | do(T=1)] = ", round(E_Y_A1, digits=3))
println("  E[W | do(T=0)] = ", round(E_Y_A0, digits=3))
println("  ATE = ", round(ATE_estimate, digits=3))
println("  95% CI: ", ATE_CI)
println("  p-value: ", round(ATE_pvalue, digits=4))
println("\nComparison:")
println("  Naive estimate (ignoring confounding): ", round(naive_ATE, digits=3))
println("  TMLE estimate (adjusted): ", round(ATE_estimate, digits=3))
println("  Structural coefficient on continuous treatment scale: 0.7")
TMLE Results (treatment → worm burden, adjust nutrition):
  E[W | do(T=1)] = 0.093
  E[W | do(T=0)] = -0.305
  ATE = 0.4
  95% CI: (0.2821222823000251, 0.518097348794098)
  p-value: 0.0

Comparison:
  Naive estimate (ignoring confounding): 1.748
  TMLE estimate (adjusted): 0.4
  Structural coefficient on continuous treatment scale: 0.7
Figure 25.1: TMLE using TMLE.jl package for robust causal effect estimation

25.3.2 Choosing Between Methods

G-computation is preferred when:

  • Outcome models are well-understood
  • Full distribution is needed
  • Treatment strategies are complex

MSMs (IPTW) are preferred when:

  • Treatment model is well-understood
  • Simple marginal effects are sufficient
  • Implementation simplicity is important

TMLE is preferred when:

  • Robustness to model misspecification is critical
  • Semiparametric efficiency is desired
  • Valid inference (confidence intervals) is needed

25.3.3 AgeSCM: propensity and IPTW for transport (not ATE on age)

Case Study 3 uses TMLE.jl alignment for domain propensity, not a targeted ATE on mosquito age. The estimand is predictive transport (E1): MAE when a whole country is held out.

  1. Nuisance design L: replica, dates, species, experiment, rearing, status, excluding country from L when fitting P(C | L), so propensity is not trivially saturated by the hold-out label.
  2. Stabilisation: weights use full-cohort P(C*) rather than train-only marginals.
  3. Schemes T0–T4: unweighted; marginal IPTW; adjusted IPTW (T2); trimmed weights (T3); target-domain reweight (T4).

On 73,521 spectra (full LOSO), T2 adjusted IPTW reduced pooled MAE for mean spectrum OLS from 23.73 to 22.87 days. This implements the weighting step familiar from MSM/TMLE workflows (Section 11.7) while the age head remains OLS or Flux+OLS in AgeSCM. Full doubly robust targeting of a scalar age estimand on is deferred to the GraphPPL/RxInfer head (Ch 30).

25.4 Continuous exposures and LMTP

Binary ATE TMLE (above) is the right pedagogy for deterministic \(do(A=a)\). For continuous or multi-valued exposures, deterministic interventions often lack scientific interest and worsen positivity violations. Longitudinal modified treatment policies (LMTP) shift the natural value of treatment (e.g. raise exposure by one standardised unit subject to clamps) and admit cross-fitted TMLE / sequentially doubly robust estimators (Díaz et al. 2023; Díaz Muñoz and Laan 2012).

Conceptually, identification of the estimand precedes estimation. In this book’s examples, identification certificates come from the graph layer; continuous MTP / mediation grids use CausalTargeted (run_lmtp_grid, run_mediation_grid, recommend_run_options) (Díaz and Hejazi 2020; Liu et al. 2024). Keep TMLE.jl for tabular binary ATEs; reach for an LMTP / mediation estimator when the estimand is a continuous-shift curve or mediated contrast.

TipSmall-n checklist (CausalTargeted)

For continuous MTP and mediation with tens to low hundreds of units (Díaz et al. 2023; Laan and Rose 2011):

  1. Start from recommend_run_options(n; engine, n_mediators) (lean Super Learner when \(n < 80\); parallel=false by default).
  2. Inspect positivity / support (positivity_report or grid positivity=true) (Petersen et al. 2012).
  3. For mediation, sweep nested MC (mediation_n_mc_sweep) until signs and SEs stabilise (Liu et al. 2024).
  4. Report tipping-point / partial-\(R^2\) sensitivity (sensitivity_report) (Cinelli and Hazlett 2020).
  5. Treat discovery graphs as sensitivity only (merge_discovery_sensitivity!), never as silent DAG replacement.

Package prose and DOIs: CausalTargeted methods · references. Worked δ-grid: Chapter 23 (Policy Evaluation).

A worked δ-grid appears in Policy Evaluation. Here is a compact mediation bite with the lean SuperLearner library:

project_root = let
    current = pwd()
    while !isfile(joinpath(current, "Project.toml")) && !isfile(joinpath(current, "_quarto.yml"))
        parent = dirname(current)
        parent == current && break
        current = parent
    end
    current
end
include(joinpath(project_root, "scripts", "ensure_packages.jl"))
@auto_using CausalTargeted DataFrames StableRNGs

df_med, truth_med = simulate_continuous_mtp_mediation(100; rng = StableRNG(20))
med_grid = run_mediation_grid(
    df_med, :A, :Y;
    covar = [:W],
    mediators = [:M],
    deltas = [0.5],
    folds = 2,
    n_mc = 8,
    learners = DEFAULT_SL_LEARNERS,
    parallel = false,
    rng = StableRNG(21),
)
nde = only(filter(r -> r.estimand == "NDE", eachrow(med_grid))).est
nie = only(filter(r -> r.estimand == "NIE", eachrow(med_grid))).est
eff = truth_med.effects(0.5)  # nominal δ in SD units (inactive clamp ≈ δ)
println("Mediation grid (δ = 0.5): NDE ≈ ", round(nde; digits = 3),
    ", NIE ≈ ", round(nie; digits = 3))
println("Oracle (approx.): NDE = ", round(eff.nde; digits = 3),
    ", NIE = ", round(eff.nie; digits = 3))
Mediation grid (δ = 0.5): NDE ≈ 0.189, NIE ≈ 0.173
Oracle (approx.): NDE = 0.175, NIE = 0.192

25.5 Stratum context

This chapter addresses Seeing in the Observable stratum: what can we observe/learn from actualised data? TMLE provides robust estimation from observable data, bridging the Observable stratum (what we observe) with the Structural stratum (what would happen under interventions).

25.6 Key Takeaways

  1. TMLE is double robust and semiparametric efficient
  2. TMLE algorithm combines outcome and treatment models with targeted update
  3. TMLE is preferred when robustness and valid inference are critical
  4. TMLE bridges Observable (data) and Structural (interventions)
  5. Continuous / longitudinal MTPs use CausalTargeted.jl (Ch. 23), not only binary TMLE.jl

25.7 Further Reading

  • Laan and Rubin (2006): “Targeted maximum likelihood learning”
  • Laan and Rose (2011): “Targeted learning”
  • Schuler and Rose (2017): “Targeted learning in R”
  • Chernozhukov et al. (2018): Double/debiased machine learning for treatment and structural parameters
  • Chernozhukov et al. (2024): Applied Causal Inference Powered by ML and AI, Chapter 9 (generic DML; Double Lasso precursor in Ch. 4)
  • Díaz et al. (2023): Nonparametric causal effects based on longitudinal modified treatment policies
  • Díaz and Hejazi (2020): Causal mediation analysis for stochastic interventions
  • Observational Methods: Learning from Data: G-methods and IPTW
  • Policy Evaluation: Continuous MTP grids
  • Model Validation with Observable Data: Validating TMLE results