use crate::common::{StatError, TailType, TestResult, calculate_ci, calculate_p};
use statrs::distribution::Normal;
pub fn z_test_paired<I1, I2, T1, T2>(
data1: I1,
data2: I2,
pop_std_diff: f64,
tail: TailType,
alpha: f64,
) -> Result<TestResult, StatError>
where
I1: IntoIterator<Item = T1>,
I2: IntoIterator<Item = T2>,
T1: Into<f64>,
T2: Into<f64>,
{
if pop_std_diff <= 0.0 {
return Err(StatError::ComputeError(format!(
"Population standard deviation must be positive, got: {pop_std_diff}"
)));
}
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() != sample2.len() {
return Err(StatError::ComputeError(format!(
"Sample sizes must be equal: {} vs {}",
sample1.len(),
sample2.len()
)));
}
let n = sample1.len() as f64;
let differences: Vec<f64> = sample1
.iter()
.zip(sample2.iter())
.map(|(x1, x2)| x1 - x2)
.collect();
let sample_mean_diff = differences.iter().sum::<f64>() / n;
let std_error = pop_std_diff / n.sqrt();
let test_statistic = sample_mean_diff / std_error;
let z_dist = Normal::new(0.0, 1.0).map_err(|e| {
StatError::ComputeError(format!("Failed to create Normal distribution: {e}"))
})?;
let p_value = calculate_p(test_statistic, tail.clone(), &z_dist);
let confidence_interval = calculate_ci(sample_mean_diff, std_error, alpha, &z_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,
})
}
pub fn z_test_ind<I1, I2, T1, T2>(
data1: I1,
data2: I2,
pop_std1: f64,
pop_std2: f64,
tail: TailType,
alpha: f64,
) -> Result<TestResult, StatError>
where
I1: IntoIterator<Item = T1>,
I2: IntoIterator<Item = T2>,
T1: Into<f64>,
T2: Into<f64>,
{
if pop_std1 <= 0.0 {
return Err(StatError::ComputeError(format!(
"Population standard deviation 1 must be positive, got: {pop_std1}",
)));
}
if pop_std2 <= 0.0 {
return Err(StatError::ComputeError(format!(
"Population standard deviation 2 must be positive, got: {pop_std2}"
)));
}
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() {
return Err(StatError::EmptyData);
}
if sample2.is_empty() {
return Err(StatError::EmptyData);
}
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 std_error = ((pop_std1.powi(2) / n1) + (pop_std2.powi(2) / n2)).sqrt();
let test_statistic = (mean1 - mean2) / std_error;
let z_dist = Normal::new(0.0, 1.0).map_err(|e| {
StatError::ComputeError(format!("Failed to create Normal distribution: {e}"))
})?;
let p_value = calculate_p(test_statistic, tail.clone(), &z_dist);
let confidence_interval = calculate_ci(mean1 - mean2, std_error, alpha, &z_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,
})
}