Source code for dowhy.interpreters.propensity_balance_interpreter
import numpy as np
import pandas as pd
from dowhy.causal_estimator import CausalEstimate
from dowhy.causal_estimators.propensity_score_estimator import PropensityScoreEstimator
from dowhy.causal_estimators.propensity_score_stratification_estimator import PropensityScoreStratificationEstimator
from dowhy.interpreters.visual_interpreter import VisualInterpreter
[docs]class PropensityBalanceInterpreter(VisualInterpreter):
SUPPORTED_ESTIMATORS = [
PropensityScoreStratificationEstimator,
]
def __init__(self, estimate, **kwargs):
super().__init__(estimate, **kwargs)
if not isinstance(estimate, CausalEstimate):
error_msg = "The interpreter method expects a CausalEstimate object."
self.logger.error(error_msg)
raise ValueError(error_msg)
self.estimator = self.estimate.estimator
if not any(
isinstance(self.estimator, est_class) for est_class in PropensityBalanceInterpreter.SUPPORTED_ESTIMATORS
):
error_msg = "The interpreter method only supports propensity score stratification estimator."
self.logger.error(error_msg)
raise ValueError(error_msg)
[docs] def interpret(self):
"""Balance plot that shows the change in standardized mean differences for each covariate after propensity score stratification."""
cols = (
self.estimator._observed_common_causes_names
+ self.estimator._treatment_name
+ ["strata", "propensity_score"]
)
df = self.estimator._data[cols]
df_long = (
pd.wide_to_long(df.reset_index(), stubnames=["W"], i="index", j="common_cause_id")
.reset_index()
.astype({"W": "float64"})
)
# First, calculating mean differences by strata
mean_diff = df_long.groupby(self.estimator._treatment_name + ["common_cause_id", "strata"]).agg(
mean_w=("W", np.mean)
)
mean_diff = (
mean_diff.groupby(["common_cause_id", "strata"]).transform(lambda x: x.max() - x.min()).reset_index()
)
mean_diff = mean_diff.query("v0==True")
size_by_w_strata = (
df_long.groupby(["common_cause_id", "strata"]).agg(size=("propensity_score", np.size)).reset_index()
)
size_by_strata = df_long.groupby(["common_cause_id"]).agg(size=("propensity_score", np.size)).reset_index()
size_by_strata = pd.merge(size_by_w_strata, size_by_strata, on="common_cause_id")
mean_diff_strata = pd.merge(mean_diff, size_by_strata, on=("common_cause_id", "strata"))
stddev_by_w_strata = df_long.groupby(["common_cause_id", "strata"]).agg(stddev=("W", np.std)).reset_index()
mean_diff_strata = pd.merge(mean_diff_strata, stddev_by_w_strata, on=["common_cause_id", "strata"])
mean_diff_strata["scaled_mean"] = (mean_diff_strata["mean_w"] / mean_diff_strata["stddev"]) * (
mean_diff_strata["size_x"] / mean_diff_strata["size_y"]
)
mean_diff_strata = (
mean_diff_strata.groupby("common_cause_id").agg(std_mean_diff=("scaled_mean", np.sum)).reset_index()
)
# Second, without strata
mean_diff_overall = df_long.groupby(self.estimator._treatment_name + ["common_cause_id"]).agg(
mean_w=("W", np.mean)
)
mean_diff_overall = (
mean_diff_overall.groupby("common_cause_id").transform(lambda x: x.max() - x.min()).reset_index()
)
mean_diff_overall = mean_diff_overall[mean_diff_overall[self.estimator._treatment_name[0]] == True] # TODO
stddev_overall = df_long.groupby(["common_cause_id"]).agg(stddev=("W", np.std)).reset_index()
mean_diff_overall = pd.merge(mean_diff_overall, stddev_overall, on=["common_cause_id"])
mean_diff_overall["std_mean_diff"] = mean_diff_overall["mean_w"] / mean_diff_overall["stddev"]
# Third, concatenating them and plotting
mean_diff_overall = mean_diff_overall[["common_cause_id", "std_mean_diff"]]
mean_diff_strata["sample"] = "PropensityAdjusted"
mean_diff_overall["sample"] = "Unadjusted"
plot_df = pd.concat([mean_diff_overall, mean_diff_strata])
import matplotlib.pyplot as plt
plt.style.use("seaborn-white")
fig, ax = plt.subplots(1, 1)
for label, subdf in plot_df.groupby("common_cause_id"):
subdf.plot(kind="line", x="sample", y="std_mean_diff", ax=ax, label=label)
plt.legend(title="Common causes")
plt.ylabel("Standardized mean difference between treatment and control")
plt.xlabel("")
plt.xticks(rotation=45)
return plot_df