Balance / treatcontrol

Source: Balance/treatcontrol.Rmd

This example is a set of schematic normal-density pictures used in Chapter 20 to separate a causal comparison from a predictive comparison. The R page draws eleven bell curves with different centers and vertical scales. The Python port below keeps the same parameters and makes the figure-generating function explicit.

Setup

Code
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import norm

savefigs = False

Bell-curve helper

Code
def bell(mu, sd=0.4, lo=0.3, hi=5.7, ymax=2.0, ax=None, title=None):
    """Draw the normal curve used in the ROS balance/treatment-control figure."""
    if ax is None:
        _, ax = plt.subplots(figsize=(9, 3))
    x = np.linspace(lo, hi, 500)
    ax.plot(x, norm.pdf(x, loc=mu, scale=sd), color="black", lw=2)
    ax.set_xlim(lo, hi)
    ax.set_ylim(0, ymax)
    ax.set_xlabel("")
    ax.set_ylabel("")
    ax.set_yticks([])
    ax.spines[["left", "right", "top"]].set_visible(False)
    if title:
        ax.set_title(title)
    return ax

Original sequence of curves

Code
curves = [
    ("bell1h", 2.0, 0.4, 0.3, 5.7, 1.0),
    ("bell1l", 2.0, 0.4, 0.3, 5.7, 3.0),
    ("bell2", 3.0, 0.4, 0.3, 5.7, 2.0),
    ("bell3l", 4.0, 0.4, 0.3, 5.7, 3.0),
    ("bell3h", 4.0, 0.4, 0.3, 5.7, 1.0),
    ("bell4l", 1.5, 0.4, 0.3, 5.7, 3.0),
    ("bell5h", 2.5, 0.4, 0.3, 5.7, 1.0),
    ("bell5", 2.5, 0.4, 0.3, 5.7, 2.0),
    ("bell6", 3.5, 0.4, 0.3, 5.7, 2.0),
    ("bell6h", 3.5, 0.4, 0.3, 5.7, 1.0),
    ("bell7l", 4.5, 0.4, 0.3, 5.7, 3.0),
]

fig, axes = plt.subplots(4, 3, figsize=(12, 9), sharex=True)
for ax, (name, mu, sd, lo, hi, ymax) in zip(axes.ravel(), curves):
    bell(mu, sd, lo, hi, ymax, ax=ax, title=f"{name}: mean={mu}")
for ax in axes.ravel()[len(curves):]:
    ax.axis("off")
fig.tight_layout()

The different vertical scales are intentional: the original figure is not comparing densities by area in a single panel. It uses the same normal curve shape as a visual building block for a treatment/control balance story.