Here’s a pattern that shows up in nearly every company that has been making decisions for a while. The data science team trains a model on historical data to decide who should get an action. The model finds that the action is associated with great outcomes. They deploy it. The outcomes don’t improve, or get worse.
The reason is that historical data doesn’t record what actions do. It records what past decision makers chose, and what happened afterwards. Those choices weren’t random. Whatever drove them also drives the outcome, and a predictive model can’t tell the difference.
This module is about estimating what actions actually change. It’s the most important and most neglected part of decision modeling.
Potential outcomes
The cleanest way to think about action effects is potential outcomes. For each customer and each action, imagine the outcome that would happen if that action were taken:
- Y(1): whether the customer is retained if they get the offer
- Y(0): whether they’re retained if they don’t
The individual treatment effect is Y(1) − Y(0). The problem, sometimes called the fundamental problem of causal inference, is that you only ever see one of the two. A customer either got the offer or didn’t. The other outcome is counterfactual.
So we estimate averages instead:
- ATE (average treatment effect): E[Y(1) − Y(0)] over everyone. “Does the offer work on average?”
- CATE (conditional average treatment effect): E[Y(1) − Y(0) | X = x] for customers with features x. “Does it work for customers like this one?”
Targeting decisions need CATE. You want to give the action to the customers for whom it changes the outcome most, relative to its cost. A churn probability is neither of these. It’s E[Y | X] under whatever the old policy did, a mixture of potential outcomes weighted by who happened to get treated.
How past policies confound data
A concrete example. Historically, account managers called customers they had good relationships with: high engagement, long tenure. Those customers were already likely to stay. Simulate it:
import numpy as np
rng = np.random.default_rng(1)
n = 50_000
engagement = rng.normal(0, 1, n)
# Old policy: managers call engaged customers far more often
p_call = 1 / (1 + np.exp(-(2.0 * engagement - 1.5)))
called = rng.random(n) < p_call
# True world: retention driven by engagement; the call itself
# has a SMALL NEGATIVE effect (some customers find it pushy).
p_retain = 1 / (1 + np.exp(-(0.3 + 1.2 * engagement - 0.10 * called)))
retained = rng.random(n) < p_retain
print("retention | called: ", retained[called].mean()) # ~0.75
print("retention | not called:", retained[~called].mean()) # ~0.48
The naive comparison says calls lift retention by more than 25 points. The truth is that calls slightly hurt. Any model trained on these logs, with or without the call as a feature, will learn some version of “calls are great”, and a decision system built on it will scale up calls, waste the account team’s time, and lose a few customers in the process.
This is confounding: engagement causes both the treatment (getting called) and the outcome (staying). Every historical decision log is confounded by the policy that generated it, unless that policy was random.
Causal graphs
A directed acyclic graph (DAG) makes assumptions about cause and effect explicit. Nodes are variables; arrows point from causes to effects.
flowchart LR
E["Engagement"] --> C["Called"]
E --> R["Retained"]
C --> R
C --> S["Contacted support"]
R --> S
T["Tenure"] --> C
T --> R
style E fill:#fff7ed,stroke:#f59e0b,color:#0F172A
style T fill:#fff7ed,stroke:#f59e0b,color:#0F172A
style C fill:#EEF0F7,stroke:#6366F1,color:#0F172A
style R fill:#f0fdf9,stroke:#0D9488,color:#0F172A
style S fill:#fef2f2,stroke:#ef4444,color:#0F172A
Three kinds of variables matter:
- Confounders (engagement, tenure) cause both treatment and outcome. You must adjust for them.
- Mediators sit on the path from treatment to outcome. Adjusting for them removes part of the effect you’re trying to measure. Usually leave them out.
- Colliders (contacted support) are caused by both treatment and outcome. Conditioning on a collider creates bias, for example by analyzing only customers who contacted support. Leave them out.
The backdoor criterion formalizes which variables to adjust for: block every path from treatment to outcome that starts with an arrow into the treatment, without conditioning on colliders or descendants of the treatment. Tools like DoWhy (Lesson 12) can compute valid adjustment sets from a DAG you write down.
The practical value of drawing the DAG isn’t the math. It’s the conversation. Sit with the people who made the historical decisions and ask “what did you look at when deciding who to call?”. Every answer is a confounder. If something they used isn’t in your data, you can’t fully adjust for it, and you should know that before trusting any estimate.
Selection bias and survivorship
Confounding has cousins that cause the same kind of damage:
- Selection bias: training only on customers who accepted an offer, or loans that were approved (you never see whether rejected applicants would have repaid). A credit model trained only on approved loans has never seen the population it’s now deciding on. This specific case is called reject inference in credit risk.
- Survivorship: analyzing only current customers excludes those who already left, often exactly the ones most affected by past decisions.
- Feedback loops: once a model drives decisions, future training data reflects the model’s own choices. If it never gives offers to some segment, it never learns what offers do there. Lesson 21 returns to this.
Randomization: the gold standard
All of these problems go away when actions are assigned at random. If a coin decides who gets called, engagement can’t influence who gets called, so the difference in retention between called and uncalled customers is the causal effect.
You rarely want to randomize everything. But a small permanent randomized holdout is the single most valuable data investment a decision system can make:
- Reserve, say, 5% of decisions. For those, choose uniformly (or with known probabilities) among the eligible actions, including “none”.
- Log the probability with which each action was chosen, for every decision, randomized or not.
- Use the holdout to train uplift models (next lesson), to validate observational estimates (Lesson 12), and to evaluate new policies offline (Lesson 15).
The cost is real: some customers get a suboptimal action. But it’s bounded and measurable, and without it you’re flying blind, estimating effects from data shaped by your own past choices. Teams that set this up early have a compounding advantage. Teams that don’t spend years arguing about whether their models work.
Next lesson: given data where treatment was randomized, how do you estimate who each action helps?