function rmultinom(n, pr, binom) {
const out = new Array(pr.length).fill(0)
let left = n, mass = 1
for (let j = 0; j < pr.length - 1 && left > 0; j++) {
const q = Math.min(1, Math.max(0, pr[j] / mass))
out[j] = q > 0 ? binom(left, q)() : 0
left -= out[j]
mass -= pr[j]
}
out[pr.length - 1] += left
return out
}
// max_{s,k} |t_{s,k}| for 6 states x 4 votes (the first 3 votes are candidates)
function maxT(counts, pRef) {
let m = 0
for (let s = 0; s < 6; s++) {
const N = counts[4 * s] + counts[4 * s + 1] + counts[4 * s + 2] + counts[4 * s + 3]
for (let k = 0; k < 3; k++) {
const p = counts[4 * s + k] / N
const t = Math.abs(p - pRef[3 * s + k]) / Math.sqrt(p * (1 - p) / N)
m = Math.max(m, Number.isNaN(t) ? Infinity : t)
}
}
return m
}
pollCounts = rmultinom(demo.n, poll_probs, d3.randomBinomial.source(d3.randomLcg(1000 + demo.poll)))
pollShares = d3.range(18).map(i => {
const s = Math.floor(i / 3), k = i % 3
const c = pollCounts.slice(4 * s, 4 * s + 4)
return c[k] / d3.sum(c)
})
bootR = {
const binom = d3.randomBinomial.source(d3.randomLcg(7 + 31 * demo.poll))
const freq = pollCounts.map(c => c / demo.n)
return Float64Array.from({length: demo.B}, () => maxT(rmultinom(demo.n, freq, binom), pollShares))
}
cStar = d3.quantile(bootR, 0.95)
truthN = poll_truth[String(demo.n)]
bw = poll_edges[1] - poll_edges[0]
histBoot = poll_edges.slice(0, -1).map((x0, i) => {
const x1 = poll_edges[i + 1]
const inBin = bootR.filter(r => (i === 0 ? true : r >= x0) && (i === poll_edges.length - 2 ? true : r < x1)).length
return {x0, x1, dens: inBin / (demo.B * bw)}
})
truthLine = truthN.dens.map((d, i) => ({x: (poll_edges[i] + poll_edges[i + 1]) / 2, dens: d}))
Plot.plot({
width: 1150, height: 390, marginLeft: 50, marginBottom: 45,
style: {fontSize: "20px", fontFamily: "Arial, Arimo, sans-serif"},
x: {label: "max |t|", domain: [1, 5.5], labelAnchor: "center", labelArrow: "none"},
y: {label: null, domain: [0, 1.6], ticks: 4},
grid: true,
marks: [
Plot.rectY(histBoot, {x1: "x0", x2: "x1", y: "dens", fill: "#0072BC", fillOpacity: 0.35, inset: 0.5}),
Plot.line(truthLine, {x: "x", y: "dens", stroke: "#2D3E50", strokeWidth: 3, curve: "basis"}),
Plot.ruleX([1.96], {stroke: "#EB811B", strokeWidth: 3, strokeDasharray: "7,5"}),
Plot.ruleX([poll_c_limit], {stroke: "#888888", strokeWidth: 2, strokeDasharray: "3,4"}),
Plot.ruleX([truthN.c], {stroke: "#2D3E50", strokeWidth: 3}),
Plot.ruleX([cStar], {stroke: "#0072BC", strokeWidth: 3}),
Plot.text([`bootstrap ĉ* = ${cStar.toFixed(2)}`], {frameAnchor: "top-right", dx: -10, dy: 8, fill: "#0072BC", fontWeight: "bold", fontSize: 21, stroke: "white", strokeWidth: 6}),
Plot.text([`true c = ${truthN.c.toFixed(2)}`], {frameAnchor: "top-right", dx: -10, dy: 36, fill: "#2D3E50", fontWeight: "bold", fontSize: 21, stroke: "white", strokeWidth: 6}),
Plot.text([`limit = ${poll_c_limit.toFixed(2)}`], {frameAnchor: "top-right", dx: -10, dy: 64, fill: "#888888", fontWeight: "bold", fontSize: 21, stroke: "white", strokeWidth: 6}),
Plot.text([`TV: 1.96 covers all 18 only ${Math.round(100 * truthN.cov196)}% of the time`], {frameAnchor: "top-right", dx: -10, dy: 92, fill: "#EB811B", fontWeight: "bold", fontSize: 21, stroke: "white", strokeWidth: 6})
]
})