codexempirical-distribution-simultaneous-uncertainty.py
empirical-distribution-simultaneous-uncertainty.py
"""Illustrate the analytic DKW band, without simulating samples or coverage.
Tested with Python 3.14, Matplotlib 3.10.7 and NumPy 2.3.5.
Run from _media to write this generator's matching opaque PNG there.
"""
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
def main():
alpha = 0.1
sizes = np.array([5, 50, 500])
eps = np.sqrt(np.log(2 / alpha) / (2 * sizes))
sample = np.array([1, 1, 2, 4, 6])
edges = np.array([0, 1, 2, 4, 6, 7])
# Exact right-continuous values at every edge, including both tied 1s.
fn = np.count_nonzero(sample[:, None] <= edges[None, :], axis=0) / sample.size
low, high = np.clip(fn - eps[0], 0, 1), np.clip(fn + eps[0], 0, 1)
blue, orange = "#1767a1", "#c36b13"
plt.rcParams.update({"font.size": 11, "axes.spines.top": False,
"axes.spines.right": False})
fig, (ax, width_ax) = plt.subplots(
1, 2, figsize=(10.2, 5.3), gridspec_kw={"width_ratios": [1.3, 1]},
facecolor="white",
)
fig.subplots_adjust(left=0.075, right=0.97, top=0.72, bottom=0.23, wspace=0.30)
fig.suptitle("A band for the entire population CDF", fontsize=17, y=0.98)
fig.text(0.5, 0.875,
r"$\alpha=0.1,\quad \varepsilon_{n,\alpha}=\sqrt{\log(2/\alpha)/(2n)}$",
ha="center", fontsize=14)
ax.fill_between(edges, low, high, step="post", color=blue, alpha=0.16,
label="clipped confidence band")
ax.step(edges, low, where="post", color=blue, lw=1, alpha=0.65)
ax.step(edges, high, where="post", color=blue, lw=1, alpha=0.65)
ax.step(edges, fn, where="post", color=blue, lw=2.3, label=r"$F_5$")
ax.scatter([1, 2, 4, 6], [0.4, 0.6, 0.8, 1.0], s=25, color=blue, zorder=4)
ax.set_title(r"Chosen sample [1, 1, 2, 4, 6]; $n=5$", fontsize=12)
ax.set_xlim(0, 7)
ax.set_ylim(-0.04, 1.04)
ax.set_xticks(range(8))
ax.set_yticks([0, 0.2, 0.4, 0.6, 0.8, 1])
ax.set_xlabel(r"Threshold $x$")
ax.set_ylabel("Cumulative probability")
ax.grid(axis="y", alpha=0.2)
ax.legend(loc="lower right", fontsize=9, framealpha=1)
width_ax.barh([2, 1, 0], eps, height=0.45, color=[blue, "#5797c5", "#98c4df"])
width_ax.set_yticks([2, 1, 0], [r"$n=5$", r"$n=50$", r"$n=500$"])
for row, value in zip([2, 1, 0], eps):
width_ax.text(value + 0.015, row, f"{value:.5f}", va="center", fontsize=11)
width_ax.set_xlim(0, 0.72)
width_ax.set_ylim(-0.6, 2.6)
width_ax.set_xticks([0, 0.2, 0.4, 0.6])
width_ax.set_xlabel("Half-width before clipping")
width_ax.set_title("Theoretical widths; no extra samples", fontsize=12)
width_ax.grid(axis="x", alpha=0.2)
width_ax.set_axisbelow(True)
fig.text(0.5, 0.105,
"IID sampling: probability ≥ 90% that every threshold is covered at a fixed n.",
ha="center", fontsize=11, color=orange)
fig.text(0.5, 0.05,
"The chosen sample illustrates the procedure; coverage follows from the DKW inequality.",
ha="center", fontsize=10)
fig.savefig(Path(__file__).stem + ".png", dpi=140, facecolor="white")
plt.close(fig)
if __name__ == "__main__":
main()