use crate::accum::moments::{checked_variance, pooled_var};
use crate::error::StatError;
use crate::htest::effect::cohen_d;
use crate::htest::ci::{ci_mean, ci_mean_diff, ci_mean_diff_welch};
use crate::htest::result::{EffectSize, TestResult};
use crate::special::betai;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VarAssumption {
Equal,
Welch,
}
fn t_two_sided_p(t: f64, df: f64) -> f64 {
let x = df / (df + t * t);
betai(df / 2.0, 0.5, x)
}
pub fn t_test_one(v: &[f64], mu0: f64) -> Result<TestResult, StatError> {
let s = checked_variance(v)?;
let n = s.count() as f64;
let se = s.sd_sample() / n.sqrt();
let t = (s.mean() - mu0) / se;
let df = n - 1.0;
Ok(TestResult {
statistic: t, df, df2: None, p_value: t_two_sided_p(t, df),
effect_size: Some(EffectSize::CohenD((s.mean() - mu0) / s.sd_sample())),
ci: ci_mean(v, 0.95).ok(),
})
}
pub fn t_test_two(a: &[f64], b: &[f64], va: VarAssumption) -> Result<TestResult, StatError> {
let (sa, sb) = (checked_variance(a)?, checked_variance(b)?);
let (na, nb) = (sa.count() as f64, sb.count() as f64);
let (va_, vb) = (sa.var_sample(), sb.var_sample());
let (t, df) = match va {
VarAssumption::Equal => {
let se = (pooled_var(&sa, &sb) * (1.0 / na + 1.0 / nb)).sqrt();
((sa.mean() - sb.mean()) / se, na + nb - 2.0)
}
VarAssumption::Welch => {
let se = (va_ / na + vb / nb).sqrt();
let df = (va_ / na + vb / nb).powi(2)
/ ((va_ / na).powi(2) / (na - 1.0) + (vb / nb).powi(2) / (nb - 1.0));
((sa.mean() - sb.mean()) / se, df)
}
};
let ci = match va {
VarAssumption::Equal => ci_mean_diff(a, b, 0.95),
VarAssumption::Welch => ci_mean_diff_welch(a, b, 0.95),
};
Ok(TestResult {
statistic: t, df, df2: None, p_value: t_two_sided_p(t, df),
effect_size: Some(EffectSize::CohenD(cohen_d(a, b)?)),
ci: ci.ok(),
})
}
pub fn t_test_paired(a: &[f64], b: &[f64]) -> Result<TestResult, StatError> {
if a.len() != b.len() { return Err(StatError::MismatchedLengths { a: a.len(), b: b.len() }); }
let diff: Vec<f64> = a.iter().zip(b).map(|(x, y)| x - y).collect();
t_test_one(&diff, 0.0)
}