codexbayes-coin-updating.py
bayes-coin-updating.py
"""Exact finite-model Bayesian updates for one hidden selected coin."""
from fractions import Fraction
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
def posterior(sequence):
biased = Fraction(1, 5)
fair = Fraction(4, 5)
for toss in sequence:
biased *= Fraction(3, 4) if toss == 'H' else Fraction(1, 4)
fair *= Fraction(1, 2)
return biased / (biased + fair)
def main():
assert posterior('H') == Fraction(3, 11)
assert posterior('HHH') == Fraction(27, 59)
assert posterior('HHHH') == Fraction(81, 145)
p_head = Fraction(4, 5) * Fraction(1, 2) + Fraction(1, 5) * Fraction(3, 4)
p_two = Fraction(4, 5) * Fraction(1, 2) ** 2 + Fraction(1, 5) * Fraction(3, 4) ** 2
assert p_head == Fraction(11, 20)
assert p_two == Fraction(5, 16) and p_two - p_head ** 2 == Fraction(1, 100)
heads = np.arange(9)
after_heads = np.array([float(posterior('H' * int(n))) for n in heads])
mixed_sequence = 'HHHTHTHT'
mixed = np.array([float(posterior(mixed_sequence[:n])) for n in range(9)])
plt.rcParams.update({'font.size': 12})
fig, axes = plt.subplots(1, 2, figsize=(11, 5), layout='constrained', facecolor='white')
ax, bars = axes
ax.plot(heads, after_heads, 'o-', color='#b76816', label='Only heads observed')
ax.plot(heads, mixed, 's-', color='#2563a6', label='Sequence H H H T H T H T')
ax.axhline(.5, color='#777777', linewidth=1, linestyle='--')
ax.set(xlabel='Number of observations', ylabel='Posterior probability of the 3/4-head coin',
ylim=(0, 1), xticks=heads, title='One selected coin, repeatedly tossed')
ax.grid(alpha=.2)
ax.legend(fontsize=10, loc='upper left')
ax.annotate('4 heads: 81/145', (4, after_heads[4]), xytext=(4.35, .42),
fontsize=10, arrowprops={'arrowstyle': '->', 'color': '#555555'})
values = np.array([[.8, .2], [8 / 11, 3 / 11]])
labels = ['Before data', 'After one head']
positions = np.arange(2)
bars.bar(positions, values[:, 0], color='#9ab6ca', label='Fair coin')
bars.bar(positions, values[:, 1], bottom=values[:, 0], color='#e2a451', label='3/4-head coin')
for i in range(2):
bars.text(i, values[i, 0] / 2, ['4/5', '8/11'][i], ha='center', va='center')
bars.text(i, values[i, 0] + values[i, 1] / 2, ['1/5', '3/11'][i],
ha='center', va='center')
bars.set(xticks=positions, xticklabels=labels, ylim=(0, 1), ylabel='Probability',
title='A head favors the biased coin\nbut does not yet make it more likely')
bars.legend(loc='lower center', bbox_to_anchor=(.5, -.26), fontsize=11, ncol=2)
fig.suptitle('Bayes: posterior odds = prior odds × likelihood ratio', fontsize=15)
fig.savefig(Path.cwd() / 'bayes-coin-updating.png', dpi=120, facecolor='white')
plt.close(fig)
if __name__ == '__main__':
main()