1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum Fdr {
6 BenjaminiHochberg,
8 BenjaminiYekutieli,
10}
11
12pub fn adjust(p: &[f64], method: Fdr) -> Vec<f64> {
14 let mut order: Vec<usize> = (0..p.len()).filter(|&i| !p[i].is_nan()).collect();
15 let m = order.len() as f64;
16 let scale = match method {
17 Fdr::BenjaminiHochberg => 1.0,
18 Fdr::BenjaminiYekutieli => (1..=order.len()).map(|i| 1.0 / i as f64).sum(),
19 };
20 order.sort_by(|&a, &b| p[a].total_cmp(&p[b]));
21 let mut q = vec![f64::NAN; p.len()];
22 let mut running = 1.0f64;
23 for (rank, &i) in order.iter().enumerate().rev() {
24 running = running.min(p[i] * m * scale / (rank + 1) as f64);
25 q[i] = running.min(1.0);
26 }
27 q
28}
29
30#[cfg(test)]
31mod tests {
32 use super::*;
33
34 #[test]
35 fn bh_and_by_by_hand() {
36 let p = [0.01, 0.04, 0.03, f64::NAN, 0.5];
37 let bh = adjust(&p, Fdr::BenjaminiHochberg);
38 assert!((bh[0] - 0.04).abs() < 1e-15);
40 assert!((bh[1] - 0.04 * 4.0 / 3.0).abs() < 1e-15 && (bh[2] - bh[1]).abs() < 1e-15);
41 assert!(bh[3].is_nan() && (bh[4] - 0.5).abs() < 1e-15);
42 let by = adjust(&p, Fdr::BenjaminiYekutieli);
43 let c: f64 = 1.0 + 0.5 + 1.0 / 3.0 + 0.25;
44 assert!((by[0] - (0.04 * c).min(1.0)).abs() < 1e-15);
45 assert_eq!(adjust(&[0.9, 0.95], Fdr::BenjaminiYekutieli), [1.0, 1.0]);
46 assert!(adjust(&[], Fdr::BenjaminiHochberg).is_empty());
47 }
48}