Press Play. Each batch draws 20 fresh samples from the same simulated population and builds a 95% confidence interval from each. An interval drawn as a blue circle captures the true mean (the green dashed line); one drawn as a vermillion ✕ misses it. Watch across batches: roughly 19 of every 20 intervals catch the truth. That long-run capture rate -- not any single interval -- is what “95% confident” actually means.
import numpy as np
import plotly.graph_objects as go
from IPython.display import HTML
# Okabe-Ito colorblind-safe palette
OI_BLUE = "#0072B2" # intervals that CAPTURE the true mean (circle marker)
OI_VERMILION = "#D55E00" # intervals that MISS the true mean (x marker)
OI_GREEN = "#009E73" # the true-mean reference line
# An invented "true" population. SIMULATED data, fixed seed -> identical every
# build. No real dataset value is claimed: mu and sigma are round teaching
# numbers in arbitrary units, NOT a measured Kern County statistic.
TRUE_MU, SIGMA, N, CONF = 50.0, 12.0, 30, 0.95
# 95% normal critical value. scipy is unavailable in the build venv, so we use
# the exact constant z* = norm.ppf(0.975) (agrees to 9 decimals). For known
# sigma the CI half-width z*-sigma/sqrt(n) is the same for every sample, which
# is why some intervals shift off-center enough to miss mu.
Z = 1.959963984540054
rng = np.random.default_rng(2200)
n_batches, m = 20, 20 # 20 batches; 20 fresh 95% CIs drawn per batch
half = Z * SIGMA / np.sqrt(N) # CI half-width (margin of error), known-sigma
frames, lo_global, hi_global = [], np.inf, -np.inf
for b in range(n_batches):
xbars = rng.normal(TRUE_MU, SIGMA / np.sqrt(N), size=m) # sample means
los, his = xbars - half, xbars + half
covers = (los <= TRUE_MU) & (TRUE_MU <= his) # did the CI catch mu?
n_cap = int(covers.sum())
colors = np.where(covers, OI_BLUE, OI_VERMILION)
syms = np.where(covers, "circle", "x") # shape, not color-alone
lo_global = min(lo_global, los.min())
hi_global = max(hi_global, his.max())
frames.append(go.Frame(
name=str(b),
data=[go.Scatter(
x=xbars, y=list(range(m)), mode="markers",
marker=dict(color=list(colors), symbol=list(syms), size=9,
line=dict(width=1, color="white")),
error_x=dict(type="data", array=[half] * m, color="#999999",
thickness=1.4, width=0),
hovertemplate="CI: %{x:.1f} ± " + f"{half:.1f}<extra></extra>",
)],
layout=go.Layout(title=dict(text=(
"Confidence-interval coverage (simulated): vermillion ✕ "
"intervals miss the true mean<br>"
f"<sup>Batch {b + 1} of {n_batches}: {n_cap} of {m} 95% intervals "
"captured μ (green dashed line)</sup>"))),
))
pad = 0.06 * (hi_global - lo_global)
fig = go.Figure(
data=frames[0].data,
frames=frames,
layout=go.Layout(
template="simple_white",
xaxis_title="95% confidence interval for the mean (simulated units)",
yaxis_title="interval number (1 to 20)",
xaxis_range=[lo_global - pad, hi_global + pad], # FIXED so frames align
height=540, margin=dict(t=90, r=20, b=60, l=70), showlegend=False,
shapes=[dict(type="line", x0=TRUE_MU, x1=TRUE_MU, y0=-1, y1=m,
line=dict(color=OI_GREEN, dash="dash", width=2))],
annotations=[dict(x=TRUE_MU, y=m - 0.5, xanchor="left", yanchor="bottom",
text=" true mean μ", showarrow=False,
font=dict(color=OI_GREEN))],
title=dict(text=(
"Confidence-interval coverage (simulated): vermillion ✕ "
"intervals miss the true mean<br>"
f"<sup>Press Play: each batch draws 20 fresh 95% intervals; about "
"95% capture μ (green dashed line)</sup>")),
updatemenus=[dict(type="buttons", x=0.0, y=1.16, xanchor="left",
showactive=False, buttons=[
dict(label="▶ Play", method="animate",
args=[None, {"frame": {"duration": 900, "redraw": True},
"fromcurrent": True,
"transition": {"duration": 0}}]),
dict(label="❙❙ Pause", method="animate",
args=[[None], {"frame": {"duration": 0, "redraw": False},
"mode": "immediate"}])])],
sliders=[dict(active=0, pad={"t": 45},
currentvalue={"prefix": "Batch "},
steps=[dict(method="animate", label=str(b + 1),
args=[[str(b)], {"mode": "immediate",
"frame": {"duration": 0, "redraw": True},
"transition": {"duration": 0}}])
for b in range(n_batches)])],
),
)
# THE GOTCHA FIX: emit text/html ourselves (embeddable div + CDN plotly.js).
HTML(fig.to_html(full_html=False, include_plotlyjs="cdn"))
Loading...