diff --git a/math_explorer_gui/src/tabs/clinical_trials/mod.rs b/math_explorer_gui/src/tabs/clinical_trials/mod.rs index c6c52b90..022df4db 100644 --- a/math_explorer_gui/src/tabs/clinical_trials/mod.rs +++ b/math_explorer_gui/src/tabs/clinical_trials/mod.rs @@ -1,9 +1,11 @@ use crate::tabs::ExplorerTab; use eframe::egui; +pub mod randomization; pub mod sample_size; pub mod survival; +use randomization::RandomizationTool; use sample_size::SampleSizeCalculatorTool; use survival::SurvivalAnalysisTool; @@ -27,6 +29,7 @@ impl Default for ClinicalTrialsTab { tools: vec![ Box::new(SurvivalAnalysisTool::default()), Box::new(SampleSizeCalculatorTool::default()), + Box::new(RandomizationTool::default()), ], selected_tool_index: 0, } diff --git a/math_explorer_gui/src/tabs/clinical_trials/randomization.rs b/math_explorer_gui/src/tabs/clinical_trials/randomization.rs new file mode 100644 index 00000000..e606493e --- /dev/null +++ b/math_explorer_gui/src/tabs/clinical_trials/randomization.rs @@ -0,0 +1,151 @@ +use super::ClinicalTrialsTool; +use eframe::egui; +use math_explorer::applied::clinical_trials::design::{ + AllocationStrategy, BlockRandomizer, Group, SimpleRandomizer, +}; + +#[derive(Debug, PartialEq, Clone, Copy)] +enum RandomizationStrategy { + Simple, + Block, +} + +pub struct RandomizationTool { + n_subjects: usize, + strategy: RandomizationStrategy, + block_size: usize, + assignments: Option, String>>, +} + +impl Default for RandomizationTool { + fn default() -> Self { + Self { + n_subjects: 20, + strategy: RandomizationStrategy::Block, + block_size: 4, + assignments: None, + } + } +} + +impl RandomizationTool { + fn trigger_randomization(&mut self) { + let mut rng = rand::thread_rng(); + + let result = match self.strategy { + RandomizationStrategy::Simple => { + let randomizer = SimpleRandomizer; + randomizer + .assign(&mut rng, self.n_subjects) + .map_err(|e| e.to_string()) + } + RandomizationStrategy::Block => match BlockRandomizer::new(self.block_size) { + Ok(randomizer) => randomizer + .assign(&mut rng, self.n_subjects) + .map_err(|e| e.to_string()), + Err(e) => Err(e.to_string()), + }, + }; + + self.assignments = Some(result); + } +} + +impl ClinicalTrialsTool for RandomizationTool { + fn name(&self) -> &'static str { + "Randomization" + } + + fn show(&mut self, ctx: &egui::Context) { + egui::CentralPanel::default().show(ctx, |ui| { + ui.heading("Randomization Tool"); + ui.label("Interactive subject allocation for clinical trials."); + ui.add_space(10.0); + + ui.horizontal(|ui| { + ui.label("Strategy:"); + ui.radio_value(&mut self.strategy, RandomizationStrategy::Simple, "Simple"); + ui.radio_value(&mut self.strategy, RandomizationStrategy::Block, "Block"); + }); + + ui.add_space(10.0); + + ui.horizontal(|ui| { + ui.label("Number of Subjects:"); + ui.add(egui::Slider::new(&mut self.n_subjects, 1..=1000).text("Subjects")); + }); + + if self.strategy == RandomizationStrategy::Block { + ui.horizontal(|ui| { + ui.label("Block Size:"); + ui.add( + egui::Slider::new(&mut self.block_size, 2..=20) + .step_by(2.0) + .text("Size"), + ); + }); + } + + ui.add_space(10.0); + + if ui.button("Randomize").clicked() { + self.trigger_randomization(); + } + + ui.add_space(20.0); + ui.separator(); + ui.add_space(10.0); + + if let Some(ref result) = self.assignments { + match result { + Ok(assignments) => { + let treatment_count = assignments + .iter() + .filter(|&&g| g == Group::Treatment) + .count(); + let control_count = assignments.len() - treatment_count; + + ui.horizontal(|ui| { + ui.label( + egui::RichText::new(format!("Treatment: {}", treatment_count)) + .color(egui::Color32::GREEN), + ); + ui.label( + egui::RichText::new(format!("Control: {}", control_count)) + .color(egui::Color32::BLUE), + ); + }); + + ui.add_space(10.0); + + egui::ScrollArea::vertical().show(ui, |ui| { + egui::Grid::new("assignments_grid") + .striped(true) + .show(ui, |ui| { + ui.label(egui::RichText::new("Subject ID").strong()); + ui.label(egui::RichText::new("Group").strong()); + ui.end_row(); + + for (i, group) in assignments.iter().enumerate() { + ui.label(format!("Subject {}", i + 1)); + match group { + Group::Treatment => { + ui.colored_label(egui::Color32::GREEN, "Treatment") + } + Group::Control => { + ui.colored_label(egui::Color32::BLUE, "Control") + } + }; + ui.end_row(); + } + }); + }); + } + Err(e) => { + ui.colored_label(egui::Color32::RED, format!("Error: {}", e)); + } + } + } + }); + } +} diff --git a/todo_gui.md b/todo_gui.md index 4a0fff5a..66817543 100644 --- a/todo_gui.md +++ b/todo_gui.md @@ -114,7 +114,7 @@ This document outlines the roadmap for integrating the various modules of the `m * **Features:** * [x] **Survival Curves:** Kaplan-Meier plot generator. * [x] **Sample Size Calculator:** Form inputs for $\alpha$, $\beta$, and effect size. - * [ ] **Randomization:** Interactive subject allocation tool. + * [x] **Randomization:** Interactive subject allocation tool. ### 4.3 Favoritism (Satirical) * **Module:** `applied::favoritism`