codexpast-exam-of-the-digital-social-research-statistics-core-course-of-the-university-of-oxford2022-vaccination-by-trust.py
past-exam-of-the-digital-social-research-statistics-core-course-of-the-university-of-oxford/2022-vaccination-by-trust.py
"""Original count-based plot. Tested with Python 3.14.4, NumPy 2.3.5,
Matplotlib 3.10.7+dfsg1. Output: basename PNG in process CWD.
"""
from pathlib import Path
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy as np
from statistics import NormalDist
def wilson(k, n):
p = k / n
z = NormalDist().inv_cdf(0.975)
denom = 1 + z*z/n
center = (p + z*z/(2*n))/denom
half = z*np.sqrt(p*(1-p)/n + z*z/(4*n*n))/denom
return center-half, center+half
def main():
tables = [
np.array([[14, 8], [8, 6], [19, 15], [22, 104], [13, 112]]),
np.array([[3, 3], [5, 8], [4, 18], [9, 104], [8, 108]]),
np.array([[1, 2], [2, 5], [3, 10], [3, 52], [7, 72]]),
]
labels = ['Below £20,000', '£20,000–£40,000', '£40,000 and above']
colors = ['#176b8c', '#936000', '#843c92']
fig, axes = plt.subplots(1, 3, figsize=(10.8, 4.6), dpi=110, sharey=True)
fig.patch.set_facecolor('white')
for ax, tab, label, color in zip(axes, tables, labels, colors):
n = tab.sum(axis=1)
k = tab[:, 1]
p = k/n
lo, hi = wilson(k, n)
assert np.all((lo >= 0) & (hi <= 1) & (lo <= p) & (p <= hi))
x = np.arange(5)
ax.errorbar(x, 100*p, yerr=100*np.array([p-lo, hi-p]),
fmt='o-', capsize=3, color=color, lw=1.5, markersize=5)
for i, (yi, ni) in enumerate(zip(p, n)):
ax.annotate(f'n={ni}', (i, 100*yi), xytext=(0, 9),
textcoords='offset points', ha='center', fontsize=8)
ax.set_title(f'{label}\nTotal n={int(n.sum())}', fontsize=10)
ax.set_xticks(x, ['1', '2', '3', '4', '5'])
ax.set_xlabel('NHS trust category (low → high)', fontsize=9)
ax.set_ylim(0, 108)
ax.set_yticks(np.arange(0, 101, 20))
ax.grid(axis='y', color='#dedede', lw=0.6)
ax.set_facecolor('white')
ax.spines[['top', 'right']].set_visible(False)
axes[0].set_ylabel('Vaccinated respondents (%)')
fig.suptitle('Recorded vaccination and NHS trust within income groups', fontsize=12)
fig.text(0.5, 0.018, 'Original plot from reported counts; pointwise 95% Wilson intervals. Lines connect ordered categories, not equal trust increments.',
ha='center', fontsize=8)
fig.tight_layout(rect=[0, 0.065, 1, 0.94])
fig.savefig(Path.cwd() / (Path(__file__).stem + '.png'),
facecolor='white', transparent=False)
plt.close(fig)
if __name__ == '__main__':
main()