codex/normal-location-influence-curves.py

normal-location-influence-curves.py
"""Population influence curves for two normal location-scale models.

Tested with Python 3.14, matplotlib 3.10.7 and numpy 2.3.5.
Run from _media; the output basename matches this root generator.
"""

from math import erf, sqrt
from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np


def main():
    k = 1.345
    normalization = erf(k / sqrt(2))
    colors = ["#2166ac", "#b35806", "#1b7837"]
    fig, axes = plt.subplots(1, 2, figsize=(9.6, 4.3), layout="constrained")
    for ax, mu, sigma in zip(axes, [0.0, 3.0], [1.0, 2.0]):
        x = np.linspace(mu - 4 * sigma, mu + 4 * sigma, 801)
        z = (x - mu) / sigma
        ax.plot(x, x - mu, color=colors[0], label="Mean", lw=2.2)
        # The jump has no connecting vertical segment; the derivative at mu is 0.
        for sign in [-1, 1]:
            mask = sign * z > 0
            ax.plot(x[mask], sigma * sqrt(np.pi / 2) * np.sign(z[mask]),
                    color=colors[1], lw=2.2,
                    label="Median" if sign == -1 else None)
        ax.scatter([mu], [0], color=colors[1], s=28, zorder=5)
        ax.plot(x, sigma * np.clip(z, -k, k) / normalization,
                color=colors[2], lw=2.2, label=f"Huber, k = {k}")
        ax.axhline(0, color="#777777", lw=0.7)
        ax.axvline(mu, color="#777777", lw=0.7)
        ax.set(title=rf"$F = N({mu:g}, {sigma:g}^2)$; fixed scale $s={sigma:g}$",
               xlabel="Contamination location x", ylabel="Influence IF(x; T, F)")
        ax.grid(alpha=0.2)
        ax.legend(loc="upper left", fontsize=9)
    fig.suptitle("Normal-location influence: an unbounded mean and bounded alternatives", fontsize=12)
    fig.savefig(Path(__file__).with_suffix(".png").name, dpi=120,
                facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()