codexhypothesis-test-normal-tails.py
hypothesis-test-normal-tails.py
"""Draw chosen normal rejection tails and observed p-value tails.
Tested with Python 3.14.4; dependencies are NumPy and Matplotlib.
Run from _media with MPLBACKEND=Agg and MPLCONFIGDIR=/tmp.
"""
from math import erfc, sqrt
from pathlib import Path
from statistics import NormalDist
import matplotlib.pyplot as plt
import numpy as np
def main():
sigma, n, mean, null_mean, alpha = 2.0, 16, 1.2, 0.0, 0.05
standard_error = sigma / sqrt(n)
observed_z = (mean - null_mean) / standard_error
critical = NormalDist().inv_cdf(1 - alpha/2)
pvalue = erfc(abs(observed_z)/sqrt(2))
assert abs(observed_z - 2.4) < 1e-14
assert abs(pvalue - 0.016395071849192273) < 1e-14
assert abs(erfc(critical/sqrt(2)) - alpha) < 1e-14
normal = NormalDist()
for sample_size in [16, 64]:
shift = 1 / (sigma / sqrt(sample_size))
beta = normal.cdf(critical-shift) - normal.cdf(-critical-shift)
print(f"At alternative mu=1, n={sample_size}: beta={beta:.12f}, power={1-beta:.12f}")
plt.rcParams.update({"font.size": 11, "axes.spines.top": False,
"axes.spines.right": False})
fig, axes = plt.subplots(1, 2, figsize=(10, 4.2), layout="constrained",
sharey=True, facecolor="white")
blue, orange = "#146b9b", "#b33e13"
x = np.linspace(-4.2, 4.2, 2501)
density = np.exp(-x*x/2) / sqrt(2*np.pi)
choices = [(critical, orange, "Rule chosen before observing data",
r"Reject when $|Z|>1.959964\ldots$", f"Total shaded area α = {alpha:.2f}"),
(abs(observed_z), blue, "Tail area from the observed statistic",
r"Observed $z=1.2/0.5=2.4$", f"Total shaded area p = {pvalue:.7f}")]
for ax, (threshold, color, title, subtitle, area) in zip(axes, choices):
ax.plot(x, density, color="#222222", linewidth=1.7)
ax.fill_between(x, density, where=abs(x) >= threshold, color=color, alpha=0.8)
for sign in [-1, 1]:
boundary = sign * threshold
ax.axvline(boundary, color=color, linestyle="--", linewidth=1.2)
ax.text(boundary, 0.014, f"{boundary:.2f}", color=color,
ha="center", va="bottom", fontsize=10,
bbox={"facecolor": "white", "edgecolor": "none", "pad": 1})
ax.set(xlim=(-4.2, 4.2), ylim=(0, 0.51), xlabel="Standardized statistic Z")
ax.set_title(title + "\n" + subtitle, fontsize=11, pad=12)
ax.text(0, 0.45, area, ha="center", color=color, fontsize=11)
ax.set_xticks([-4, -2, 0, 2, 4])
axes[0].set_ylabel("Probability density under H₀")
fig.suptitle("Two different tail areas under the same null distribution\n"
r"$H_0:\mu=0$; $Z=\bar X/(2/\sqrt{16})\sim N(0,1)$ under $H_0$",
fontsize=13)
output = Path.cwd() / (Path(__file__).stem + ".png")
fig.savefig(output, dpi=120, facecolor="white", transparent=False)
plt.close(fig)
print(f"Saved {output}")
print(f"Observed z={observed_z:.12f}; p=erfc(|z|/sqrt(2))={pvalue:.12f}; alpha={alpha}")
if __name__ == "__main__":
main()