import { useState } from 'react'
/**
* Chart 5: Observed rate per group against model distribution.
* Shows observed arrest rate and overlays the posterior draw distributions from all four models
* as ridgeline-style density bars (proxy using interval bounds + median).
* Mirrors wp_fig_group_density() / wp_fig_hpd_ridges() from paper_figures.R.
*/
const MODEL_LABELS = {
unified_m1_mod: 'One-year, baseline',
unified_m2_mod: 'One-year + covariate',
unified_m3_mod: 'Three-year, baseline',
unified_m4_mod: 'Three-year + covariate',
}
export default function ObservedRateDensity({ quadData }) {
const [expandedGroup, setExpandedGroup] = useState(0) // first group by default
if (!quadData || Object.keys(quadData).length === 0) {
return (
Observed rate vs. model distribution — by student group
No model data available.
)
}
// Extract unique groups from the default model (unified_m2_mod)
const defaultModel = quadData['unified_m2_mod'] || []
const groups = [...new Map(defaultModel.map(r => [`${r.race}-${r.sex}`, { race: r.race, sex: r.sex, label: getLabel(r.race, r.sex) }])).values()]
// Get data for the expanded group across all 4 models
const currentGroup = groups[expandedGroup] || groups[0]
const modelResults = Object.entries(quadData).map(([modelId, rows]) => {
const row = rows.find(r => r.race === currentGroup?.race && r.sex === currentGroup?.sex)
if (!row) return null
// Convert rate median/interval to per-1k scale for visualization
const enroll = row.stu_enroll || 0
const observedRate = (row.observed_arrests || 0) / ((enroll || 1) / 1000)
const modeledRateMedian = (row.rate_median || 0) * 1000 // rate is per student, convert to per 1k
const modeledLower = (row.rate_lower || 0) * 1000
const modeledUpper = (row.rate_upper || 0) * 1000
return { modelId, observedRate, modeledRateMedian, modeledLower, modeledUpper }
}).filter(Boolean)
// Max value for scaling the ridgeline chart
const maxRate = Math.max(...modelResults.map(r => r.modeledUpper), ...modelResults.map(r => r.observedRate), 0.5)
const width = 280
const height = 220
const margin = { top: 30, right: 20, bottom: 60, left: 45 }
return (
{/* Chart title */}
Observed rate vs. model distribution — {currentGroup?.label || 'Select a group'}
{/* Group selector */}
{/* Ridgeline-style visualization (proxy using interval bars) */}
{/* Note */}
Solid blue = observed rate per 1,000 students. Teal outline + median line shows each model's posterior distribution (95% interval). Dashed lines indicate the range of plausible values according to each Bayesian specification.