use std::cmp::Ordering;
use super::analysis::EffectSize;
use super::helpers::{normal_cdf, percentile};
fn sample_stats(samples: &[f64]) -> (f64, f64, f64) {
let n = samples.len() as f64;
let mean = samples.iter().sum::<f64>() / n;
let divisor = (n - 1.0).max(1.0);
let var = samples.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / divisor;
(n, mean, var)
}
fn two_tailed_p(abs_stat: f64) -> f64 {
2.0 * (1.0 - normal_cdf(abs_stat))
}
fn sort_finite(samples: &[f64]) -> Option<Vec<f64>> {
let mut v: Vec<f64> = samples.iter().copied().filter(|x| x.is_finite()).collect();
if v.is_empty() {
return None;
}
v.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
Some(v)
}
#[derive(Debug, Clone)]
pub struct ComparisonResult {
pub t_statistic: f64,
pub p_value: f64,
pub effect_size: EffectSize,
pub statistically_significant: bool,
pub practically_significant: bool,
pub degrees_of_freedom: f64,
}
impl ComparisonResult {
pub fn welch_t_test(sample1: &[f64], sample2: &[f64]) -> Option<Self> {
if sample1.len() < 2 || sample2.len() < 2 {
return None;
}
let (n1, mean1, var1) = sample_stats(sample1);
let (n2, mean2, var2) = sample_stats(sample2);
let se1 = var1 / n1;
let se2 = var2 / n2;
let se_diff = (se1 + se2).sqrt();
if se_diff == 0.0 {
return None;
}
let t = (mean1 - mean2) / se_diff;
let df = (se1 + se2).powi(2) / (se1.powi(2) / (n1 - 1.0) + se2.powi(2) / (n2 - 1.0));
let p_value = two_tailed_p(t.abs());
let effect_size = EffectSize::cohens_d(sample1, sample2)?;
Some(Self {
t_statistic: t,
p_value,
effect_size: effect_size.clone(),
statistically_significant: p_value < 0.05,
practically_significant: effect_size.is_significant(),
degrees_of_freedom: df,
})
}
pub fn is_meaningful(&self) -> bool {
self.statistically_significant && self.practically_significant
}
}
#[derive(Debug, Clone)]
pub struct MannWhitneyResult {
pub u_statistic: f64,
pub p_value: f64,
pub effect_size: f64,
pub significant: bool,
}
impl MannWhitneyResult {
pub fn test(sample1: &[f64], sample2: &[f64]) -> Option<Self> {
if sample1.is_empty() || sample2.is_empty() {
return None;
}
let n1 = sample1.len();
let n2 = sample2.len();
let mut combined: Vec<(f64, usize)> = sample1
.iter()
.map(|&x| (x, 0))
.chain(sample2.iter().map(|&x| (x, 1)))
.collect();
combined.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal));
let mut ranks: Vec<f64> = vec![0.0; combined.len()];
let mut i = 0;
while i < combined.len() {
let mut j = i;
while j < combined.len() && combined[j].0 == combined[i].0 {
j += 1;
}
let avg_rank = (i + j + 1) as f64 / 2.0;
for k in i..j {
ranks[k] = avg_rank;
}
i = j;
}
let r1: f64 = combined
.iter()
.enumerate()
.filter(|(_, (_, group))| *group == 0)
.map(|(idx, _)| ranks[idx])
.sum();
let u1 = r1 - (n1 * (n1 + 1)) as f64 / 2.0;
let u2 = (n1 * n2) as f64 - u1;
let u = u1.min(u2);
let mean_u = (n1 * n2) as f64 / 2.0;
let std_u = ((n1 * n2 * (n1 + n2 + 1)) as f64 / 12.0).sqrt();
let z = if std_u > 0.0 {
(u - mean_u) / std_u
} else {
0.0
};
let p_value = two_tailed_p(z.abs());
let effect_size = 1.0 - (2.0 * u) / (n1 * n2) as f64;
Some(Self {
u_statistic: u,
p_value,
effect_size,
significant: p_value < 0.05,
})
}
}
#[derive(Debug, Clone)]
pub struct OutlierFilter {
pub lower_fence: f64,
pub upper_fence: f64,
pub q1: f64,
pub q3: f64,
pub iqr: f64,
pub multiplier: f64,
}
impl OutlierFilter {
pub fn new(samples: &[f64]) -> Option<Self> {
Self::with_multiplier(samples, 1.5)
}
pub fn with_multiplier(samples: &[f64], multiplier: f64) -> Option<Self> {
let sorted = sort_finite(samples)?;
let q1 = percentile(&sorted, 0.25);
let q3 = percentile(&sorted, 0.75);
let iqr = q3 - q1;
let lower_fence = q1 - multiplier * iqr;
let upper_fence = q3 + multiplier * iqr;
Some(Self {
lower_fence,
upper_fence,
q1,
q3,
iqr,
multiplier,
})
}
pub fn is_outlier(&self, value: f64) -> bool {
value < self.lower_fence || value > self.upper_fence
}
pub fn filter(&self, samples: &[f64]) -> Vec<f64> {
samples
.iter()
.copied()
.filter(|&x| !self.is_outlier(x))
.collect()
}
pub fn count_outliers(&self, samples: &[f64]) -> usize {
samples.iter().filter(|&&x| self.is_outlier(x)).count()
}
}