codexmarkov-average-and-tail.py
markov-average-and-tail.py
"""Plot a finite distribution and its exact inclusive tail against Markov's bound."""
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
def main():
values = np.array([0.0, 1.0, 2.0, 5.0])
probabilities = np.array([0.5, 0.2, 0.2, 0.1])
mean = float(values @ probabilities)
assert np.isclose(mean, 1.1)
assert np.isclose(probabilities[values >= 2].sum(), 0.3)
assert np.isclose(probabilities[values >= 5].sum(), 0.1)
assert np.isclose(0.22 * 5, mean)
plt.rcParams.update({"font.size": 12, "axes.titlesize": 14})
fig, axes = plt.subplots(1, 2, figsize=(10.6, 5.2), dpi=120, facecolor="white")
ax = axes[0]
ax.bar(values, probabilities, width=0.48, color="#206a9d")
for v, p in zip(values, probabilities):
ax.text(v, p + 0.022, f"p = {p:g}", ha="center", fontsize=11)
ax.set(xlim=(-0.6, 5.6), ylim=(0, 0.62), xticks=values,
xlabel="Value of Y", ylabel="Probability mass",
title="Mean = 0.5·0 + 0.2·1 + 0.2·2 + 0.1·5 = 1.1")
ax.grid(axis="y", color="#e7eaed")
ax.set_axisbelow(True)
ax = axes[1]
thresholds = np.linspace(0.15, 6, 801)
bound = np.minimum(1, mean / thresholds)
ax.plot(thresholds, bound, color="#be7521", lw=2.5,
label=r"Bound: $\min(1,1.1/a)$")
segments = [(0.15, 1, 0.5), (1, 2, 0.3), (2, 5, 0.1), (5, 6, 0)]
for i, (left, right, height) in enumerate(segments):
ax.plot([left, right], [height, height], color="#206a9d", lw=2.5,
label=r"Exact $P(Y\geq a)$" if i == 0 else None)
if i > 0:
ax.scatter(left, height, facecolor="white", edgecolor="#206a9d",
linewidth=1.7, s=55, zorder=5)
if i < len(segments) - 1:
ax.scatter(right, height, color="#206a9d", s=55, zorder=5)
ax.vlines(2, 0.3, 0.55, color="#2f8a68", lw=3)
ax.scatter(2, 0.55, color="#be7521", s=55, zorder=5)
ax.annotate("At a = 2:\nactual 0.30, bound 0.55", xy=(2, 0.425),
xytext=(2.7, 0.67), fontsize=11,
arrowprops={"arrowstyle": "->", "color": "#2f8a68"})
ax.set(xlim=(0.15, 6), ylim=(-0.045, 1.08),
xlabel="Threshold a > 0", ylabel="Tail probability / upper bound",
title="Bound from mean and nonnegativity")
ax.legend(loc="upper right", frameon=False, fontsize=10)
ax.grid(color="#e7eaed")
for ax in axes:
ax.spines[["top", "right"]].set_visible(False)
fig.suptitle("Markov's inequality: the same mean limits every positive-threshold tail", fontsize=15)
fig.subplots_adjust(left=0.06, right=0.985, top=0.79, bottom=0.14, wspace=0.3)
fig.savefig(Path.cwd() / Path(__file__).with_suffix(".png").name,
facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()