
Image by Author
You run a regression and see that users who receive a discount spend more money. The coefficient looks strong. The p-value is convincing. It feels like you’ve found something useful. But there’s a problem hiding underneath. Did the discount actually cause the increase in spending, or were those users already more likely to spend?
This is where many analyses break down. Traditional statistics and machine learning are very good at finding patterns in data, but real decisions depend on something stricter. You need to understand cause and effect, not just correlation.
DoWhy is a Python library, originally developed by Microsoft researchers, that helps you move from “this is related” to “this causes that.” Instead of treating causal analysis as a black box, it forces you to be explicit about your assumptions and then tests whether your conclusions hold up.
To make this concrete, think of a few simple questions:
- Does increasing marketing spend lead to higher sales?
- Does a treatment actually improve patient recovery?
These are causal questions. And they’re much harder than they look. In this tutorial, you’ll build your first causal model with DoWhy, estimate an effect from data, and understand what that number actually means in practice.
What is DoWhy?
At its core, causal inference asks a very simple question: What happens if we intervene?
Not “what do we observe,” but “what would change if we actively did something different.”
That distinction is the whole game. DoWhy gives you a structured way to answer that question. Instead of jumping straight into modeling, it breaks the process into clear, traceable steps. You define your assumptions, express them as a causal graph, estimate the effect using statistical methods, and then try to break your own result.
Under the hood, it combines three things:
- Causal graphs to represent assumptions about how variables relate
- Statistical estimators to compute effects from data
- Refutation tests to check whether your result is robust
The workflow is always the same, and that consistency is what makes it useful:
- Model: Define the causal relationships you believe exist
- Identify: Determine whether the effect you want can be estimated
- Estimate: Compute the causal effect using data
- Refute: Test how sensitive your result is to hidden assumptions
You don’t need to understand do-calculus or read research papers to get started. The library handles the heavy lifting. What matters is that you’re forced to be clear about what you assume, and that alone already puts you ahead of many analyses.
Installing DoWhy
Getting started with DoWhy is straightforward. You can install it directly from PyPI using pip:
pip install dowhy
DoWhy works with standard Python data science environments and integrates well with tools like pandas and scikit-learn. If you’re using Python 3.8 or newer, you should be fine in most cases.
If you run into environment-specific issues or want optional features, the official DoWhy installation guide walks through extra setup steps in more detail.
Loading a Sample Dataset
To keep things simple, we’ll start with a small synthetic dataset. This is useful for causal inference because you know exactly how the data was generated, which makes it easier to understand what the model is doing.
We’ll work with three types of variables:
- Treatment (T): the variable you control or intervene on
- Outcome (Y): the result you care about
- Confounder (W): a variable that influences both the treatment and the outcome
Here’s a minimal example:
import pandas as pd
import numpy as np
from dowhy import CausalModel
# Set seed for reproducibility
np.random.seed(42)
# Create synthetic data
n = 1000
W = np.random.normal(0, 1, n) # Confounder
T = 0.5 * W + np.random.normal(0, 1, n) # Treatment influenced by W
Y = 2 * T + W + np.random.normal(0, 1, n) # Outcome influenced by T and W
# Put into a DataFrame
df = pd.DataFrame({
"treatment": T,
"outcome": Y,
"confounder": W
})
# Preview the data
print(df.head())
What’s going on here?
- confounder (W): This affects both the treatment and the outcome. Think of it as something like user intent or baseline health
- treatment (T): This is the variable we’re interested in. It’s partially influenced by the confounder
- outcome (Y): This is what we want to measure. It depends on both the treatment and the confounder
A quick preview (df.head()) will show something like this:
treatment outcome confounder 0 1.647713 3.116585 0.496714 1 0.855443 1.428393 -0.138264 2 0.383510 0.622985 0.647689 3 0.114431 1.444558 1.523030 4 0.581119 0.342300 -0.234153
This setup mirrors real-world problems more than it might seem. In practice, confounders are everywhere. If you ignore them, you risk attributing effects to the treatment that were actually caused by something else.
In the next step, we’ll take this dataset and explicitly define the causal relationships between these variables using DoWhy.
Step 1: Defining the Causal Model
Before estimating anything, you need to state what you believe about how your data was generated.
This is the part many people skip. DoWhy does not let you skip it.
A causal graph is a simple way to describe relationships between variables. Instead of equations, you draw arrows:
- If A affects B, you draw an arrow from A to B
- If something influences both variables, it becomes a confounder
In our case, we already know how the data was created:
- The confounder affects both the treatment and the outcome
- The treatment affects the outcome
Now we translate that into a DoWhy model:
model = CausalModel(
data=df,
treatment="treatment",
outcome="outcome",
common_causes=["confounder"]
)
What each part means:
- data=df: This is your dataset. Nothing special here, just a pandas DataFrame
- treatment=”treatment”: This is the variable you want to intervene on. In a real scenario, it could be something like a discount, a drug, or an ad campaign
- outcome=”outcome”: This is the result you care about measuring
- common_causes=[“confounder”]: This is the critical part. You are explicitly telling the model which variable affects both the treatment and the outcome
That last line is where most of the thinking happens. If you miss a confounder here, your final result can be misleading.

