import numpy as np
import plotly.graph_objects as go
from IPython.display import HTML
# Okabe-Ito colorblind-safe palette
OI_BLUE = "#0072B2" # histogram bars (sample means)
OI_ORANGE = "#D55E00" # population-mean reference line
# A skewed, strictly-positive "population" standing in for daily PM2.5-style
# readings (gamma is right-skewed, like real air-quality data). SIMULATED data,
# fixed seed -> identical every build. No real dataset value is claimed here.
rng = np.random.default_rng(2200)
pop = rng.gamma(shape=1.5, scale=6.2, size=200_000)
mu, sigma = pop.mean(), pop.std()
sizes = [2, 5, 10, 20, 30, 50, 100] # the sample-size slider stops
reps = 5000 # repeated samples per slider stop
# One histogram trace per sample size; only the first is visible at load.
fig = go.Figure()
for k, n in enumerate(sizes):
means = rng.choice(pop, size=(reps, n)).mean(axis=1)
fig.add_trace(go.Histogram(
x=means, visible=(k == 0), nbinsx=45,
name=f"n={n}", marker_color=OI_BLUE, opacity=0.9,
hovertemplate="sample mean: %{x:.2f}<br>count: %{y}<extra></extra>",
))
# Population mean as a layout line (always visible, not a toggled trace).
fig.add_vline(x=mu, line_dash="dash", line_color=OI_ORANGE, line_width=2,
annotation_text="population mean", annotation_position="top right")
# Slider: show exactly one sample-size's histogram and update the title/SE.
steps = []
for k, n in enumerate(sizes):
se = sigma / np.sqrt(n)
steps.append(dict(
method="update", label=str(n),
args=[{"visible": [c == k for c in range(len(sizes))]},
{"title.text": f"Sampling distribution of the sample mean "
f"(simulated) - n = {n}, SE = sigma/sqrt(n) "
f"= {se:.2f}"}]))
fig.update_layout(
template="simple_white", bargap=0.02, showlegend=False,
xaxis_title="Sample mean of the simulated population",
yaxis_title="Number of samples (out of 5000)",
xaxis_range=[0, 20], # FIXED range so bars don't jump as n changes
height=460, margin=dict(t=70, r=20, b=60, l=70),
sliders=[dict(active=0, pad={"t": 40},
currentvalue={"prefix": "Sample size n = "}, steps=steps)],
title=f"Sampling distribution of the sample mean (simulated) - n = {sizes[0]}, "
f"SE = sigma/sqrt(n) = {sigma/np.sqrt(sizes[0]):.2f}",
)
# THE GOTCHA FIX: emit text/html ourselves (embeddable div + CDN plotly.js).
HTML(fig.to_html(full_html=False, include_plotlyjs="cdn"))