Demo for the DoWhy causal API#
We show a simple example of adding a causal extension to any dataframe.
[1]:
import dowhy
dowhy.enable_notebook_rendering()
import dowhy.datasets
import dowhy.api
from dowhy.graph import build_graph_from_str
import numpy as np
import pandas as pd
from statsmodels.api import OLS
[2]:
data = dowhy.datasets.linear_dataset(beta=5,
num_common_causes=1,
num_instruments = 0,
num_samples=1000,
treatment_is_binary=True)
df = data['df']
df['y'] = df['y'] + np.random.normal(size=len(df)) # Adding noise to data. Without noise, the variance in Y|X, Z is zero, and mcmc fails.
nx_graph = build_graph_from_str(data["dot_graph"])
treatment= data["treatment_name"][0]
outcome = data["outcome_name"][0]
common_cause = data["common_causes_names"][0]
df
[2]:
| W0 | v0 | y | |
|---|---|---|---|
| 0 | 1.382710 | True | 8.060313 |
| 1 | 1.472841 | True | 8.334425 |
| 2 | 0.306800 | True | 7.820742 |
| 3 | -0.430429 | False | -0.455280 |
| 4 | 0.618401 | True | 6.239341 |
| ... | ... | ... | ... |
| 995 | 1.002819 | True | 9.206626 |
| 996 | 0.213227 | True | 6.627189 |
| 997 | 3.054601 | True | 10.629182 |
| 998 | 0.230412 | True | 6.199779 |
| 999 | 1.725729 | True | 8.568986 |
1000 rows × 3 columns
[3]:
# data['df'] is just a regular pandas.DataFrame
df.causal.do(x=treatment,
variable_types={treatment: 'b', outcome: 'c', common_cause: 'c'},
outcome=outcome,
common_causes=[common_cause],
).groupby(treatment).mean().plot(y=outcome, kind='bar')
[3]:
<Axes: xlabel='v0'>
[4]:
df.causal.do(x={treatment: 1},
variable_types={treatment:'b', outcome: 'c', common_cause: 'c'},
outcome=outcome,
method='weighting',
common_causes=[common_cause]
).groupby(treatment).mean().plot(y=outcome, kind='bar')
[4]:
<Axes: xlabel='v0'>
[5]:
cdf_1 = df.causal.do(x={treatment: 1},
variable_types={treatment: 'b', outcome: 'c', common_cause: 'c'},
outcome=outcome,
graph=nx_graph
)
cdf_0 = df.causal.do(x={treatment: 0},
variable_types={treatment: 'b', outcome: 'c', common_cause: 'c'},
outcome=outcome,
graph=nx_graph
)
[6]:
cdf_0
[6]:
| W0 | v0 | y | propensity_score | weight | |
|---|---|---|---|---|---|
| 0 | 1.857935 | False | 1.035500 | 0.044035 | 22.709352 |
| 1 | 1.441613 | False | 2.773858 | 0.080289 | 12.455070 |
| 2 | 1.161925 | False | 1.867316 | 0.118267 | 8.455410 |
| 3 | 2.289897 | False | 2.249772 | 0.023179 | 43.143088 |
| 4 | -0.360114 | False | -0.769929 | 0.581352 | 1.720127 |
| ... | ... | ... | ... | ... | ... |
| 995 | 0.859367 | False | 0.020092 | 0.175907 | 5.684812 |
| 996 | 0.394338 | False | -0.685477 | 0.303598 | 3.293825 |
| 997 | 1.611883 | False | 1.782004 | 0.062979 | 15.878280 |
| 998 | 0.136252 | False | -2.412841 | 0.393196 | 2.543263 |
| 999 | 2.017139 | False | 2.659729 | 0.034817 | 28.721824 |
1000 rows × 5 columns
[7]:
cdf_1
[7]:
| W0 | v0 | y | propensity_score | weight | |
|---|---|---|---|---|---|
| 0 | 0.413405 | True | 5.595959 | 0.702556 | 1.423374 |
| 1 | 1.392446 | True | 7.668865 | 0.913957 | 1.094144 |
| 2 | 0.726007 | True | 6.576239 | 0.792414 | 1.261967 |
| 3 | 2.509753 | True | 8.063277 | 0.983352 | 1.016930 |
| 4 | -0.564910 | True | 4.925555 | 0.344610 | 2.901828 |
| ... | ... | ... | ... | ... | ... |
| 995 | 0.428467 | True | 5.023315 | 0.707367 | 1.413694 |
| 996 | 2.598522 | True | 8.081928 | 0.985443 | 1.014772 |
| 997 | 1.304708 | True | 6.729470 | 0.902753 | 1.107722 |
| 998 | 2.152605 | True | 7.396697 | 0.971536 | 1.029298 |
| 999 | 1.264880 | True | 6.376408 | 0.897250 | 1.114516 |
1000 rows × 5 columns
Comparing the estimate to Linear Regression#
First, estimating the effect using the causal data frame, and the 95% confidence interval.
[8]:
(cdf_1['y'] - cdf_0['y']).mean()
[8]:
$\displaystyle 5.43679388164681$
[9]:
1.96*(cdf_1['y'] - cdf_0['y']).std() / np.sqrt(len(df))
[9]:
$\displaystyle 0.144027847284715$
Comparing to the estimate from OLS.
[10]:
model = OLS(np.asarray(df[outcome]), np.asarray(df[[common_cause, treatment]], dtype=np.float64))
result = model.fit()
result.summary()
[10]:
| Dep. Variable: | y | R-squared (uncentered): | 0.974 |
|---|---|---|---|
| Model: | OLS | Adj. R-squared (uncentered): | 0.974 |
| Method: | Least Squares | F-statistic: | 1.864e+04 |
| Date: | Sun, 25 Oct 2026 | Prob (F-statistic): | 0.00 |
| Time: | 21:11:19 | Log-Likelihood: | -1394.5 |
| No. Observations: | 1000 | AIC: | 2793. |
| Df Residuals: | 998 | BIC: | 2803. |
| Df Model: | 2 | ||
| Covariance Type: | nonrobust |
| coef | std err | t | P>|t| | [0.025 | 0.975] | |
|---|---|---|---|---|---|---|
| x1 | 1.4748 | 0.035 | 42.029 | 0.000 | 1.406 | 1.544 |
| x2 | 5.0245 | 0.054 | 93.093 | 0.000 | 4.919 | 5.130 |
| Omnibus: | 3.385 | Durbin-Watson: | 2.003 |
|---|---|---|---|
| Prob(Omnibus): | 0.184 | Jarque-Bera (JB): | 3.256 |
| Skew: | 0.134 | Prob(JB): | 0.196 |
| Kurtosis: | 3.078 | Cond. No. | 2.97 |
Notes:
[1] R² is computed without centering (uncentered) since the model does not contain a constant.
[2] Standard Errors assume that the covariance matrix of the errors is correctly specified.