use std::fmt::Debug;
use num_traits::Float;
use rand::prelude::SliceRandom;
use rand::Rng;
use crate::Error;
pub enum PValueType {
TwoSided,
OneSidedRightTail,
OneSidedLeftTail,
}
pub fn two_samples_non_parametric_ht<
R: Rng + ?Sized,
F: Float + std::iter::Sum,
S: Clone + Default,
>(
mut rng: &mut R,
a: &[S],
b: &[S],
test_statistic_fn: impl Fn(&[S], &[S]) -> F,
pvalue_type: PValueType,
rep: usize,
) -> Result<F, Error> {
let n_a = a.len();
if n_a == 0 {
return Err(Error::NotEnoughSamples);
}
let n_b = b.len();
if n_b == 0 {
return Err(Error::NotEnoughSamples);
}
let test_stat = test_statistic_fn(a, b);
let mut test_stat_dist = vec![F::from(0.).unwrap(); rep];
let reference = [a, b].concat();
let mut a_ = vec![S::default(); n_a];
let mut b_ = vec![S::default(); n_b];
for test_stat_dist_ in test_stat_dist.iter_mut() {
for a__ in a_.iter_mut() {
*a__ = reference.choose(&mut rng).unwrap().clone();
}
for b__ in b_.iter_mut() {
*b__ = reference.choose(&mut rng).unwrap().clone();
}
let test_stat_ = test_statistic_fn(&a_, &b_);
*test_stat_dist_ = test_stat_;
}
let p_value = compute_p_value(pvalue_type, test_stat, test_stat_dist);
Ok(p_value)
}
pub fn one_sample_non_parametric_ht<
R: Rng + ?Sized,
F: Float + std::iter::Sum + Debug,
S: Clone + Default + Debug,
>(
mut rng: &mut R,
a: &[S],
test_stat: F,
test_statistic_fn: impl Fn(&[S]) -> F,
pvalue_type: PValueType,
rep: usize,
) -> Result<F, Error> {
let n_a = a.len();
if n_a == 0 {
return Err(Error::NotEnoughSamples);
}
let mut test_stat_dist = vec![F::from(0.).unwrap(); rep];
let mut a_ = vec![S::default(); n_a];
for test_stat_dist_ in test_stat_dist.iter_mut() {
for a__ in a_.iter_mut() {
*a__ = a.choose(&mut rng).unwrap().clone();
}
let test_stat_ = test_statistic_fn(&a_);
*test_stat_dist_ = test_stat_;
}
let p_value = compute_p_value(pvalue_type, test_stat, test_stat_dist);
Ok(p_value)
}
fn compute_p_value<F: Float + std::iter::Sum>(
pvalue_type: PValueType,
test_stat: F,
test_stat_dist: Vec<F>,
) -> F {
let n = F::from(test_stat_dist.len()).unwrap();
match pvalue_type {
PValueType::TwoSided => {
let right_p_value =
F::from(test_stat_dist.iter().filter(|t| **t >= test_stat).count()).unwrap() / n;
let left_p_value =
F::from(test_stat_dist.iter().filter(|t| **t <= test_stat).count()).unwrap() / n;
let min = if right_p_value <= left_p_value {
right_p_value
} else {
left_p_value
};
F::from(2.).unwrap() * min
}
PValueType::OneSidedRightTail => {
F::from(test_stat_dist.iter().filter(|&t| t >= &test_stat).count()).unwrap() / n
}
PValueType::OneSidedLeftTail => {
F::from(test_stat_dist.iter().filter(|&t| t <= &test_stat).count()).unwrap() / n
}
}
}
#[cfg(test)]
mod tests {
use itertools::Itertools;
use rand::SeedableRng;
use rand_chacha::ChaCha8Rng;
use rand_distr::{Distribution, StandardNormal};
use super::*;
#[test]
fn test_two_samples_ht() {
let a = vec![1.0, 2.0, 3.0];
let b = vec![4.0, 5.0, 6.0];
let mut rng = ChaCha8Rng::seed_from_u64(42);
let test_statistic_fn = |a: &[f64], b: &[f64]| {
let a_mean = a.iter().sum::<f64>() / a.len() as f64;
let b_mean = b.iter().sum::<f64>() / b.len() as f64;
(a_mean - b_mean).abs()
};
let p_value = two_samples_non_parametric_ht(
&mut rng,
&a,
&b,
test_statistic_fn,
PValueType::OneSidedRightTail,
10_000,
)
.unwrap();
assert_eq!(p_value, 0.0345);
}
#[test]
fn test_two_samples_normal_distributions_ht() {
let mut rng = &mut ChaCha8Rng::seed_from_u64(42);
let a = StandardNormal
.sample_iter(&mut rng)
.take(100)
.collect::<Vec<f64>>();
let b = StandardNormal
.sample_iter(&mut rng)
.take(40)
.collect::<Vec<f64>>();
let mut rng = ChaCha8Rng::seed_from_u64(42);
let test_statistic_fn = |a: &[f64], b: &[f64]| {
let a_mean = a.iter().sum::<f64>() / a.len() as f64;
let b_mean = b.iter().sum::<f64>() / b.len() as f64;
(a_mean - b_mean).abs()
};
let p_value = two_samples_non_parametric_ht(
&mut rng,
&a,
&b,
test_statistic_fn,
PValueType::OneSidedRightTail,
10_000,
)
.unwrap();
assert_eq!(p_value, 0.8298);
}
#[test]
fn test_two_normal_distributions_two_sided_ht() {
let mut rng = &mut ChaCha8Rng::seed_from_u64(42);
let a = StandardNormal
.sample_iter(&mut rng)
.take(100)
.collect::<Vec<f64>>();
let b = StandardNormal
.sample_iter(&mut rng)
.take(40)
.collect::<Vec<f64>>();
let mut rng = ChaCha8Rng::seed_from_u64(42);
let test_statistic_fn = |a: &[f64], b: &[f64]| {
let a_mean = a.iter().sum::<f64>() / a.len() as f64;
let b_mean = b.iter().sum::<f64>() / b.len() as f64;
a_mean - b_mean
};
let p_value = two_samples_non_parametric_ht(
&mut rng,
&a,
&b,
test_statistic_fn,
PValueType::TwoSided,
10_000,
)
.unwrap();
assert_eq!(p_value, 0.8222);
}
#[test]
fn test_two_different_normal_distributions_two_sided_ht() {
let mut rng = &mut ChaCha8Rng::seed_from_u64(42);
let a = StandardNormal
.sample_iter(&mut rng)
.take(100)
.collect::<Vec<f64>>();
let b = StandardNormal
.sample_iter(&mut rng)
.take(100)
.map(|x: f64| x + 0.3)
.collect::<Vec<f64>>();
let mut rng = ChaCha8Rng::seed_from_u64(42);
fn test_statistic_fn(a: &[f64], b: &[f64]) -> f64 {
let a_mean = a.iter().sum::<f64>() / a.len() as f64;
let b_mean = b.iter().sum::<f64>() / b.len() as f64;
a_mean - b_mean
}
let p_value = two_samples_non_parametric_ht(
&mut rng,
&a,
&b,
test_statistic_fn,
PValueType::TwoSided,
10_000,
)
.unwrap();
assert_eq!(p_value, 0.0418);
}
#[test]
fn test_two_different_normal_distributions_one_sided_95percentile_ht() {
let mut rng = &mut ChaCha8Rng::seed_from_u64(42);
let a = StandardNormal
.sample_iter(&mut rng)
.take(100)
.collect::<Vec<f64>>();
let b = StandardNormal
.sample_iter(&mut rng)
.take(100)
.map(|x: f64| x + 0.7)
.collect::<Vec<f64>>();
let mut rng = ChaCha8Rng::seed_from_u64(42);
let test_statistic_fn = |a: &[f64], b: &[f64]| {
let a_95percentile = a
.iter()
.sorted_by(|x, y| x.partial_cmp(y).unwrap())
.nth(95 * a.len() / 100)
.unwrap();
let b_95percentile = b
.iter()
.sorted_by(|x, y| x.partial_cmp(y).unwrap())
.nth(95 * b.len() / 100)
.unwrap();
(a_95percentile - b_95percentile).abs()
};
let p_value = two_samples_non_parametric_ht(
&mut rng,
&a,
&b,
test_statistic_fn,
PValueType::OneSidedRightTail,
10_000,
)
.unwrap();
assert_eq!(p_value, 0.0218);
}
#[test]
fn test_one_sample_std_normal_mean_ht() {
let mut rng = &mut ChaCha8Rng::seed_from_u64(42);
let a = StandardNormal
.sample_iter(&mut rng)
.take(100)
.collect::<Vec<f64>>();
let mut rng = ChaCha8Rng::seed_from_u64(42);
let test_statistic_fn = |a: &[f64]| {
let a_mean = a.iter().sum::<f64>() / a.len() as f64;
a_mean
};
let test_stat = test_statistic_fn(&a);
let p_value = one_sample_non_parametric_ht(
&mut rng,
&a,
test_stat,
test_statistic_fn,
PValueType::TwoSided,
10_000,
)
.unwrap();
assert_eq!(p_value, 0.999);
}
#[test]
fn test_one_sample_mean_ht() {
let a = [94., 197., 16., 38., 99., 141., 23.];
let a_mean = a.iter().sum::<f32>() / a.len() as f32;
let mut rng = ChaCha8Rng::seed_from_u64(42);
let test_statistic_fn = |a: &[f32]| {
let a_mean = a.iter().sum::<f32>() / a.len() as f32;
let a_std =
(a.iter().map(|x| (x - a_mean).powi(2)).sum::<f32>() / (a.len() - 1) as f32).sqrt();
(a_mean - 129.) / (a_std / (a.len() as f32).sqrt())
};
let test_stat: f32 = test_statistic_fn(&a);
let a = a
.into_iter()
.map(|x| x - a_mean + 129.)
.collect::<Vec<f32>>();
let p_value = one_sample_non_parametric_ht(
&mut rng,
&a,
test_stat,
test_statistic_fn,
PValueType::OneSidedLeftTail,
1_000,
)
.unwrap();
assert_eq!(p_value, 0.092);
}
}