codexgaussian-mean-likelihood.py
gaussian-mean-likelihood.py
"""Plot known-unit-variance normal mean likelihoods; Python 3.14."""
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
def main():
original = np.array([-1.0, 0.0, 2.0, 3.0])
mu = np.linspace(-0.6, 2.6, 1601)
samples = (
(original, "#007c83", "n = 4: [−1, 0, 2, 3]"),
(np.tile(original, 4), "#c4511c", "n = 16: four repetitions of that array"),
)
fig, axes = plt.subplots(1, 2, figsize=(10.6, 4.7), dpi=100,
facecolor="white")
for data, color, label in samples:
n = len(data)
mean = data.mean()
assert mean == 1
direct = -n / 2 * np.log(2 * np.pi) - 0.5 * np.sum(
(data[:, None] - mu[None, :])**2, axis=0)
peak = -n / 2 * np.log(2 * np.pi) - 0.5 * np.sum((data - mean)**2)
difference = -n / 2 * (mu - mean)**2
assert np.allclose(direct - peak, difference, atol=1e-12)
assert np.isclose(mu[np.argmax(direct)], 1)
width = np.sqrt(4 / n)
assert np.isclose(-n / 2 * width**2, -2)
axes[0].plot(mu, np.exp(difference), lw=2.7, color=color, label=label)
axes[1].plot(mu, difference, lw=2.7, color=color,
label=f"n = {n}: curvature −ℓ″ = {n}")
for ax, height in zip(axes, (np.exp(-2), -2)):
ax.plot([1 - width, 1 + width], [height, height], "o",
color=color, ms=5)
axes[0].annotate("", xy=(1 - width, np.exp(-2)),
xytext=(1 + width, np.exp(-2)),
arrowprops={"arrowstyle": "<->", "color": color,
"lw": 1.6})
assert np.sum((original - 1)**2) == 10
assert np.isclose((2 * np.pi)**-2 * np.exp(-5), 0.000170674231586843)
assert np.isclose(np.exp(-2), 0.1353352832366127)
# Check observed curvature from the direct sums of squares at three means.
for data, _, _ in samples:
step = 0.01
values = np.array([-0.5 * np.sum((data - m)**2)
for m in (1 - step, 1, 1 + step)])
assert np.isclose(-(values[0] - 2 * values[1] + values[2]) / step**2,
len(data), rtol=1e-8)
axes[0].set(title="Relative likelihood: narrower for n = 16",
ylabel="L(μ) / L(1)", ylim=(0, 1.08))
axes[1].set(title="Log-likelihood difference: −n(μ − 1)² / 2",
ylabel="ℓ(μ) − ℓ(1)", ylim=(-12, 0.8))
axes[0].axhline(np.exp(-2), color="#777777", lw=1, linestyle="--")
axes[1].axhline(-2, color="#777777", lw=1, linestyle="--")
axes[0].text(2.56, np.exp(-2) + 0.025, "e⁻²", ha="right", fontsize=10)
axes[1].text(2.56, -1.7, "−2", ha="right", fontsize=10)
for ax in axes:
ax.set(xlim=(-0.6, 2.6), xlabel="Candidate mean μ (known σ = 1)")
ax.axvline(1, color="#555555", lw=1, linestyle=":")
ax.set_xticks([0, 0.5, 1, 1.5, 2])
ax.grid(color="#e7e7e7", linewidth=0.7)
ax.spines[["top", "right"]].set_visible(False)
ax.set_facecolor("white")
ax.title.set_fontsize(11)
handles, labels = axes[0].get_legend_handles_labels()
fig.legend(handles, labels, loc="upper center", bbox_to_anchor=(0.5, 0.91),
ncol=2, fontsize=9.5, frameon=False)
axes[1].legend(loc="lower center", fontsize=9, framealpha=1)
fig.suptitle("Constructed normal samples: fitted mean = 1, known variance = 1",
fontsize=13, y=0.97)
fig.text(0.5, 0.08,
"At relative height e⁻²: distance from the peak is 1 for n = 4, "
"and ½ for n = 16. Width scales as 1/√n.",
ha="center", fontsize=10)
fig.text(0.5, 0.035,
"Each sample is modeled as independent observations. "
"Copying recorded data does not create independent evidence.",
ha="center", fontsize=10)
fig.subplots_adjust(left=0.075, right=0.98, bottom=0.23, top=0.77,
wspace=0.25)
fig.savefig(Path.cwd() / Path(__file__).with_suffix(".png").name,
facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()