codexpast-exam-of-the-digital-social-research-statistics-core-course-of-the-university-of-oxford2018-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()