Show code
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
np.set_printoptions(precision=4, suppress=True)Potential outcomes and treatment selection
Chapter 2 is the basic potential-outcomes setup: treatment effects are individual-level objects, while observed outcomes depend on both the potential outcomes and the treatment rule.
rng = np.random.default_rng(2)
n = 1000
y0 = rng.normal(loc=0.0, scale=1.0, size=n)
tau = -0.4 + 0.9 * y0
y1 = y0 + tau
ate = tau.mean()
att_if_treat_positive = tau[tau >= 0.0].mean()
pd.DataFrame(
{
"estimand": ["ATE", "Share with positive treatment effect", "ATT if only gainers are treated"],
"value": [ate, np.mean(tau >= 0.0), att_if_treat_positive],
}
)| estimand | value | |
|---|---|---|
| 0 | ATE | -0.420168 |
| 1 | Share with positive treatment effect | 0.311000 |
| 2 | ATT if only gainers are treated | 0.623081 |
The perfect doctor treats exactly when the individual treatment effect is nonnegative. The coin-flip doctor ignores the potential outcomes entirely.
z_perfect = (tau >= 0.0).astype(float)
z_coin = rng.binomial(1, 0.5, size=n).astype(float)
y_perfect = z_perfect * y1 + (1.0 - z_perfect) * y0
y_coin = z_coin * y1 + (1.0 - z_coin) * y0
def observed_difference(y_obs, z_obs):
treated = z_obs == 1.0
control = ~treated
return y_obs[treated].mean() - y_obs[control].mean()
selection_table = pd.DataFrame(
{
"rule": ["Perfect doctor", "Coin-flip doctor"],
"observed_difference": [
observed_difference(y_perfect, z_perfect),
observed_difference(y_coin, z_coin),
],
"treated_share": [z_perfect.mean(), z_coin.mean()],
"average_effect_among_treated": [
tau[z_perfect == 1.0].mean(),
tau[z_coin == 1.0].mean(),
],
}
)
selection_table| rule | observed_difference | treated_share | average_effect_among_treated | |
|---|---|---|---|---|
| 0 | Perfect doctor | 2.305471 | 0.311 | 0.623081 |
| 1 | Coin-flip doctor | -0.356470 | 0.503 | -0.400482 |
Under coin-flip treatment, the observed difference is estimating the ATE. Under targeted treatment, the observed difference mixes causal effects with positive selection on untreated potential outcomes.
selection_bias = y0[z_perfect == 1.0].mean() - y0[z_perfect == 0.0].mean()
pd.DataFrame(
{
"quantity": [
"ATE",
"Observed difference under perfect doctor",
"Average effect among treated under perfect doctor",
"Selection bias in untreated outcomes",
],
"value": [
ate,
observed_difference(y_perfect, z_perfect),
tau[z_perfect == 1.0].mean(),
selection_bias,
],
}
)| quantity | value | |
|---|---|---|
| 0 | ATE | -0.420168 |
| 1 | Observed difference under perfect doctor | 2.305471 |
| 2 | Average effect among treated under perfect doctor | 0.623081 |
| 3 | Selection bias in untreated outcomes | 1.682389 |
Potential outcomes make it obvious why the same observed contrast can have different meanings under different assignment rules. Chapter 2 is mostly estimand bookkeeping, but it is the bookkeeping that the rest of the Ding material depends on.