Causal Effects from Observational Data

11 min read Module 4 of 9 Lesson 12 of 25

What you'll learn

  • State the assumptions needed to estimate causal effects from observational data: no unmeasured confounding, overlap, and consistency
  • Estimate effects with inverse propensity weighting and doubly robust methods, and check overlap
  • Use double machine learning and causal forests (EconML) to estimate heterogeneous effects with confidence intervals
  • Stress-test estimates with refutation and sensitivity analysis (DoWhy), and spot natural experiments such as cutoffs
Building this at your company? For enterprise and company teams taking this to production: book a 30-minute session with our AI engineers for architecture guidance, code review, and a rollout plan for your use case.
Book a Team Session

The last lesson assumed randomized data. Often you don’t have it: the programme ran for years under a targeting policy, randomizing now would take a quarter to read out, or randomizing is unethical or illegal for the decision at hand. You still need effect estimates.

You can get them from observational data, under assumptions. This lesson covers the methods, the assumptions they rest on, and the checks that keep you honest. Treat everything here as weaker evidence than a randomized test, and validate against randomized data whenever you can get some.

The assumptions

Estimating causal effects from logs requires three things:

  1. No unmeasured confounding (also called ignorability or exchangeability): every variable that influenced both the treatment decision and the outcome is in your data. This is untestable from data alone. It’s a claim about how the historical decisions were made, which is why the DAG conversation in Lesson 10 matters.
  2. Overlap (positivity): for every type of unit, there was some chance of both receiving and not receiving the treatment. If the old policy always called customers with engagement above 2, the data contains no information about what happens when you don’t call them.
  3. Consistency: the treatment is well defined. “Got a retention offer” covering five different offers isn’t one treatment.

Everything below is machinery for using these assumptions efficiently. None of it rescues you if they fail.

Propensity scores and weighting

The propensity score e(x) = P(T = 1 | X = x) is the probability of being treated given the features. If the confounders are all in X, then within groups of equal propensity, treatment is as good as random.

Inverse propensity weighting (IPW) reweights the data so the treated and control groups look like the whole population: each treated unit counts 1/e(x), each control 1/(1 − e(x)).

import numpy as np
from sklearn.ensemble import HistGradientBoostingClassifier

# X: confounders (pre-treatment features only!), t: 0/1 treatment, y: outcome
ps_model = HistGradientBoostingClassifier(max_iter=300).fit(X, t)
e = ps_model.predict_proba(X)[:, 1]

# Overlap check: look at the propensity distributions per group.
print("treated e range:", np.percentile(e[t == 1], [1, 50, 99]))
print("control e range:", np.percentile(e[t == 0], [1, 50, 99]))

# Trim to the region of common support, then weight.
keep = (e > 0.02) & (e < 0.98)
w = np.where(t == 1, 1 / e, 1 / (1 - e))[keep]
ate_ipw = (np.sum(w * y[keep] * t[keep]) / np.sum(w * t[keep])
           - np.sum(w * y[keep] * (1 - t[keep])) / np.sum(w * (1 - t[keep])))

Two rules that save teams from embarrassing results:

  • Only pre-treatment features. Anything measured after the treatment (including things the treatment influenced) must not be in X. This is the most common leakage bug in causal pipelines.
  • Check overlap before estimating. If propensities pile up near 0 or 1, some units carry enormous weights and the estimate becomes a coin toss. Trim to common support and be explicit that the estimate only applies to that population.

The trimming threshold changes the population you’re describing. That’s fine, as long as you say so: “among customers with between 2% and 98% chance of being called under the old policy, calling changes retention by…”.

Doubly robust estimation

IPW depends entirely on the propensity model; outcome regression depends entirely on the outcome model. Doubly robust estimators (augmented IPW) combine them: fit outcome models μ₁(x), μ₀(x) and a propensity model e(x), and estimate

τ̂ = mean[ μ₁(x) − μ₀(x)
          + t·(y − μ₁(x))/e(x)
          − (1 − t)·(y − μ₀(x))/(1 − e(x)) ]

If the outcome model is right, the correction terms average to zero. If the propensity model is right, they correct the outcome model’s bias. The estimate is consistent if either is correct, and in practice it’s usually more stable than either alone. You’ll meet the same estimator again for off-policy evaluation in Lesson 15.

Fit the nuisance models with cross-fitting: train on one fold, predict on another, so each unit’s predictions come from models that didn’t see it. Without it, flexible ML models overfit and bias the estimate.

Double machine learning and causal forests

