codexconditioning-entropy-outcome-counterexample.py
conditioning-entropy-outcome-counterexample.py
"""Plot an exact outcome-wise entropy counterexample in the current directory.
Tested with Python 3.14, Matplotlib 3.10.7 and NumPy 2.3.5.
"""
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
def entropy(probabilities):
positive = np.asarray(probabilities)[np.asarray(probabilities) > 0]
return max(0.0, -np.dot(positive, np.log2(positive)))
def main():
joint = np.array([[0.8, 0.1], [0.0, 0.1]])
marginal = joint.sum(axis=1)
weights = joint.sum(axis=0)
conditional = joint / weights
distributions = [marginal, conditional[:, 0], conditional[:, 1]]
entropies = np.array([entropy(p) for p in distributions])
average = np.dot(weights, entropies[1:])
assert np.isclose(average, 0.2)
assert entropies[1] == 0 and entropies[2] == 1
assert average < entropies[0] < entropies[2]
plt.rcParams.update({'font.size': 12, 'axes.spines.top': False,
'axes.spines.right': False})
fig = plt.figure(figsize=(10, 6.5), layout='constrained', facecolor='white')
grid = fig.add_gridspec(2, 3, height_ratios=[1, 1.1])
headings = ['Before any report', 'Common report: Y = 0', 'Rare report: Y = 1']
fractions = [['9/10', '1/10'], ['1', '0'], ['1/2', '1/2']]
for column, (p, heading) in enumerate(zip(distributions, headings)):
ax = fig.add_subplot(grid[0, column])
ax.bar([0, 1], p, color=['#2862a5', '#db802e'], width=0.55)
for x, probability in enumerate(p):
ax.text(x, probability + 0.04, fractions[column][x], ha='center')
ax.set(xticks=[0, 1], xticklabels=['X = 0', 'X = 1'], ylim=(0, 1.25),
title=heading, ylabel='Probability' if column == 0 else '')
ax.set_yticks([0, 0.5, 1])
ax.grid(axis='y', alpha=0.18)
ax.set_axisbelow(True)
ax.text(0.5, -0.22, f'Entropy = {entropies[column]:.6g} bits',
transform=ax.transAxes, ha='center')
ax = fig.add_subplot(grid[1, :])
values = [entropies[0], 0, 1, average]
ax.bar(np.arange(4), values, color=['#526e8c', '#2862a5', '#db802e', '#26866f'],
width=0.56)
for x, value in enumerate(values):
ax.text(x, value + 0.035, f'{value:.6g}', ha='center', weight='bold')
ax.set(xticks=np.arange(4),
xticklabels=['Before report', 'Given Y = 0\n(weight 4/5)',
'Given Y = 1\n(weight 1/5)', 'Weighted average\nafter report'],
ylim=(0, 1.4), ylabel='Entropy (bits)')
ax.grid(axis='y', alpha=0.18)
ax.set_axisbelow(True)
ax.text(0.5, 0.95, '(4/5) × 0 + (1/5) × 1 = 1/5 bit',
transform=ax.transAxes, ha='center', va='top',
bbox={'facecolor': 'white', 'edgecolor': '#d2d9df', 'pad': 6})
fig.suptitle('A rare observation increases uncertainty; the average decreases',
fontsize=15)
fig.savefig(Path.cwd() / (Path(__file__).stem + '.png'), dpi=120,
facecolor='white', transparent=False)
plt.close(fig)
if __name__ == '__main__':
main()