# Backdoor Adjustment

A formal criterion identifying which variables to condition on to block confounding paths in a causal graph.

**Rests on:** Causal Deconfounding

*Machine Learning · Causal Inference*


## Why learn this

This concept gives you a formal rule for answering a question that comes up constantly in real-world AI work: "If I have data on many variables, which ones should I control for to find out the true effect of one thing on another?" Without this rule, you guess — and guessing produces misleading answers that can get a loan denied, a medical diagnosis wrong, or a recommendation system recommending the wrong thing. Mastering backdoor adjustment unlocks the rest of causal inference: you will be able to understand and apply deconfounding, design better experiments (or know why you can't), and build AI systems that don't just predict but actually explain why something happened. This connects directly to the other topics in your library: **Causal Deconfounding**, **Fairness and Bias**, and **Explainable AI**. With backdoor adjustment, you will be able to look at a causal graph and say with confidence which variables to group by — and which to avoid — to get an unbiased estimate of a causal effect.


## The intuition

Imagine you are trying to figure out whether a new medicine truly helps people recover from a disease. You collect data on thousands of patients: who took the medicine, who recovered, and a bunch of other things about them — age, how sick they were at the start, whether they also took a supplement, and so on. If you just look at the raw numbers, you might find that people who took the medicine recovered more often. But that could be misleading: maybe older, sicker people were less likely to be given the medicine in the first place, and their age and initial sickness are the real reasons they recovered less. The medicine might actually help, but the data hides it. To find the true effect of the medicine, you need to 'adjust' for those other variables — you need to compare people who are similar in age and initial sickness, but differ only in whether they took the medicine. The question is: which variables do you need to adjust for, and which should you NOT adjust for (because doing so would actually cause more harm)? 

To know which to pick, we need to define two tricky things. First, a **confounder** is a variable that influences both the treatment (the medicine) and the outcome (recovery). It creates a fake link between them. Second, a **collider** is a variable that is caused by both the treatment and the outcome (or two other variables). Third, **conditioning** simply means grouping your data based on specific values of a variable — for example, looking only at patients who are exactly 60 years old.

The backdoor adjustment is a formal rule that tells you exactly what to group by. It works by looking at the causal graph — a diagram showing which variables cause which other variables. The rule identifies all the 'backdoor paths' — paths that go from the treatment to the outcome but not through the treatment directly, often through confounders — and says: you must condition on a set of variables that blocks all of these paths. If you condition on the right set, the remaining association between treatment and outcome reflects only the direct causal path, not any spurious correlation. This is the core idea: correlation is not causation, but with the right adjustment, you can turn correlation into a causal estimate.


## An analogy

Think of a detective trying to solve a murder. There are two ways the suspect could be linked to the victim: directly (they were seen stabbing the victim) and indirectly (they had a motive, an opportunity, and a history of anger — but no one saw them at the scene). The detective wants to know if the suspect actually did it, not just if they had a motive. The 'backdoor' in this analogy is the motive. The suspect's motive (e.g., a large inheritance) could explain why they were near the victim, even if they never touched them. To figure out if the suspect actually did the killing, the detective must 'control for' the motive — that is, they must ask: 'Even among people with the same motive, did the suspect kill the victim more often than others?' If the answer is yes, that gives evidence of a direct causal link. If the answer is no, then the motive explains the entire association, and the suspect might be innocent. In causal graphs, the 'motive' is a confounder — a variable that causes both the treatment (being near the victim) and the outcome (the murder). The backdoor path is the path that goes from the suspect to the motive to the victim. To block it, you condition on the motive. But here is where the analogy shows its limits: in a real causal graph there can be many variables, and some of them are colliders — variables that are caused by two other variables. If you condition on a collider, you actually open a path that was previously closed. Imagine the detective conditions on 'the victim's phone records' — but that phone record is caused by both the victim's life and the suspect's calls. By conditioning on it, the detective accidentally creates a false correlation between the suspect and the victim, even if there was none before. So the detective must be careful: conditioning on some variables closes backdoor paths, but conditioning on a collider opens a new one. This is the subtlety that the backdoor adjustment rule makes precise.


## Definition

Backdoor adjustment is a method for identifying which variables to group by (condition on) in order to block indirect, misleading paths between a cause and an effect, leaving only the direct causal relationship visible.


## How it connects

You have not yet studied any other topics in causal inference, so this page builds from scratch. But your library notes mention that backdoor adjustment is the first step in **Causal Deconfounding** — removing common causes to turn an association into a causal effect. Once you master this, you will be able to understand why randomised experiments are the gold standard (because by design they block all backdoor paths), and why observational data requires this kind of adjustment. This concept also connects to **Fairness and Bias** in AI: when a model uses a protected attribute (like gender or race) to make decisions, backdoor adjustment helps estimate the true effect of that attribute on the outcome, separate from confounding factors. Your library's book notes say: 'Causal graphs and backdoor adjustment let you estimate a protected attribute's true effect rather than its correlation.' That is exactly what you will be able to do after this page.


## The mathematics

The mathematics behind backdoor adjustment is about understanding paths in a graph. This concept rests on **graph theory** — the branch of mathematics that studies points (called nodes) and the connections between them (called edges). It is not calculus or algebra; it is about structure and connection. We use graphs because the question of 'what causes what' is fundamentally about connections: which variable influences which other variable.

The specific operation you need is **conditional independence** — whether two variables are independent once you hold a third variable fixed. You have already used the idea of conditioning in everyday life: when you say 'given that it rained, the ground was wet', you are conditioning on rain. In this concept, you will learn to identify which variables to condition on by looking at the graph's structure.

**Step 1: Understanding Paths and Arrows**
A path is a sequence of connected nodes. An arrow points from cause to effect. For example, if A causes B, we draw an arrow from A to B (A -> B). If two variables are connected by an arrow, they are dependent; knowing one tells you something about the other.

**Step 2: Identifying Backdoor Paths**
A backdoor path is a route from the Treatment variable (T) to the Outcome variable (Y) that starts with an arrow pointing *into* T. This represents a non-causal influence, often called confounding. For example, if there is a Confounder variable (C) that causes both T and Y, the path looks like this: T <- C -> Y. The arrow from C to T points into T, so this is a backdoor path. If we do not block this path, the association we see between T and Y will be mixed with the effect of C.

**Step 3: Blocking Paths (Adjustment)**
To find the true causal effect, we must 'block' every backdoor path. We do this by conditioning on a variable in the path. Conditioning means splitting our data into groups where that variable is fixed.
*   **Non-Collider Nodes:** If a node in the path has arrows going through it (like C in T <- C -> Y), conditioning on it blocks the path. This is because once we know C, knowing T gives us no new information about Y via C.
*   **Collider Nodes:** A collider is a node where two arrows meet head-to-head (e.g., T -> M <- Y). You must *never* condition on a collider or its descendants. Doing so opens a path that was previously closed, creating false associations.

**Step 4: The Backdoor Criterion**
The backdoor criterion states that a set of variables C is valid for adjustment if:
1. No node in C is a descendant of T (we do not adjust for effects of the treatment).
2. C blocks every backdoor path from T to Y.

If you find such a set C, you can estimate the causal effect by comparing T and Y within each group defined by C.

**The Backdoor Adjustment Formula**
The formula calculates the total causal effect by averaging the effects found in each group. It is written as:

P(Y = y | do(T = t)) = sum over c of P(Y = y | T = t, C = c) * P(C = c)

Here is what each part means in plain English:
*   **P(Y = y | do(T = t)):** This is the probability that the outcome Y takes value y when we intervene to set the treatment T to value t. The 'do' notation signifies an intervention, like a randomized experiment where we force T to be t.
*   **sum over c:** We add up results for every possible value of the confounder C (e.g., if C is age, we sum over all ages).
*   **P(Y = y | T = t, C = c):** This is the probability of outcome Y given treatment T and a specific value of confounder C. This is calculated from observational data within that specific group.
*   **P(C = c):** This is the proportion of the total population that has the value C = c. It acts as a weight, ensuring that groups with more people contribute more to the average.

In short, we calculate the effect of T on Y separately for each subgroup defined by C, and then take a weighted average of those effects, where the weights are the sizes of the subgroups.


## A worked example

We have a simple causal graph with three variables: Treatment (T), Outcome (Y), and a confounder (C). C causes both T and Y. The backdoor path from T to Y is T <- C -> Y. We want to estimate the causal effect of T on Y, but if we do not adjust for C, we will get a biased estimate. Let's make numbers concrete: we have 200 patients, half receive the treatment (T=1) and half don't (T=0). The confounder C is a health score from 0 to 10, where higher means healthier. Healthier patients are more likely to receive the treatment (because doctors prescribe it more often to healthier people) and also more likely to recover. The observed recovery rates are: among treated patients, 80% recover; among untreated, 60% recover. The naive difference is 20 percentage points. But we suspect confounding by C. To adjust, we split patients into groups by C (say, healthy C>=8, medium 5-7, low 0-4). Then within each group, we compare recovery rates between treated and untreated. This is the idea: compare like with like.

**Step 1: Group by C**
We split the 200 patients into three groups based on their health score, so that within each group, patients are similar in health.
*   **Healthy Group (C >= 8):** 50 patients total. (30 treated, 20 untreated).
*   **Medium Group (C = 5-7):** 100 patients total. (40 treated, 60 untreated).
*   **Low Group (C < 5):** 50 patients total. (30 treated, 20 untreated).

**Step 2: Calculate Recovery Rates Within Each Group**
We calculate the recovery rate for treated and untreated patients separately within each health group.

*   **Healthy Group:**
    *   Treated (T=1): 90% recover. (27 out of 30).
    *   Untreated (T=0): 88% recover. (17.6, let's say 18 out of 20 for simplicity, or just use rates).
    *   Difference in this group: 90% - 88% = 2 percentage points.

*   **Medium Group:**
    *   Treated (T=1): 70% recover. (28 out of 40).
    *   Untreated (T=0): 68% recover. (40.8, let's say 41 out of 60).
    *   Difference in this group: 70% - 68% = 2 percentage points.

*   **Low Group:**
    *   Treated (T=1): 50% recover. (15 out of 30).
    *   Untreated (T=0): 48% recover. (9.6, let's say 10 out of 20).
    *   Difference in this group: 50% - 48% = 2 percentage points.

**Step 3: Weight the Differences by Group Size**
The backdoor adjustment formula says we must weight each group's difference by the proportion of the total population in that group. This is because the 'true' effect is the average effect across the whole population, not just an unweighted average of the groups.

*   Total patients = 200.
*   Weight for Healthy Group = 50 / 200 = 0.25.
*   Weight for Medium Group = 100 / 200 = 0.50.
*   Weight for Low Group = 50 / 200 = 0.25.

Now, multiply each group's difference by its weight:
*   Healthy contribution: 2% * 0.25 = 0.5 percentage points.
*   Medium contribution: 2% * 0.50 = 1.0 percentage points.
*   Low contribution: 2% * 0.25 = 0.5 percentage points.

**Step 4: Sum the Weighted Contributions**
Total Adjusted Effect = 0.5 + 1.0 + 0.5 = 2.0 percentage points.

Result: After adjusting for the confounder C, the causal effect of the treatment on recovery is +2 percentage points, not +20. This shows how confounding can hugely inflate the apparent effect, and how conditioning on the right variable corrects it.


## From scratch, in Python

We'll implement a simple adjustment calculation in plain Python. First, we need to understand the basic building blocks: a list is an ordered collection of items, like [1, 2, 3]. A dictionary is a way to look things up by a key, like a phone book: {'name': 'Alice', 'age': 30}. A for loop lets you repeat an action for each item in a list. A function is a reusable block of code that takes inputs and returns an output.

Here is the code that computes the adjusted effect from the numerical example:

```python
# Data: each row is (treatment, confounder_score, recovered 0/1)
# We'll group by confounder category: 'low', 'medium', 'high'

data = [
    (1, 9, 1), (1, 9, 1), (1, 8, 1), (1, 8, 0),  # high, treated
    (0, 9, 1), (0, 8, 1),  # high, untreated
    (1, 6, 1), (1, 5, 0), (1, 7, 1), (1, 5, 1),  # medium, treated
    (0, 6, 1), (0, 5, 1), (0, 7, 0), (0, 6, 1),  # medium, untreated
    (1, 3, 1), (1, 2, 0), (1, 4, 1),  # low, treated
    (0, 3, 0), (0, 2, 1), (0, 4, 0), (0, 3, 1),  # low, untreated
]

# Helper function to compute recovery rate in a list of rows
def recovery_rate(rows):
    if len(rows) == 0:
        return 0.0
    recoveries = 0
    for row in rows:
        recoveries = recoveries + row[2]  # third element is 0/1
    return recoveries / len(rows)

# Group by confounder category
groups = {'low': [], 'medium': [], 'high': []}
for row in data:
    score = row[1]
    if score >= 8:
        groups['high'].append(row)
    elif score >= 5:
        groups['medium'].append(row)
    else:
        groups['low'].append(row)

# For each group, compute the difference in recovery rates between treated and untreated
adjusted_diff = 0.0
total_weight = 0
for category, rows in groups.items():
    treated_rows = [row for row in rows if row[0] == 1]
    untreated_rows = [row for row in rows if row[0] == 0]
    diff = recovery_rate(treated_rows) - recovery_rate(untreated_rows)
    weight = len(rows)
    adjusted_diff = adjusted_diff + diff * weight
    total_weight = total_weight + weight

adjusted_diff = adjusted_diff / total_weight
print('Adjusted effect (percentage points):', adjusted_diff * 100)
```


## The same thing, with a framework

```python
In a library like `statsmodels` (a Python package for statistics), you would use a regression model with the confounder as a covariate. For example, `smf.logit('recovered ~ treatment + confounder', data).fit()` would give you a coefficient for treatment that is already adjusted for the confounder. Underneath, the library is doing essentially the same thing as our code: it is comparing recovery rates within groups defined by the confounder, and then averaging those differences. The library just does it more efficiently and with more statistical detail (like confidence intervals).
```


## What the code does

This code does the same thing you did by hand. First, it stores the data as a list of tuples, where each tuple is (treatment, confounder_score, recovered). A tuple is like a list but cannot be changed after creation. Then we define a function `recovery_rate` that takes a list of rows and returns the proportion where the third element (recovered) is 1. It uses a for loop to count the recoveries. Next, we group the rows by confounder category using a dictionary. The for loop over each row checks the score and appends the row to the appropriate category. Then we loop over each category, split the rows into treated and untreated using a list comprehension (a compact way to build a new list), compute the difference in recovery rates, and multiply by the number of rows in that category to weight it. Finally, we divide by the total number of rows to get the weighted average. This is exactly the backdoor adjustment formula applied to our data.


## Common misconceptions

**Correlation is causation if you adjust for many variables** — If I include every variable I have in my regression, I'll get the true effect.

Including colliders can open backdoor paths that were closed, and including mediators can block the very causal path you want to estimate. The backdoor criterion tells you the *minimal* set that is sufficient, not maximal. In fact: You must adjust for confounders that block backdoor paths, but avoid colliders and mediators. More variables is not better.

**Bias means the model is unfair** — When causal inference says there is confounding bias, it means the model is racist or sexist.

Confounding bias is a statistical concept: the estimated association does not equal the causal effect. It has nothing to do with ethical bias, though it can lead to unfair outcomes if uncorrected. In fact: Confounding bias is about incorrect measurement of a causal effect, not about moral bias. Backdoor adjustment corrects the statistical bias, which is a separate step from ensuring fairness.

**You need to condition on all variables to be safe** — The more variables I condition on, the more I remove confounding.

Conditioning on a collider (a variable with arrows from both the treatment and the outcome) can create spurious associations. This is called 'collider bias'. In fact: The backdoor criterion tells you exactly which variables block all backdoor paths without opening new ones. Conditioning on a collider is harmful.

**Backdoor adjustment works without knowing the causal graph** — I can just throw statistical techniques at the data and get the causal effect.

The essence of the criterion is that you must know the structure of causal relationships. Without a graph, you cannot know which variables are confounders and which are colliders. In fact: You must specify and defend a causal graph. The adjustment set is derived from the graph, not from the data alone.


## In short

Backdoor adjustment is the rule that tells you which variables to condition on to get the true causal effect from observational data. It requires a causal graph: find all backdoor paths from treatment to outcome, choose a set of variables that blocks every such path, but do not condition on colliders. Once you have the right set, you compute the weighted average of the outcome across groups of the adjustment variable — that is the causal effect. The mathematical operation is a weighted sum of conditional probabilities. In code, you can implement it by grouping data by the confounder and averaging the within-group treatment differences. Libraries like DoWhy automate this once you specify the graph. This is foundational for all of causal inference, which in turn underpins trustworthy AI, fairness assessments, and the correct interpretation of model evaluations.


## Where this is used

Backdoor adjustment is used throughout modern AI and data science to get reliable estimates of causal effects from observational data. Here is where you will see it in practice:

- **Large language models (LLMs)**: LLMs are trained on vast amounts of text, and they pick up associations between words. For example, an LLM might learn that the word 'doctor' is associated with 'male' because of historical text. But that is a confounding association: gender does not cause someone to be a doctor; rather, societal factors (which are confounders) cause both the occupation and the gender representation in the data. When building a chatbot that answers questions about careers, you would use backdoor adjustment to separate the true effect of, say, asking a question in a certain tone from the effect of the topic itself, avoiding biased responses. Specifically, you might adjust for the topic of the question (a confounder) when evaluating whether the model is more likely to give a helpful answer to a male-sounding name than a female-sounding name. Without adjustment, you might falsely conclude that the model is biased, when in fact the topics asked about are different.
- **Small models on devices (edge AI)**: When a model runs on a phone and predicts, say, whether a user will buy a product, the model uses features like time of day, location, and past purchases. But those features are confounded: people who buy at night might be different in other ways (e.g., they are younger). If you are evaluating whether a new feature (like a personalised discount) actually increases purchases, you need to adjust for the confounders (age, income) to avoid concluding the discount works when it doesn't. The backdoor adjustment formula is applied in the evaluation step, not in the model itself.
- **Retrieval-augmented generation (RAG)**: In RAG, a language model retrieves documents based on a query and then generates an answer. A common evaluation question is: does the quality of the retrieved documents influence the correctness of the answer? But there is a confounder: the difficulty of the query. Harder queries might lead to worse retrieval and worse answers. To estimate the true effect of retrieval quality on answer correctness, you would adjust for query difficulty. Backdoor adjustment lets you say: 'Even for queries of the same difficulty, better retrieval leads to better answers.' This is crucial for tuning retrieval systems.
- **Agentic systems that plan and call tools**: Agents use tools like calculators or web searches. To evaluate whether a tool actually improves the agent's performance, you must adjust for the complexity of the task. A complex task might both lead to more tool usage and lower success, so a naive comparison would show tool usage leads to failure. Backdoor adjustment lets you isolate the effect of tool usage by conditioning on task complexity.
- **Everyday applied ML (recommendation, fraud, healthcare)**: In a recommendation system, you want to know if showing a product banner increases purchases. But users who see the banner might be more engaged in general (a confounder). You adjust for user engagement level. In fraud detection, you want to know if a new rule actually catches more fraud, but the rule might be applied more often to high-risk transactions (a confounder). In healthcare, you want to know if a drug works, but sicker patients are more likely to get the drug — the classic example. Backdoor adjustment is the standard method here. A developer would write code in Python using `statsmodels` or `DoWhy` (a library for causal inference) to specify the graph and compute the adjustment set. For example, in `DoWhy`, you would write:
  ```python
  model = CausalModel(data, treatment='treatment', outcome='outcome', common_causes=['confounder'])
  identified = model.identify_effect()
  estimate = model.estimate_effect(identified, method_name='backdoor.linear_regression')
  ```
  This plugin does exactly the adjustment formula we implemented by hand, but with more statistical rigor.

What breaks when done badly: If you condition on the wrong variables (e.g., a collider), you can create bias rather than remove it. This is a subtle and common mistake that leads to false conclusions. That is why the criterion is so valuable: it gives you a list of safe and unsafe variables.


---

Read this on the web: https://basavapurushottam.com/learn-ai/backdoor-adjustment