For targeting you need heterogeneous effects (CATE), with uncertainty. Double machine learning (DML) (Chernozhukov et al., 2018) does this by “partialling out”: predict the outcome from confounders, predict the treatment from confounders, and regress the outcome residuals on the treatment residuals. What’s left is the effect, free of the confounders’ influence, with valid confidence intervals even when the nuisance models are flexible ML.

Causal forests combine this with a random-forest-style estimator of how the effect varies with features. EconML implements both:

from econml.dml import CausalForestDML
from sklearn.ensemble import HistGradientBoostingRegressor, HistGradientBoostingClassifier

est = CausalForestDML(
    model_y=HistGradientBoostingRegressor(),   # outcome nuisance model
    model_t=HistGradientBoostingClassifier(),  # propensity nuisance model
    discrete_treatment=True,
    cv=5,                                      # cross-fitting folds
    random_state=0,
)
# X: features the effect may vary with; W: extra confounders to adjust for
est.fit(Y=y, T=t, X=X_effect, W=W_confounders)

cate = est.effect(X_new)                         # per-customer uplift
lo, hi = est.effect_interval(X_new, alpha=0.1)   # 90% intervals

The intervals are useful in decisions. Customers whose effect interval excludes zero can be targeted confidently; those whose interval straddles zero are candidates for exploration (Module 5) rather than confident action.

Refute before you trust

Observational estimates can be wrong in ways no metric reveals. DoWhy structures the workflow as model (write the DAG), identify (derive what to adjust for), estimate, and refute:

from dowhy import CausalModel

model = CausalModel(data=df, treatment="called", outcome="retained",
                    graph=dag_string)          # the DAG from Lesson 10
estimand = model.identify_effect()
estimate = model.estimate_effect(estimand,
                                 method_name="backdoor.propensity_score_weighting")

# Placebo: a random treatment should have ~zero effect.
model.refute_estimate(estimand, estimate, method_name="placebo_treatment_refuter")
# Adding a random common cause should not move the estimate much.
model.refute_estimate(estimand, estimate, method_name="random_common_cause")
# Re-estimating on subsets should give similar results.
model.refute_estimate(estimand, estimate, method_name="data_subset_refuter")

Also run a sensitivity analysis: how strong would an unmeasured confounder have to be to explain away the effect? If a confounder as strong as tenure would erase it, be cautious. If it would need to be ten times stronger than anything you’ve measured, the estimate is sturdier.

And the strongest check of all: compare against a randomized benchmark. If your permanent holdout (Lesson 10) covers even a slice of the population, compare observational and randomized estimates on that slice. Large disagreement means the observational pipeline is missing something.

Natural experiments in your own rules

Decision systems are full of accidental experiments. Look for them:

  • Cutoffs → regression discontinuity. Approved at credit score ≥ 680, free shipping above $50, VIP status at 10 orders. Units just either side of the cutoff are nearly identical, so the jump in outcomes at the cutoff estimates the effect locally.
  • Rollouts → difference-in-differences. A new policy launched in some regions before others. Compare the change over time in launched regions with the change in not-yet-launched ones.
  • Arbitrary assignment. Cases routed to reviewers by queue order, offers capped when the daily budget ran out at a random time of day. If assignment was effectively random for reasons unrelated to outcomes, it’s an experiment.

These designs often give more credible estimates than any amount of propensity modeling, because their assumptions are easier to defend. They also suggest a design principle: when you write a new hard threshold into a decision system, you’re creating a future natural experiment. Log the running variable.

With effects estimated, by randomization or carefully from logs, the decision stack can finally target the right customers. But there’s a gap: what about actions you’ve never tried, and worlds that keep changing? That’s Module 5.

Knowledge Check

4 questions to test your understanding

1 Inverse propensity weights for some treated customers are above 500. What does this indicate?

2 What makes an estimator 'doubly robust'?

3 Loans were approved automatically for credit scores at or above 680 and declined below. How can this help estimate the effect of approval on later financial health?

4 A placebo refutation test replaces the real treatment with a random one and re-estimates. The estimated effect stays large. What should you conclude?

Go further with expert guidance

Ready to build production AI?
Talk to our R&D team.

These courses give you the foundation. Our embedded AI teams take you from prototype to production in 30–90 days, with your team, your codebase, your goals. Book a free strategy call to see how we can accelerate your AI initiative.

30 minutes · No obligation · Expert AI engineers, not sales reps

AI Architecture Review

Audit your current stack and identify high-impact improvements

Project Review

Get expert feedback on your AI implementation and codebase

Team Mentoring

Upskill your engineers with hands-on AI coaching sessions

AI Strategy

Define your AI roadmap, prioritization, and implementation plan