#[must_use]
pub fn benjamini_hochberg(pvalues: &[f32]) -> Vec<f32> {
let m = pvalues.len();
if m == 0 {
return Vec::new();
}
let mut order: Vec<usize> = (0..m).collect();
order.sort_by(|&a, &b| {
pvalues[a]
.partial_cmp(&pvalues[b])
.unwrap_or(std::cmp::Ordering::Equal)
});
let mut q = vec![0.0f32; m];
let mut running_min = 1.0f32;
for rank in (0..m).rev() {
let idx = order[rank];
let adj = (pvalues[idx] * (m as f32) / ((rank + 1) as f32)).clamp(0.0, 1.0);
running_min = running_min.min(adj);
q[idx] = running_min;
}
q
}
#[must_use]
pub fn mean(x: &[f32]) -> f32 {
if x.is_empty() {
f32::NAN
} else {
x.iter().sum::<f32>() / x.len() as f32
}
}
pub fn bootstrap_mean_ci(
x: &[f32],
n_boot: usize,
alpha: f64,
rng: &mut impl rand::RngExt,
) -> (f32, f32, f32) {
let n = x.len();
if n == 0 || n_boot == 0 {
return (f32::NAN, f32::NAN, f32::NAN);
}
let mut means: Vec<f32> = (0..n_boot)
.map(|_| {
let mut s = 0f32;
for _ in 0..n {
s += x[rng.random_range(0..n)];
}
s / n as f32
})
.collect();
means.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let m = mean(&means);
let var = if n_boot < 2 {
0.0
} else {
means.iter().map(|&v| (v - m) * (v - m)).sum::<f32>() / (n_boot - 1) as f32
};
let se = var.max(0.0).sqrt();
let lo = means[(((alpha / 2.0) * n_boot as f64) as usize).min(n_boot - 1)];
let hi = means[(((1.0 - alpha / 2.0) * n_boot as f64) as usize).min(n_boot - 1)];
(se, lo, hi)
}
pub fn sign_flip_pvalue(x: &[f32], n_perm: usize, rng: &mut impl rand::RngExt) -> f32 {
let n = x.len();
if n == 0 {
return f32::NAN;
}
let obs = mean(x).abs();
let mut ge = 0usize;
for _ in 0..n_perm {
let mut s = 0f32;
for &xi in x {
if rng.random::<bool>() {
s += xi;
} else {
s -= xi;
}
}
if (s / n as f32).abs() >= obs {
ge += 1;
}
}
(1.0 + ge as f32) / (1.0 + n_perm as f32)
}
#[cfg(test)]
#[path = "hypothesis/tests.rs"]
mod tests;