codex/past-exam-of-the-digital-social-research-statistics-core-course-of-the-university-of-oxford/2018-blocking-by-age.py

past-exam-of-the-digital-social-research-statistics-core-course-of-the-university-of-oxford/2018-blocking-by-age.py
"""Original count-derived blocking comparisons for Oxford Statistics Core 2018.

Python 3.14.4, NumPy 2.3.5, Matplotlib 3.10.7 (root pyproject).
Writes the same-basename opaque white PNG to CWD. No individual records or
third-party image inputs are used.
"""
from pathlib import Path
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt


def main():
    tables=np.array([[[9,70,19],[13,132,55]],[[11,56,7],[15,139,63]],[[16,85,10],[9,174,45]],[[12,53,9],[20,174,28]],[[6,79,5],[16,272,27]]])
    pooled=tables.sum(0)
    assert np.array_equal(pooled,[[54,343,50],[73,891,218]])
    rates=tables[:,:,2]/tables.sum(2)
    difference=100*(rates[:,1]-rates[:,0])
    weights=tables.sum((1,2))/tables.sum()
    standardized=weights@rates
    adjusted=100*(standardized[1]-standardized[0])
    crude=100*(218/1182-50/447)
    plt.rcParams.update({'font.size':10,'axes.spines.top':False,'axes.spines.right':False})
    fig,(ax,bx)=plt.subplots(1,2,figsize=(10,4.3),dpi=100,gridspec_kw={'width_ratios':[1.2,1]})
    fig.patch.set_facecolor('white');x=np.arange(5);labels=['18–24','25–34','35–44','45–54','55+']
    ax.bar(x-.17,100*rates[:,0],width=.34,color='#7e96a9',label='Not populist')
    ax.bar(x+.17,100*rates[:,1],width=.34,color='#b15c27',label='Populist')
    ax.set(xticks=x,xticklabels=labels,xlabel='Age group',ylabel='Reported Yes (%)',ylim=(0,35),title='Within-group blocking reports')
    ax.legend(loc='upper right',fontsize=9)
    bx.plot(difference,x,'o',color='#244b67',markersize=6)
    bx.hlines(x,0,difference,color='#8da4b4',linewidth=2)
    bx.axvline(crude,color='#555555',linestyle='--',linewidth=1,label=f'Crude: {crude:.2f} pp')
    bx.axvline(adjusted,color='#b15c27',linestyle=':',linewidth=1.4,label=f'Common-age weights: {adjusted:.2f} pp')
    bx.set(yticks=x,yticklabels=labels,xlabel='Populist minus not-populist (pp)',title='Descriptive contrasts',xlim=(-1,23))
    bx.invert_yaxis();bx.legend(fontsize=8,loc='lower right')
    fig.suptitle('Reported blocking by populism and age: 1,629 complete cases',fontsize=12)
    fig.text(.5,.012,'Not know remains a valid non-Yes response. Points show empirical contrasts, without uncertainty intervals.',ha='center',fontsize=8)
    fig.tight_layout(rect=(0,.045,1,.92))
    fig.savefig(Path.cwd()/(Path(__file__).stem+'.png'),facecolor='white',transparent=False,metadata={'Software':'Original derived plot; NumPy/Matplotlib'})
    plt.close(fig)


if __name__=='__main__':
    main()