use crate::common::{StatError, TailType, TestResult, calculate_p, mean_null_hypothesis};
use statrs::distribution::FisherSnedecor;
pub fn anova<T, I>(data_groups: &[I], alpha: f64) -> Result<TestResult, StatError>
where
T: Into<f64> + Copy,
I: AsRef<[T]>,
{
let num_groups = data_groups.len();
if num_groups < 2 {
return Err(StatError::ComputeError(
"ANOVA requires at least two groups".into(),
));
}
let mut all_values = Vec::new();
for group in data_groups {
let slice = group.as_ref();
if slice.is_empty() {
return Err(StatError::EmptyData);
}
all_values.extend(slice.iter().copied().map(Into::into));
}
let total_n = all_values.len() as f64;
let grand_mean = all_values.iter().sum::<f64>() / total_n;
let ss_between = data_groups.iter().fold(0.0, |acc, group| {
let values: Vec<f64> = group.as_ref().iter().copied().map(Into::into).collect();
let n = values.len() as f64;
let mean = values.iter().sum::<f64>() / n;
acc + n * (mean - grand_mean).powi(2)
});
let ss_within = data_groups.iter().fold(0.0, |acc, group| {
let values: Vec<f64> = group.as_ref().iter().copied().map(Into::into).collect();
let mean = values.iter().sum::<f64>() / values.len() as f64;
acc + values.iter().map(|x| (x - mean).powi(2)).sum::<f64>()
});
let df_between = (num_groups - 1) as f64;
let df_within = total_n - num_groups as f64;
if df_within <= 0.0 {
return Err(StatError::ComputeError(
"Degrees of freedom too small".into(),
));
}
let ms_between = ss_between / df_between;
let ms_within = ss_within / df_within;
if ms_within == 0.0 {
return Err(StatError::ComputeError(
"Mean square within groups is zero".into(),
));
}
let f_statistic = ms_between / ms_within;
let f_dist = FisherSnedecor::new(df_between, df_within)
.map_err(|e| StatError::ComputeError(format!("Failed to create F distribution: {e}")))?;
let p_value = calculate_p(f_statistic, TailType::Right, &f_dist);
let reject_null = p_value < alpha;
let null_hypothesis = mean_null_hypothesis(num_groups);
let alt_hypothesis = "Ha: At least one group mean is different".to_string();
Ok(TestResult {
test_statistic: f_statistic,
p_value,
reject_null,
null_hypothesis,
alt_hypothesis,
confidence_interval: (f64::NAN, f64::NAN), })
}