use crate::common::{StatError, TailType, TestResult, calculate_ci, calculate_p};
use crate::t::t_test;
use statrs::distribution::StudentsT;
pub fn t_test_paired<I1, I2, T1, T2>(
data1: I1,
data2: I2,
tail: TailType,
alpha: f64,
) -> Result<TestResult, StatError>
where
I1: IntoIterator<Item = T1>,
I2: IntoIterator<Item = T2>,
T1: Into<f64>,
T2: Into<f64>,
{
let sample1: Vec<f64> = data1.into_iter().map(|x| x.into()).collect();
let sample2: Vec<f64> = data2.into_iter().map(|x| x.into()).collect();
if sample1.len() != sample2.len() {
return Err(StatError::ComputeError(format!(
"Sample sizes must be equal for paired t-test. Got {} and {}",
sample1.len(),
sample2.len()
)));
}
let differences: Vec<f64> = sample1
.iter()
.zip(sample2.iter())
.map(|(x1, x2)| x1 - x2)
.collect();
let mut result = t_test(differences.iter().copied(), 0.0, tail.clone(), alpha)?;
result.null_hypothesis = match tail {
TailType::Left => "H0: µ1 >= µ2".to_string(),
TailType::Right => "H0: µ1 <= µ2".to_string(),
TailType::Two => "H0: µ1 = µ2".to_string(),
};
result.alt_hypothesis = match tail {
TailType::Left => "Ha: µ1 < µ2".to_string(),
TailType::Right => "Ha: µ1 > µ2".to_string(),
TailType::Two => "Ha: µ1 ≠ µ2".to_string(),
};
Ok(result)
}
pub fn t_test_paired_vec(
data1: &[f64],
data2: &[f64],
tail: TailType,
alpha: f64,
) -> Result<TestResult, StatError> {
t_test_paired(data1.iter().copied(), data2.iter().copied(), tail, alpha)
}
pub fn t_test_ind<I1, I2, T1, T2>(
data1: I1,
data2: I2,
tail: TailType,
alpha: f64,
pooled: bool,
) -> Result<TestResult, StatError>
where
I1: IntoIterator<Item = T1>,
I2: IntoIterator<Item = T2>,
T1: Into<f64>,
T2: Into<f64>,
{
let sample1: Vec<f64> = data1.into_iter().map(|x| x.into()).collect();
let sample2: Vec<f64> = data2.into_iter().map(|x| x.into()).collect();
if sample1.is_empty() || sample2.is_empty() {
return Err(StatError::EmptyData);
}
if sample1.len() < 2 || sample2.len() < 2 {
return Err(StatError::InsufficientData);
}
let n1 = sample1.len() as f64;
let n2 = sample2.len() as f64;
let mean1 = sample1.iter().sum::<f64>() / n1;
let mean2 = sample2.iter().sum::<f64>() / n2;
let var1 = sample1.iter().map(|x| (x - mean1).powi(2)).sum::<f64>() / (n1 - 1.0);
let var2 = sample2.iter().map(|x| (x - mean2).powi(2)).sum::<f64>() / (n2 - 1.0);
let (std_error, df) = if pooled {
let pooled_var = ((n1 - 1.0) * var1 + (n2 - 1.0) * var2) / (n1 + n2 - 2.0);
let std_error = (pooled_var * (1.0 / n1 + 1.0 / n2)).sqrt();
let df = n1 + n2 - 2.0;
(std_error, df)
} else {
let std_error = (var1 / n1 + var2 / n2).sqrt();
let df = (var1 / n1 + var2 / n2).powi(2)
/ ((var1 / n1).powi(2) / (n1 - 1.0) + (var2 / n2).powi(2) / (n2 - 1.0));
(std_error, df)
};
let test_statistic = (mean1 - mean2) / std_error;
let t_dist = StudentsT::new(0.0, 1.0, df).map_err(|e| {
StatError::ComputeError(format!("Failed to create StudentsT distribution: {e}"))
})?;
let p_value = calculate_p(test_statistic, tail.clone(), &t_dist);
let confidence_interval = calculate_ci(mean1 - mean2, std_error, alpha, &t_dist);
let reject_null = p_value < alpha;
let null_hypothesis = match tail {
TailType::Left => "H0: µ1 >= µ2".to_string(),
TailType::Right => "H0: µ1 <= µ2".to_string(),
TailType::Two => "H0: µ1 = µ2".to_string(),
};
let alt_hypothesis = match tail {
TailType::Left => "Ha: µ1 < µ2".to_string(),
TailType::Right => "Ha: µ1 > µ2".to_string(),
TailType::Two => "Ha: µ1 ≠ µ2".to_string(),
};
Ok(TestResult {
test_statistic,
p_value,
confidence_interval,
null_hypothesis,
alt_hypothesis,
reject_null,
})
}