Step 2: Identifying the Causal Effect
Once the model is defined, the next question is:
Can we actually estimate the effect we care about from this data?
This step is called identification. In simple terms, identification means finding a valid strategy to isolate the effect of the treatment on the outcome, given your assumptions.
Here’s how you do it:
identified_estimand = model.identify_effect() print(identified_estimand)
What is this doing?
DoWhy looks at your causal graph and determines:
- Whether the causal effect is identifiable
- What mathematical expression can be used to compute it
For this simple setup, it will typically use a backdoor adjustment, which means: “Control for the confounder so it doesn’t bias the relationship between the treatment and the outcome.”
Why this matters
Without this step, you are just guessing how to estimate the effect. With identification, you have a justified path from assumptions → estimation. It acts as a checkpoint. If your causal effect cannot be identified from the data and assumptions you provided, DoWhy will tell you before you move forward.
Step 3: Estimating the Effect
At this point, you’ve done the hard thinking. You defined your assumptions and confirmed that the causal effect can be identified. Now you move to estimation, which means turning that causal idea into a number.
In plain terms, you are asking:
“How much does the outcome change when the treatment changes, after accounting for the confounder?”
Here’s how you estimate it using a basic linear regression method in DoWhy:
estimate = model.estimate_effect(
identified_estimand,
method_name="backdoor.linear_regression"
)
print(estimate.value)
What is happening here?
- method_name=”backdoor.linear_regression”: This tells DoWhy to use a backdoor adjustment strategy with linear regression. In practice, this means it controls for the confounder while estimating the effect of the treatment
- estimate.value: This is the final number, which represents the estimated causal effect
How do you interpret the result? Let’s say the output is:
2.05
This means:
For every one-unit increase in the treatment, the outcome increases by about 2.05 units, after accounting for the confounder.
That “after accounting for the confounder” part is what makes this different from a regular regression. You are not just fitting a model. You are estimating a causal effect based on the assumptions you defined earlier.
If your assumptions are correct, this number is meaningful. If they are wrong, the number can still look convincing, which is why the next step exists.
Step 4: Refuting the Result (Quick Intro)
Even with a clean estimate, you should not trust the result immediately. Causal inference is sensitive to assumptions. A missing variable or a small bias can completely change your conclusion. DoWhy includes refutation tests to help you stress-test your result.
Here’s a simple example:
refutation = model.refute_estimate(
identified_estimand,
estimate,
method_name="random_common_cause"
)
print(refutation)
This method adds a random variable as a fake confounder and checks whether your estimate changes significantly.
Why this matters:
If your estimate shifts a lot after adding random noise, that’s a warning sign. It suggests your result may not be stable. If it stays roughly the same, that gives you more confidence that your finding is not just an artifact of the data.
In short, this step answers a simple question: “If something unexpected was missing from my model, would my conclusion still hold?”
Putting It All Together
At this point, you’ve gone through the full workflow once. You started by stating your assumptions rather than jumping straight into modeling. You defined how the variables relate to each other. Then you checked whether the effect you care about can actually be identified from those assumptions.
After that, you estimated the effect using the data and arrived at a number you could interpret. Finally, you ran a basic refutation test to see if that result holds up under small changes.
That sequence matters more than the code itself. Most analyses skip straight to estimation. What DoWhy does is force you to slow down and make each step explicit. You are not just getting a result. You are showing how you got there and what assumptions it depends on.
That alone makes your analysis easier to trust and easier to question when something feels off.
When Should You Use DoWhy?
DoWhy is most useful when you care about cause and effect but cannot run a controlled experiment. A few common situations include:
- Observational data: You did not run an A/B test, but you still want to understand the impact. This is common in product analytics, healthcare, and economics
- Policy analysis: You want to estimate the effect of a decision or intervention, such as a pricing change or public policy, when randomized experiments are not practical
- Marketing attribution: You want to know whether a campaign actually drove conversions, not just whether it is correlated with them
In all of these cases, the data alone is not enough. The assumptions you make about how the world works are just as important. DoWhy gives you a structured way to combine both.
Conclusion
The hardest part of causal analysis is not running the model. It is being clear about what you believe and why. Once you write those assumptions down, everything else becomes more honest. You can see where your result comes from. You can question it. You can test it.
That is the real value of DoWhy. It does not magically turn observational data into truth. What it does is give you a structured way to reason about cause and effect without hiding the assumptions behind the scenes. If you use it properly, the goal is not just to get a number. It is to understand how fragile or reliable that number is.
