codexlaplace-absolute-loss-and-median-robustness.py
laplace-absolute-loss-and-median-robustness.py
"""Show absolute loss and the effect of replacing one chosen value.
Tested with Python 3.14, NumPy 2.3.5 and Matplotlib 3.10.7.
Run from the desired output directory; the PNG is written to the CWD.
The lists are chosen examples, not actual observations or a model check.
"""
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
def main():
plt.rcParams.update({"font.size": 11, "axes.titlesize": 12})
blue, orange, green = "#176ca4", "#ce6010", "#267747"
original = np.array([1, 2, 2, 5, 10])
changed = np.array([1, 2, 2, 5, 100])
a = np.sort(np.unique(np.r_[np.linspace(-1, 26, 1081), original, 2.0]))
a = a[a <= 26]
loss = np.abs(a[:, None] - original).sum(axis=1)
changed_loss = np.abs(a[:, None] - changed).sum(axis=1)
replacement = np.linspace(10, 100, 181)
samples = np.tile(original.astype(float), (replacement.size, 1))
samples[:, -1] = replacement
means = samples.mean(axis=1)
medians = np.median(samples, axis=1)
fig, (objective, centers) = plt.subplots(1, 2, figsize=(10, 4.7), dpi=120)
fig.patch.set_facecolor("white")
fig.suptitle("One changed value: median stays, mean moves", fontsize=16, y=0.98)
fig.text(0.5, 0.90, "Chosen illustration: [1, 2, 2, 5, 10] becomes [1, 2, 2, 5, 100]",
ha="center", fontsize=11)
for ax in (objective, centers):
ax.set_facecolor("white")
ax.grid(alpha=0.22)
ax.spines[["top", "right"]].set_visible(False)
objective.plot(a, loss - loss.min(), color=blue, lw=3,
label=r"Original: $\min S=12$")
objective.plot(a, changed_loss - changed_loss.min(), color=orange, lw=2.2,
ls="--", label=r"After change: $\min S=102$")
objective.scatter([2], [0], color=green, s=40, zorder=5)
objective.annotate("Both minimized at 2", xy=(2, 0), xytext=(5, 23),
fontsize=10, arrowprops={"arrowstyle": "->", "color": green})
objective.set(title="Absolute-loss objectives, aligned at minima",
xlabel=r"Candidate location $a$", ylabel=r"$S(a)-\min S$",
xlim=(-1, 26), ylim=(-4, 105))
objective.set_xticks([0, 2, 10, 20, 26])
objective.legend(loc="upper left", fontsize=10, frameon=False)
centers.plot(replacement, means, color=orange, lw=2.5, label="Sample mean")
centers.plot(replacement, medians, color=green, lw=2.5, label="Sample median")
centers.scatter([10, 100], [4, 22], color=orange, s=35, zorder=5)
centers.scatter([10, 100], [2, 2], color=green, marker="s", s=30, zorder=5)
centers.text(12, 5.8, "Mean = 4", color=orange, fontsize=10)
centers.text(97, 23, "Mean = 22", color=orange, ha="right", fontsize=10)
centers.text(58, 3.3, "Median = 2 throughout", color=green, ha="center", fontsize=10)
centers.set(title="Change only the largest value", xlabel="Replacement for 10",
ylabel="Fitted center", xlim=(5, 105), ylim=(0, 26))
centers.set_xticks([10, 25, 50, 75, 100])
centers.legend(loc="upper left", frameon=False, fontsize=10)
fig.text(0.5, 0.025,
r"Not measured data. The fitted Laplace scale changes too: $12/5=2.4$ to $102/5=20.4$.",
ha="center", fontsize=10)
fig.subplots_adjust(left=0.075, right=0.98, bottom=0.19, top=0.79, wspace=0.32)
fig.savefig(Path.cwd() / f"{Path(__file__).stem}.png", facecolor="white",
transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()