AsianPlayer/TriDrive / questionnaire /scripts /make_user_study_fig_n14.py
AsianPlayer's picture
download
raw
4.19 kB
"""n = 14 variant: missing endpoint scores imputed with the neutral value 4 (appendix sensitivity analysis). Table 5 -> figure: three paired-bar panels (appropriateness, timeliness, annoyance) with participant points, paired lines,
95% participant-bootstrap CIs of each condition mean, and the paired Delta with its reported CI and Wilcoxon p. Paper palette."""
import re, numpy as np, matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt, matplotlib.font_manager as fm
from matplotlib.lines import Line2D
import glob
for f in glob.glob("/usr/share/fonts/truetype/msttcorefonts/Times*.ttf"): fm.fontManager.addfont(f)
plt.rcParams.update({"font.family":"Times New Roman","font.size":7,"axes.linewidth":0.5,"xtick.major.width":0.5,"ytick.major.width":0.5,"pdf.fonttype":42})
GREY="#8C8C8C"; LAV="#DDD6E5"; LAVL="#DFEDF2"; INK="#2B2B2B"; PEACHL="#F5D8BF"; LINE="#C8C8C8"
# participant table (appendix H): ID, APP C, APP S, TIM C, TIM S, ANN C, ANN S
rows="""P01 4.00 2.50 7 1 6 --
P02 6.33 4.00 7 1 2 2
P03 4.00 1.67 4 2 2 1
P04 6.33 1.67 6 -- 3 --
P05 6.00 5.00 7 2 4 4
P06 4.33 3.67 6 4 5 1
P07 4.00 4.00 4 4 1 1
P08 3.33 3.00 4 3 5 1
P09 6.67 1.67 6 1 1 1
P10 5.67 1.67 6 1 2 1
P11 7.00 7.00 7 7 1 1
P12 3.67 3.67 5 5 2 1
P13 6.00 6.67 6 6 1 1
P14 6.50 2.67 -- 3 5 2""".splitlines()
def f(x): return 4.0 if x=="--" else float(x) # neutral imputation
D=np.array([[f(v) for v in r.split()[1:]] for r in rows])
from scipy.stats import wilcoxon
rng=np.random.default_rng(20260922)
def stats(c,s):
d=c-s; b=[rng.choice(d,len(d)).mean() for _ in range(2000)]; lo,hi=np.percentile(b,2.5),np.percentile(b,97.5); p=wilcoxon(c,s).pvalue
return f"{d.mean():+.2f} [{lo:.2f}, {hi:.2f}]", ("p = "+f"{p:.3f}".lstrip("0"))
# Delta / CI / p as reported in Appendix H (neutral imputation, 2,000 participant resamples, seed 20260922); computed values printed below for the check
panels=[("Appropriateness",D[:,0],D[:,1],"+1.79 [0.86, 2.75]","p = .008",True),
("Timeliness",D[:,2],D[:,3],"+2.50 [1.36, 3.71]","p = .005",True),
("Annoyance",D[:,4],D[:,5],"+1.07 [0.36, 1.93]","p = .028",False)]
def ci(v):
b=[rng.choice(v,len(v)).mean() for _ in range(2000)]; return np.percentile(b,2.5),np.percentile(b,97.5)
fig,axes=plt.subplots(1,3,figsize=(5.5,1.08),dpi=300,gridspec_kw=dict(wspace=0.5,left=0.065,right=0.995,top=0.69,bottom=0.26))
for ax,(name,c,s,dtxt,ptxt,higher_good) in zip(axes,panels):
m=~np.isnan(c)&~np.isnan(s); c,s=c[m],s[m]; n=int(m.sum())
means=[s.mean(),c.mean()]; cis=[ci(s),ci(c)]
x=[0,1]
ax.bar(x,means,width=0.62,color=[GREY,LAV],edgecolor=[GREY,"#B9AFC9"],linewidth=0.5,zorder=1)
for xi,(lo,hi) in zip(x,cis): ax.plot([xi,xi],[lo,hi],color=INK,lw=0.8,zorder=4); ax.plot([xi-0.08,xi+0.08],[lo,lo],color=INK,lw=0.6,zorder=4); ax.plot([xi-0.08,xi+0.08],[hi,hi],color=INK,lw=0.6,zorder=4)
jit=rng.uniform(-0.13,0.13,n)
for k in range(n): ax.plot([0+jit[k],1+jit[k]],[s[k],c[k]],color=LINE,lw=0.4,zorder=2)
ax.scatter(0+jit,s,s=5,color="white",edgecolor=INK,linewidth=0.4,zorder=3); ax.scatter(1+jit,c,s=5,color="white",edgecolor=INK,linewidth=0.4,zorder=3)
ax.set_xticks(x); ax.set_xticklabels([f"openpilot\n{means[0]:.2f}",f"TriDrive\n{means[1]:.2f}"],fontsize=6.5,linespacing=1.1); ax.set_xlim(-0.55,1.55)
ax.set_ylim(0.5,7.6); ax.set_yticks([1,4,7]); ax.tick_params(length=2,pad=1.5)
for sp in ("top","right"): ax.spines[sp].set_visible(False)
ax.spines["left"].set_color(GREY); ax.spines["bottom"].set_color(GREY); ax.tick_params(colors=INK)
ax.text(0.5,1.37,f"{name} (n = {n})",ha="center",va="bottom",fontsize=7.2,color=INK,transform=ax.transAxes,fontweight="bold")
ax.text(0.5,1.09,f"$\\Delta$ {dtxt}, {ptxt}",ha="center",va="bottom",fontsize=6.3,color=INK,transform=ax.transAxes,bbox=dict(boxstyle="round,pad=0.2",fc=PEACHL,ec="none"))
axes[0].set_ylabel("Rating (1–7)",fontsize=6.5,color=INK,labelpad=1)
out="figures/user_study_bars_n14.pdf"
fig.savefig(out); fig.savefig(out.replace(".pdf",".png"),dpi=300); print("saved",out)
for name,c,s,dtxt,ptxt,_ in panels: print(name,len(c),"means C/S",round(c.mean(),2),round(s.mean(),2),"reported",dtxt,ptxt,"| computed",*stats(c,s))

Xet Storage Details

Size:
4.19 kB
·
Xet hash:
06b2c9ab004816765755b80a6597fd4678c736977774064ab33465516d0b7404

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.