Confounding, DAGs and what to adjust for
The three conditions a confounder has to meet, how drawing your causal assumptions as a DAG turns "what goes in the model" into a question you can check item by item, and why adjusting for a collider manufactures an association out of nothing — including the collider that happens before the exposure and looks entirely harmless.
What this page answers
The cohort study chapter set out the central difficulty of observational research: the two groups differ before any treatment is given, so an observed association is not a treatment effect. Its answer was to put the measured confounders into a Cox model, with one honest qualifier attached — statistical adjustment can only handle variables you actually measured.
This page goes one step earlier, to the more basic question: among the variables you did measure, which ones belong in the model?
The intuitive answer is “all of them — the more you adjust for, the cleaner it gets”. That intuition is wrong in three situations, and it is wrong in a specific direction: the problem is not that you adjusted too little, it is that the adjustment itself created the bias. Telling those three situations apart cannot be done with p-values, stepwise regression, or AIC. All of those look only at associations in the data, and whether a variable should be adjusted for is a question about causal structure, which the data alone will never answer.
A directed acyclic graph (DAG) is the tool for writing that structure down. It will not find the truth for you. It does exactly one thing: it puts your assumptions out in the open, so that “what should I adjust for” becomes a question you can check line by line instead of a judgement call made by feel.
The three conditions for a confounder
For a variable to be a confounder of the exposure and the outcome , the traditional definition asks for three conditions at once:
- is associated with the exposure — the treated and untreated groups differ on
- is an independent risk factor for — even setting aside, affects the outcome
- does not lie on the causal path from to — it is not caused by the exposure, and it is not a way station through which the exposure acts
The third is the one people skip, and it is exactly the line separating a confounder from a mediator. The first two are visible in the data (you can compute a correlation, you can run a regression). The third is not. It is knowledge about timing and mechanism, and it can only come from the clinic, from the literature, and from your understanding of the disease.
Drawing the problem as a DAG
The rules of a DAG fit in one sentence: draw a node for each variable, draw an arrow for each “I believe X directly causes Y”, point every arrow at the consequence, and never let a path loop back on itself (acyclic). An absent arrow is a substantive claim — “I believe there is no direct causal link between these two” — so the places where you cannot draw an arrow matter as much as the places where you can.
Once the graph is drawn, association flows along three kinds of path:
| Shape of the path | Name | Open by default? | What adjusting for the middle node does |
|---|---|---|---|
| Fork / common cause | Open | Closes it — which is exactly what we want | |
| Chain / mediation | Open | Closes it — but what it closes is the effect you meant to estimate | |
| Collider / common effect | Closed | Opens it, manufacturing an association out of nothing |
The third row is the least intuitive and the most useful thing in the whole DAG language. A collider path is closed by default, and adjustment opens it. It is the single most important counterexample to “more adjustment is better”.
Four structures, four answers
Each of the four structures was simulated 500 times with 2000 participants per run, and each run fits two models: exposure only (Y ~ A), and exposure plus one more variable (Y ~ A + Z). Every arrow in all four structures was given the same strength, so the only thing that differs between them is the shape.
figures/scripts/B6-01-dag.Rset.seed(20260822)
n <- 2000; g <- 0.8 # g = strength of every non-causal arrow, shared by all four
# 1. Confounder: C causes both A and Y; true effect = 1
gen_confounder <- function(n) {
C <- rnorm(n); A <- g * C + rnorm(n); Y <- 1 * A + g * C + rnorm(n)
data.frame(A, Y, Z = C)
}
# 2. Collider: A and Y are independent, both cause S; true effect = 0
gen_collider <- function(n) {
A <- rnorm(n); Y <- 0 * A + rnorm(n); S <- g * A + g * Y + rnorm(n)
data.frame(A, Y, Z = S)
}
# 3. Mediator: A acts through M; total effect = 0.2 + 0.8 * 0.8
gen_mediator <- function(n) {
A <- rnorm(n); M <- g * A + rnorm(n); Y <- 0.2 * A + g * M + rnorm(n)
data.frame(A, Y, Z = M)
}
# 4. M-bias: Z precedes the exposure, but is a common effect of two
# unmeasured variables; true effect = 0
gen_mbias <- function(n) {
U1 <- rnorm(n); U2 <- rnorm(n)
Z <- g * U1 + g * U2 + rnorm(n)
A <- g * U1 + rnorm(n)
Y <- 0 * A + g * U2 + rnorm(n)
data.frame(A, Y, Z)
}
d <- gen_collider(n)
coef(lm(Y ~ A, data = d))["A"] # unadjusted
coef(lm(Y ~ A + Z, data = d))["A"] # after adjusting for the colliderVerified with R 4.6.0; base R only, no packages needed
import numpy as np, statsmodels.api as sm
rng = np.random.default_rng(20260822)
n, g = 2000, 0.8
def gen_collider(n):
A = rng.normal(size=n)
Y = 0 * A + rng.normal(size=n)
S = g * A + g * Y + rng.normal(size=n)
return A, Y, S
A, Y, S = gen_collider(n)
crude = sm.OLS(Y, sm.add_constant(A)).fit()
adj = sm.OLS(Y, sm.add_constant(np.column_stack([A, S]))).fit()
print(crude.params[1], adj.params[1]) # near zero vs clearly away from zeroThe numpy + statsmodels version; the four generators correspond line for line to the R ones.
figures/scripts/B6-01-dag.R| Structure | How it reads clinically | True effect | Y ~ A | Y ~ A + Z | What to do |
|---|---|---|---|---|---|
| Confounder | Disease severity decides both who gets the drug and who dies | 1.00 | +1.39 | +1.00 | Adjust |
| Collider | Being admitted is caused by the exposure and by the outcome | 0.00 | 0.00 | −0.39 | Do not adjust |
| Mediator | The drug works by lowering blood pressure | 0.84 | +0.84 | +0.20 | Do not adjust (unless the direct effect is what you want) |
| M-bias | A variable measured before the exposure that is a common effect of two unmeasured causes | 0.00 | 0.00 | −0.12 | Do not adjust |
Only in the first row does the extra variable improve anything. In the other three, the variable you added took an unbiased estimate and broke it.
Colliders: adjustment manufactures an association
The second row is worth stopping on. The exposure and the outcome are completely independent in the simulation — the true effect is set to zero — and the unadjusted estimate is +0.001. After adjustment it becomes −0.388. In a single run of the simulation the confidence interval for that association runs from -0.455 to -0.372, nowhere near zero, with a p-value too small for the output to print.
Two variables with nothing whatsoever to do with each other look strongly related, purely because one common effect was added to the model.
Here is why, intuitively. Suppose there are only two reasons to be admitted to hospital (): the exposure, and the disease itself. Now look only at admitted patients — among them, anyone who was not exposed must have been admitted because their disease was more severe. So within admitted patients, “unexposed” and “severely ill” travel together, and that association does not exist in the general population at all. You created it by choosing whom to look at.
M-bias: it happens before the exposure, and you still must not adjust for it
That test invites a convenient shortcut: “adjust only for things that happened before the exposure, and you can never hit a collider.” The shortcut is right most of the time. M-bias is the rest of the time.
The fourth DAG has this structure. Two things go unmeasured: (say, a tendency toward health-seeking behaviour) and (say, undiagnosed latent disease). influences whether a patient receives the exposure, influences the outcome, and both of them influence the same variable (say, a lab value measured years earlier).
clearly happened before the exposure, it is associated with the exposure, and it is associated with the outcome — run it through the traditional three-condition checklist and it looks like a textbook confounder. In fact it is a collider for and . Adjusting for it opens the path , which was closed: the true effect is zero, and the adjusted estimate is −0.124.
Mediators: you adjust away the thing you wanted to estimate
The third row is a different kind of problem. It is not bias — it is answering a different question.
In the simulation the drug acts along two paths: a direct one, and one through a mediator (blood pressure). The total effect is 0.84; the unadjusted model estimates 0.841, which is accurate. Put the mediator in and the estimate falls to 0.200 — which is precisely the direct effect.
Nobody miscalculated. The two numbers answer different questions:
- Total effect: “if I give this drug, how much does the outcome change?” — the number a clinical decision needs
- Direct effect: “with the blood-pressure pathway taken out, how much of the drug’s action is left?” — the number a mechanistic study needs
The backdoor criterion in plain words
With those three path shapes in hand, “what should I adjust for” becomes a criterion you can execute mechanically. A backdoor path is any path that starts at the exposure with an arrow pointing into the exposure (the arrow leaves “backwards”) and ends at the outcome. The association such a path carries is not causal; it is noise.
The plain-language backdoor criterion is three sentences:
- Find every backdoor path
- Choose a set of variables such that every backdoor path is blocked — either a non-collider on the path has been adjusted for, or the path contains a collider that has not been adjusted for
- That set must contain no descendant of the exposure (anything the exposure causes, mediators included)
A set of variables satisfying all three is a valid adjustment set. Two things about it run against intuition:
- There can be more than one valid adjustment set, and the valid one is not necessarily the largest
- Adding a variable can turn a valid set into an invalid one — which is exactly what M-bias is
You do not have to do this by hand: the dagitty package (there is also a web version) takes the graph and returns every minimal valid adjustment set, and the next section shows exactly how. What still needs a human is getting the arrows right, and no tool can do that part for you.
Handing the DAG to a machine: from picture to adjustment set
The previous section said you do not have to work this out by hand. This section is that promise paid off — write the same picture as code and let it produce the answer.
The dagitty syntax is close to just typing the arrows out. What follows is the confounder structure
from the top left of this page’s first figure, character for character:
library(dagitty)
g <- dagitty('dag {
A [exposure,pos="0,0"]
Y [outcome,pos="1,0"]
C [pos="0.5,-0.85"]
A -> Y
C -> A
C -> Y
}')
adjustmentSets(g) # every minimal sufficient adjustment set
impliedConditionalIndependencies(g) # what this graph claims you will not seeVerified with dagitty 0.3.4. The web version at dagitty.net takes exactly the same syntax, so the string below can be pasted straight in.
# There is no equivalent Python package. networkx can draw the graph, but solving
# the backdoor criterion would have to be written from scratch, which is not worth it.
# Do the DAG step in R or on dagitty.net, then bring the variable list back to Python.There is no comparable Python implementation; do the DAG step in R or on the web.
The pos= coordinates affect layout only, never the computation. They are there so that pasting the
string into dagitty.net gives you the same picture as the figure above. (The y values are negative
because the web version points its y axis downwards.)
What the four structures answer
Running all four structures from this page through adjustmentSets():
| Structure | What instinct adjusts for | adjustmentSets() | Was instinct right |
|---|---|---|---|
| Confounder | C | { C } | Yes |
| Collider | S | { } | No |
| Mediator | M | { } | No |
| M-bias | Z | { } | No |
Only the confounder structure has instinct and tool agreeing. For the other three the tool returns
{ } — adjust for nothing.
That is the same conclusion the simulations earlier on this page reached, arrived at by a different route. The simulations answer “how far does the estimate move if you adjust”; this answers “the graph never asked you to adjust in the first place”. The second kind of evidence needs no data at all.
A DAG can be refuted by data
impliedConditionalIndependencies() lists the places where the graph claims you will find no
association. For both the collider and the M-bias structures it returns the same line,
A _||_ Y — exposure and outcome should
be unrelated, because neither of those graphs contains a causal arrow from A to Y, and the one path
that does join them in the M-bias graph runs through the collider Z, which stays closed for as long
as you do not adjust for it.
That is useful because you can check it against data. If A and Y are plainly associated in the data, the data is not the problem: your graph is wrong — a missing arrow, or one pointing the wrong way.
The confounder and mediator graphs return an empty list. On their three observed nodes those graphs are complete — every pair is joined by a direct arrow — and a complete graph forbids nothing, so there is nothing left for data to contradict. The asymmetry is worth remembering: a DAG that forbids nothing is a DAG that cannot be shown to be wrong.
So what should you adjust for
Everything above, compressed into a checklist you can take into a meeting:
| Role of the variable | How to recognise it | Into the model? |
|---|---|---|
| Confounder | Precedes the exposure, affects both exposure and outcome, not on the causal path | Yes — this is what adjustment exists for |
| Predictor of the outcome only | Affects the outcome, unrelated to the exposure | Fine to include; improves precision, creates no bias |
| Predictor of the exposure only | Affects the exposure, not the outcome | Usually leave out; it costs precision and amplifies residual bias |
| Mediator | Comes after the exposure; the way station through which it acts | Leave out (unless the direct effect is what you want, with a full mediation analysis) |
| Collider | Affected by both exposure and outcome | Never include, and never select the sample on it either |
| Pre-exposure collider (M-bias) | A common effect of two unmeasured causes | Leave out, but the cost depends on arrow strengths you cannot know |
| Instrumental variable | Affects the exposure, and affects the outcome only through the exposure | Not as a covariate — it has its own estimator; see instrumental variables |
From the DAG to an actual analysis
A DAG tells you which paths need blocking. It does not tell you how to block them. There are several ways, each with its own price, and the next few pages take them one at a time:
- Adjust inside a regression model — the most direct route; see the Cox model and linear regression
- Propensity score matching — compress “who gets the exposure” into a single score, then match two comparable groups
- Inverse probability weighting — use the same score as a weight to construct a hypothetical population
- Instrumental variables — the only route left when the crucial confounder was never measured at all
- Self-controlled designs — let each person serve as their own control, removing every time-invariant personal characteristic at once
The first three share one prerequisite: the confounders were measured. Matching and weighting can do nothing at all about the grey, unmeasured nodes on a DAG — they are simply different ways of using the variables you already have. The cohort study chapter said this once; it is worth saying again.
Common misuses
| Misuse | Why it is wrong |
|---|---|
| “The more you adjust for, the better” | Colliders and mediators make the estimate worse, not better |
| Selecting covariates by univariable p-value or stepwise regression | Whether to adjust is a question about causal structure; associations in the data cannot answer it |
| “It differs significantly between groups, so we adjusted for it” | That satisfies only the first of the three conditions |
| Adjusting for a variable measured after treatment started | It is usually a mediator or a collider, and both do damage |
| Adjusting away a mediator and still calling the estimate a treatment effect | What you estimated is the direct effect, not the total effect |
| Analysing only inpatients, or only responders, without treating it as an issue | Selecting on a collider and adjusting for one are the same operation |
| “This variable precedes the exposure, so adjusting for it must be safe” | M-bias is the counterexample to that rule |
| Claiming causation because the model was adjusted | Unmeasured confounding shows up in no statistic |
| Adjusting for an instrumental variable as if it were a covariate | It amplifies residual bias; its correct use is a different estimator entirely |
| Treating a finished DAG as proof of the causal structure | A DAG makes your assumptions explicit; it does not verify them |
Reproducing every number on this page
/opt/homebrew/bin/Rscript figures/scripts/B6-01-dag.RRead the figure
The answer comes from the same statistical output that produced this page's figures, not from a number typed in beside them.
The simulation makes exposure and outcome completely independent, so the true effect is 0. Across 500 replications, putting their common consequence into the model moves the mean estimate somewhere else. Which sentence describes that move?
Show the answer and why
Correct answer: The adjusted mean over 500 replications is -0.388, an association adjustment opened up — the data-generating process never had it
The unadjusted mean is 0.001, sitting on the true effect the simulation was given. Adding the common consequence drags the mean of 500 replications to -0.388, and that is not noise: in a single replication the upper limit of the interval, -0.372, already lies entirely to the left of zero, so it neither covers zero nor leaves the conclusion where it was. A collider path is closed by default and adjustment opens it, so two variables with no relationship at all come to look strongly related. The same move wears other clothes: restricting to inpatients, or to people who returned the questionnaire, is the identical operation on the DAG.
In the simulation the drug acts along two paths and the total effect is 0.84. Putting the mediator into the model pulls the estimate down. Which sentence describes that drop?
Show the answer and why
Correct answer: The adjusted estimate is 0.200, the direct effect left once the mediated path is closed, which answers a mechanism question rather than the total one
Unadjusted the model estimates 0.841, right on the total effect; with the mediator in, it estimates 0.200, which is exactly the direct path the simulation was built with. Nothing here was miscalculated — the question changed. The total effect answers what happens if you give the drug; the direct effect answers what is left after the mediated route is closed off. The value -0.640 is that row's bias column, the gap between estimate and truth rather than a coefficient any model printed, and reading the bias column as the estimate yields the opposite conclusion, that the drug is harmful. As for collinearity: a mediator being correlated with the exposure is precisely why the exposure coefficient must move, and that movement is the path being closed.
The confounder row shows an unadjusted mean estimate of 1.390 against a true effect of 1. Which sentence describes the gap above the truth?
Show the answer and why
Correct answer: The gap is 0.390, because a common cause makes people already prone to the outcome likelier to be exposed, charging that difference to the exposure
The 1.390 is the unadjusted mean, the 1.000 is the mean once the common cause is in the model, and the 0.390 between them is the column the table calls bias. A common cause decides both who gets exposed and who has the outcome, so a difference the two groups already had is charged to the exposure's account; close that fork and the estimate returns to 1. The 0.021 is the standard deviation across the 500 replications of the unadjusted row, an order of magnitude smaller than 0.390, so the gap cannot be simulation noise — which is also why bias is read off the mean of repeated replications rather than off any single estimate. And adjustment did not strip the effect out: the adjusted mean does not sit below the truth, it sits on it.
Hand the mediator graph to dagitty's adjustmentSets(). What comes back?
Show the answer and why
Correct answer: It returns { }, so this graph asks for no adjustment at all and the mediator is in no valid set
The third clause of the backdoor criterion is explicit: a valid adjustment set may not contain a descendant of the exposure, and a mediator is exactly something the exposure caused. So the answer is the empty set, and the tool and the simulation reach the same conclusion by different routes — the simulation says how far adjustment biases the estimate, the tool says the graph never asked for the adjustment. A set containing a variable is the confounder graph's answer, and carrying it over here swaps the conclusions of two different graphs. The variable named as one that must be adjusted for is an input you supplied, not the tool's output; the tool never hands back a descendant of the exposure. Note that the tool answers the graph you drew: draw it wrong and the answer is wrong, confidently.
The bias in the M-bias row is far smaller than in the collider row. Some argue from this that an uncertain variable is better adjusted for than left out. Which sentence best states the problem with that argument?
Show the answer and why
Correct answer: Adjusting for Z gives -0.124, and that magnitude is set by four arrows, two of which run to variables you cannot estimate
The true effect is zero. Leaving Z alone gives a mean of -0.001; adjusting for it gives -0.124. That is indeed much smaller than the collider row's -0.388, which is consistent with the position that adjustment is the safer default. Two things go with it: the magnitude is set by the strength of four arrows, and two of those arrows run to unmeasured variables whose strength the data cannot reveal. So the practical conclusion is not whether to adjust but that you have to write down the structure you believe in before you can know what you are betting on. As for the structure not arising in practice: Z occurs before the exposure and is associated with both exposure and outcome, so the traditional three-condition checklist calls it a confounder, which is where the shortcut rule breaks.
In the collider row, one replication gives an adjusted estimate of -0.413 with a 95% interval that excludes zero and a p value too small for the printout. Which sentence describes that p value?
Show the answer and why
Correct answer: The effect the simulation was given is 0.000, and a p value only says the association is hard to produce by sampling error, never where it came from
The true effect was set to 0.000, and yet after adjusting for the collider the association is not merely present but highly significant. A p value asks how easily sampling error could produce an association this large, not whether the association is causal — and a bias reproduced faithfully in every replication makes the p value smaller and the interval narrower, so there is no bridge from far from zero to solid causal evidence. The lower limit of -0.455 says the same thing. The unadjusted model's p value of 0.797 is the one to believe, and not because it is larger: because that model never opened the collider path. Which model to trust is settled by the shape of the graph, not by a p value.
Chapters that use this method
Watch next
Clearing Up Confounding
因果圖 DAG 是什麼?一張圖看穿因果的陷阱
Introduction to Causal Graphs
Directed Acyclic Graphs (DAGs)
Principles of Epidemiology 05. Causal Inferences, Bias, Confounding, and InteractionSources and licences
This page is original writing