Group Effect on Game Win: Odds Ratio (95% CI)
//| title: "Group Effect on Game Win: Odds Ratio (95% CI)"
{
if (groupRows.length === 0)
return html`<p class="text-muted">No groups match the current filters.</p>`;
// log scale can't show non-positive bounds; ignore them for the extent
const lo = d3.min(groupRows, d => d.or_lower > 0 ? d.or_lower : null) ?? 1;
const hi = d3.max(groupRows, d => d.or_upper > 0 ? d.or_upper : null) ?? 1;
const xDomain = [Math.min(lo, 1) / 1.15, Math.max(hi, 1) * 1.15];
return Plot.plot({
height: Math.max(240, groupRows.length * 34 + 80),
marginLeft: 190,
marginRight: 30,
marginBottom: 45,
style: {fontSize: "13px"},
x: {
type: "log",
label: "Odds Ratio (log scale)",
grid: true,
domain: xDomain,
ticks: 6,
tickFormat: d => d3.format(".2f")(d)
},
y: {label: null, domain: groupRows.map(d => d.group_name)},
color: {
domain: sigDomain,
range: sigRange,
legend: true,
label: "Significance"
},
marks: [
Plot.ruleX([1], {
stroke: "gray", strokeDasharray: "4,4", strokeOpacity: 0.6
}),
Plot.link(groupRows, {
y: "group_name",
x1: "or_lower",
x2: "or_upper",
stroke: sigLevel,
strokeWidth: 2,
strokeOpacity: 0.7
}),
// Whisker caps: `markerStart/markerEnd: "tick"` needs a newer Plot
// than Quarto bundles, so draw them as marks instead.
Plot.tickX(groupRows, {
y: "group_name",
x: "or_lower",
stroke: sigLevel,
strokeWidth: 2,
strokeOpacity: 0.7,
inset: 11
}),
Plot.tickX(groupRows, {
y: "group_name",
x: "or_upper",
stroke: sigLevel,
strokeWidth: 2,
strokeOpacity: 0.7,
inset: 11
}),
Plot.dot(groupRows, {
y: "group_name",
x: "or",
fill: sigLevel,
r: 6,
stroke: "white",
strokeWidth: 1,
channels: {
Group: "group_name",
Games: "n_games_with_group",
"95% CI": d => `${fmtOR(d.or_lower)} – ${fmtOR(d.or_upper)}`,
"Win rate lift": d => `${fmtPP(d.win_rate_lift_pct)} pp`,
"p (raw)": d => fmtP(d.p),
"p (FDR)": d => fmtP(d.p_adj),
AIC: d => d3.format(".1f")(d.aic)
},
tip: {format: {
Group: true, Games: ",.0f", "95% CI": true, "Win rate lift": true,
"p (raw)": true, "p (FDR)": true, AIC: true,
x: false, y: false, fill: false, r: false
}}
})
]
});
}
Detailed Group Statistics
Inputs.table(groupRows, {
columns: [
"group_name", "n_games_with_group", "or", "or_lower", "or_upper",
"win_rate_lift_pct", "p", "p_adj", "aic", "or_cv"
],
header: {
group_name: "Group",
n_games_with_group: "Games",
or: "OR",
or_lower: "CI low",
or_upper: "CI high",
win_rate_lift_pct: "Win Rate Lift",
p: "p",
p_adj: "p (FDR)",
aic: "AIC",
or_cv: "OR CV"
},
format: {
or: orCell,
or_lower: fmtOR,
or_upper: fmtOR,
win_rate_lift_pct: liftCell,
p: pCell,
p_adj: pCell,
aic: d3.format(".1f"),
or_cv: d3.format(".3f")
},
align: {
n_games_with_group: "right", or: "right", or_lower: "right",
or_upper: "right", win_rate_lift_pct: "right", p: "right",
p_adj: "right", aic: "right", or_cv: "right"
},
rows: 20,
layout: "auto"
})