use crate::accum::moments::{checked_variance, comoment_pairs, pooled_var};
use crate::error::StatError;
use crate::htest::result::Ci;
use crate::special::{erfc_inv, inv_beta_reg};
fn check_level(level: f64) -> Result<(), StatError> {
if !(0.0..1.0).contains(&level) { Err(StatError::ProbabilityOutOfRange(level)) } else { Ok(()) }
}
fn t_crit(level: f64, df: f64) -> f64 {
let alpha_tail = 1.0 - level;
let x = inv_beta_reg(df / 2.0, 0.5, alpha_tail);
(df * (1.0 / x - 1.0)).sqrt()
}
fn z_crit(level: f64) -> f64 {
core::f64::consts::SQRT_2 * erfc_inv(1.0 - level)
}
pub fn ci_mean(v: &[f64], level: f64) -> Result<Ci, StatError> {
check_level(level)?;
let s = checked_variance(v)?;
let n = s.count() as f64;
let se = s.sd_sample() / n.sqrt();
let t = t_crit(level, n - 1.0);
Ok(Ci { lower: s.mean() - t * se, upper: s.mean() + t * se, level })
}
pub fn ci_mean_diff(a: &[f64], b: &[f64], level: f64) -> Result<Ci, StatError> {
check_level(level)?;
let (sa, sb) = (checked_variance(a)?, checked_variance(b)?);
let (na, nb) = (sa.count() as f64, sb.count() as f64);
let se = (pooled_var(&sa, &sb) * (1.0 / na + 1.0 / nb)).sqrt();
let t = t_crit(level, na + nb - 2.0);
let d = sa.mean() - sb.mean();
Ok(Ci { lower: d - t * se, upper: d + t * se, level })
}
pub fn ci_mean_diff_welch(a: &[f64], b: &[f64], level: f64) -> Result<Ci, StatError> {
check_level(level)?;
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 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));
let t = t_crit(level, df);
let d = sa.mean() - sb.mean();
Ok(Ci { lower: d - t * se, upper: d + t * se, level })
}
pub fn ci_proportion(successes: usize, n: usize, level: f64) -> Result<Ci, StatError> {
if n == 0 { return Err(StatError::EmptyInput); }
check_level(level)?;
let p = successes as f64 / n as f64;
let se = (p * (1.0 - p) / n as f64).sqrt();
let z = z_crit(level);
Ok(Ci { lower: p - z * se, upper: p + z * se, level })
}
pub fn ci_correlation(a: &[f64], b: &[f64], level: f64) -> Result<Ci, StatError> {
check_level(level)?;
let c = comoment_pairs(a, b)?;
let n = c.count() as f64;
if n < 4.0 { return Err(StatError::TooFewObservations { needed: 4, got: n as usize }); }
let r = c.pearson();
let zr = r.atanh();
let se = 1.0 / (n - 3.0).sqrt();
let zc = z_crit(level);
Ok(Ci { lower: (zr - zc * se).tanh(), upper: (zr + zc * se).tanh(), level })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn t_crit_matches_table() {
assert!((t_crit(0.95, 9.0) - 2.262157).abs() < 1e-4, "got {}", t_crit(0.95, 9.0));
}
#[test]
fn ci_mean_known() {
let ci = ci_mean(&[1., 2., 3., 4., 5.], 0.95).unwrap();
assert!((ci.lower - 1.036756838522439).abs() < 1e-6, "lower {}", ci.lower);
assert!((ci.upper - 4.963243161477561).abs() < 1e-6, "upper {}", ci.upper);
}
}
#[cfg(test)]
mod ci2_tests {
use super::*;
#[test]
fn proportion_ci_known() {
let ci = ci_proportion(40, 100, 0.95).unwrap();
assert!((ci.lower - 0.3039817664728938).abs() < 1e-9, "lower {}", ci.lower);
assert!((ci.upper - 0.4960182335271062).abs() < 1e-9, "upper {}", ci.upper);
}
}