1use anyhow::{Result, anyhow};
23use single_utilities::traits::FloatOps;
24use std::cmp::Ordering;
25
26pub fn bonferroni_correction<T>(p_values: &[T]) -> Result<Vec<T>>
43where
44 T: FloatOps,
45{
46 let n = p_values.len();
47
48 if n == 0 {
49 return Err(anyhow!("Empty p-value array"));
50 }
51
52 for (i, &p) in p_values.iter().enumerate() {
54 if !(T::zero()..=T::one()).contains(&p) {
55 return Err(anyhow!("Invalid p-value at index {:?}: {:?}", i, p));
56 }
57 }
58
59 let n_t = T::from(n).unwrap();
61 let adjusted = p_values
62 .iter()
63 .map(|&p| num_traits::Float::min(p * n_t, T::one()))
64 .collect();
65
66 Ok(adjusted)
67}
68
69pub fn benjamini_hochberg_correction<T>(p_values: &[T]) -> Result<Vec<T>>
86where
87 T: FloatOps,
88{
89 let n = p_values.len();
90 if n == 0 {
91 return Err(anyhow::anyhow!("Empty p-value array"));
92 }
93
94 for (i, &p) in p_values.iter().enumerate() {
96 if !(T::zero()..=T::one()).contains(&p) {
97 return Err(anyhow::anyhow!("Invalid p-value at index {:?}: {:?}", i, p));
98 }
99 }
100
101 let n_f = T::from(n).unwrap();
102
103 let mut indices: Vec<usize> = (0..n).collect();
104 indices.sort_unstable_by(|&a, &b| {
105 p_values[a]
106 .partial_cmp(&p_values[b])
107 .unwrap_or(Ordering::Equal)
108 });
109
110 let mut adjusted = vec![T::zero(); n];
111 let mut running_min = T::one();
112
113 for i in (0..n).rev() {
114 let orig_idx = indices[i];
115 let rank = T::from(i + 1).unwrap();
116 let adjustment = num_traits::Float::min(p_values[orig_idx] * n_f / rank, T::one());
117 running_min = num_traits::Float::min(adjustment, running_min);
118 adjusted[orig_idx] = running_min;
119 }
120
121 Ok(adjusted)
122}
123
124pub fn benjamini_yekutieli_correction<T>(p_values: &[T]) -> Result<Vec<T>>
141where
142 T: FloatOps,
143{
144 let n = p_values.len();
145 if n == 0 {
146 return Err(anyhow!("Empty p-value array"));
147 }
148
149 let c_n: T = (1..=n).map(|i| T::one() / T::from(i).unwrap()).sum();
151
152 let mut indexed_p_values: Vec<(usize, T)> =
154 p_values.iter().enumerate().map(|(i, &p)| (i, p)).collect();
155
156 indexed_p_values.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(Ordering::Equal));
158
159 let mut adjusted_p_values = vec![T::zero(); n];
161 let mut current_min = T::one();
162 let n_f64 = T::from(n).unwrap();
163 for i in (0..n).rev() {
165 let (orig_idx, p_val) = indexed_p_values[i];
166 let rank = i + 1;
167
168 let adjustment =
170 num_traits::Float::min(p_val * c_n * n_f64 / T::from(rank).unwrap(), T::one());
171 current_min = num_traits::Float::min(adjustment, current_min);
172 adjusted_p_values[orig_idx] = current_min;
173 }
174
175 Ok(adjusted_p_values)
176}
177
178pub fn holm_bonferroni_correction<T>(p_values: &[T]) -> Result<Vec<T>>
195where
196 T: FloatOps,
197{
198 let n = p_values.len();
199
200 if n == 0 {
201 return Err(anyhow!("Empty p-value array"));
202 }
203
204 let zero = T::zero();
205 let one = T::one();
206
207 for (i, &p) in p_values.iter().enumerate() {
209 if p < zero || p > one {
210 return Err(anyhow!("Invalid p-value at index {}: {:?}", i, p));
211 }
212 }
213
214 let mut indexed_p_values: Vec<(usize, T)> =
216 p_values.iter().enumerate().map(|(i, &p)| (i, p)).collect();
217
218 indexed_p_values.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(Ordering::Equal));
219
220 let mut adjusted_p_values = vec![zero; n];
222
223 for (i, &(idx, p_val)) in indexed_p_values.iter().enumerate() {
225 let multiplier = T::from((n - i) as f64).unwrap_or(one);
227 let adjusted_p = p_val * multiplier;
228 adjusted_p_values[idx] = num_traits::Float::min(adjusted_p, one);
229 }
230
231 Ok(adjusted_p_values)
232}
233
234pub fn hochberg_correction<T>(p_values: &[T]) -> Result<Vec<T>>
251where
252 T: FloatOps,
253{
254 let n = p_values.len();
255
256 if n == 0 {
257 return Err(anyhow!("Empty p-value array"));
258 }
259
260 let zero = T::zero();
261 let one = T::one();
262
263 let mut indexed_p_values: Vec<(usize, T)> =
265 p_values.iter().enumerate().map(|(i, &p)| (i, p)).collect();
266
267 indexed_p_values.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(Ordering::Equal));
268
269 let mut adjusted_p_values = vec![zero; n];
271
272 adjusted_p_values[indexed_p_values[0].0] = indexed_p_values[0].1;
274
275 for i in 1..n {
277 let current_index = indexed_p_values[i].0;
278 let prev_index = indexed_p_values[i - 1].0;
279
280 let n_t = T::from(n).unwrap_or(one);
282 let denominator_t = T::from(n - i).unwrap_or(one);
283
284 let hochberg_value =
285 num_traits::Float::min(indexed_p_values[i].1 * n_t / denominator_t, one);
286 adjusted_p_values[current_index] =
287 num_traits::Float::min(hochberg_value, adjusted_p_values[prev_index]);
288 }
289
290 Ok(adjusted_p_values)
291}
292
293pub fn storey_qvalues<T>(p_values: &[T], lambda: T) -> Result<Vec<T>>
311where
312 T: FloatOps,
313{
314 let n = p_values.len();
315
316 if n == 0 {
317 return Err(anyhow!("Empty p-value array"));
318 }
319
320 let zero = T::zero();
321 let one = T::one();
322
323 if lambda <= zero || lambda >= one {
324 return Err(anyhow!("Lambda must be between 0 and 1, got {:?}", lambda));
325 }
326
327 for (i, &p) in p_values.iter().enumerate() {
329 if p < zero || p > one {
330 return Err(anyhow!("Invalid p-value at index {}: {:?}", i, p));
331 }
332 }
333
334 let w = p_values.iter().filter(|&&p| p > lambda).count();
336 let w_t = T::from(w).unwrap_or(zero);
337 let n_t = T::from(n).unwrap_or(one);
338
339 let pi0 = w_t / (n_t * (one - lambda));
340 let pi0 = num_traits::Float::min(pi0, one); let bh_adjusted = benjamini_hochberg_correction(p_values)?;
344
345 let q_values = bh_adjusted
347 .iter()
348 .map(|&p| num_traits::Float::min(p * pi0, one))
349 .collect();
350
351 Ok(q_values)
352}
353
354pub fn adaptive_storey_qvalues<T>(p_values: &[T]) -> Result<Vec<T>>
371where
372 T: FloatOps,
373{
374 let n = p_values.len();
375
376 if n == 0 {
377 return Err(anyhow!("Empty p-value array"));
378 }
379
380 let zero = T::zero();
381 let one = T::one();
382
383 for (i, &p) in p_values.iter().enumerate() {
385 if p < zero || p > one {
386 return Err(anyhow!("Invalid p-value at index {}: {:?}", i, p));
387 }
388 }
389
390 let lambda_values = [0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9];
392 let lambda_grid: Vec<T> = lambda_values
393 .iter()
394 .filter_map(|&val| T::from(val))
395 .collect();
396
397 let mut pi0_estimates = Vec::with_capacity(lambda_grid.len());
399 for &lambda in &lambda_grid {
400 let w = p_values.iter().filter(|&&p| p > lambda).count();
401 let w_t = T::from(w as f64).unwrap_or(zero);
402 let n_t = T::from(n as f64).unwrap_or(one);
403
404 let pi0 = w_t / (n_t * (one - lambda));
405 pi0_estimates.push(num_traits::Float::min(pi0, one));
406 }
407
408 let pi0_sum: T = pi0_estimates.iter().copied().sum();
411 let estimates_len = T::from(pi0_estimates.len() as f64).unwrap_or(one);
412 let pi0_mean = pi0_sum / estimates_len;
413
414 let pi0 = if pi0_estimates.is_empty() {
415 one
416 } else {
417 num_traits::Float::min(pi0_mean, one)
418 };
419
420 let bh_adjusted = benjamini_hochberg_correction(p_values)?;
422
423 let q_values = bh_adjusted
425 .iter()
426 .map(|&p| num_traits::Float::min((p * pi0), one))
427 .collect();
428
429 Ok(q_values)
430}
431
432#[cfg(test)]
433mod tests {
434 use super::*;
435
436 fn assert_vec_relative_eq(a: &[f64], b: &[f64], epsilon: f64) {
437 assert_eq!(a.len(), b.len(), "Vectors have different lengths");
438 for (i, (x, y)) in a.iter().zip(b.iter()).enumerate() {
439 if (x - y).abs() > epsilon {
440 panic!("Vectors differ at index {}: {} != {}", i, x, y);
441 }
442 }
443 }
444
445 #[test]
446 fn test_bonferroni() {
447 let p_values = vec![0.01, 0.02, 0.03, 0.1, 0.2];
448 let expected = vec![0.05, 0.1, 0.15, 0.5, 1.0];
449 let adjusted = bonferroni_correction(&p_values).unwrap();
450 assert_vec_relative_eq(&adjusted, &expected, 1e-10);
451 }
452
453 use approx::assert_relative_eq;
454
455 #[test]
456 fn test_benjamini_hochberg_empty_input() {
457 let result = benjamini_hochberg_correction::<f64>(&[]);
459 assert!(result.is_err());
460 assert_eq!(result.unwrap_err().to_string(), "Empty p-value array");
461 }
462
463 #[test]
464 fn test_benjamini_hochberg_invalid_pvalues() {
465 let result = benjamini_hochberg_correction(&[0.01, -0.5, 0.03]);
467 assert!(result.is_err());
468 assert!(
469 result
470 .unwrap_err()
471 .to_string()
472 .contains("Invalid p-value at index 1")
473 );
474
475 let result = benjamini_hochberg_correction(&[0.01, 1.5, 0.03]);
477 assert!(result.is_err());
478 assert!(
479 result
480 .unwrap_err()
481 .to_string()
482 .contains("Invalid p-value at index 1")
483 );
484 }
485
486 #[test]
487 fn test_benjamini_hochberg_identical_pvalues() {
488 let p_values = vec![0.05, 0.05, 0.05];
490 let expected = vec![0.05, 0.05, 0.05];
491 let adjusted = benjamini_hochberg_correction(&p_values).unwrap();
492
493 for (a, e) in adjusted.iter().zip(expected.iter()) {
494 assert_relative_eq!(*a, *e, epsilon = 1e-10);
495 }
496 }
497
498 #[test]
499 fn test_benjamini_hochberg_ordered_pvalues() {
500 let p_values = vec![0.01, 0.02, 0.03, 0.04, 0.05];
502 let expected = vec![0.05, 0.05, 0.05, 0.05, 0.05];
503 let adjusted = benjamini_hochberg_correction(&p_values).unwrap();
504
505 for (i, (&a, &e)) in adjusted.iter().zip(expected.iter()).enumerate() {
506 assert_relative_eq!(a, e, epsilon = 1e-10, max_relative = 1e-10);
507 if num_traits::Float::abs(a - e) > 1e-10 {
509 panic!("mismatch at index {}: expected {}, got {}", i, e, a);
510 }
511 }
512 }
513
514 #[test]
515 fn test_benjamini_hochberg_unordered_pvalues() {
516 let p_values = vec![0.05, 0.01, 0.1, 0.04, 0.02];
518 let expected = vec![0.0625, 0.05, 0.1, 0.0625, 0.05];
519 let adjusted = benjamini_hochberg_correction(&p_values).unwrap();
520
521 for (i, (&a, &e)) in adjusted.iter().zip(expected.iter()).enumerate() {
522 if num_traits::Float::abs(a - e) > 1e-3 {
525 panic!(
526 "mismatch at index {}: expected {}, got {}, whole: {:?}",
527 i, e, a, adjusted
528 );
529 }
530 }
531 }
532
533 #[test]
534 fn test_benjamini_hochberg_edge_cases() {
535 let p_values = vec![1e-10, 1e-9, 1e-8];
537 let adjusted = benjamini_hochberg_correction(&p_values).unwrap();
538 assert!(adjusted.iter().all(|&p| p > 0.0 && p < 0.001));
540
541 let p_values = vec![0.1, 0.2, 1.0];
543 let adjusted = benjamini_hochberg_correction(&p_values).unwrap();
544 assert_relative_eq!(adjusted[2], 1.0, epsilon = 1e-10);
546 }
547
548 #[test]
549 fn test_benjamini_hochberg_real_example() {
550 let pvalues = vec![0.1, 0.2, 0.3, 0.4, 0.1];
552 let expected = [0.25, 0.3333333333333333, 0.375, 0.4, 0.25];
553 let adjusted = benjamini_hochberg_correction(&pvalues).unwrap();
554
555 for (i, (&a, &e)) in adjusted.iter().zip(expected.iter()).enumerate() {
556 assert_relative_eq!(a, e, epsilon = 1e-3, max_relative = 1e-3);
557 if num_traits::Float::abs(a - e) > 1e-3 {
559 panic!("mismatch at index {}: expected {}, got {}", i, e, a);
560 }
561 }
562 }
563
564 #[test]
565 fn test_benjamini_hochberg_single_pvalue() {
566 let p_values = vec![0.025];
568 let adjusted = benjamini_hochberg_correction(&p_values).unwrap();
569 assert_relative_eq!(adjusted[0], 0.025, epsilon = 1e-10);
570 }
571 #[test]
572 fn test_holm_bonferroni() {
573 let p_values = vec![0.01, 0.02, 0.03];
574 let expected = vec![0.03, 0.04, 0.03];
575 let adjusted = holm_bonferroni_correction(&p_values).unwrap();
576 assert_vec_relative_eq(&adjusted, &expected, 1e-10);
577 }
578
579 #[test]
580 fn test_storey_qvalues() {
581 let p_values = vec![0.01, 0.02, 0.03, 0.6, 0.7];
582 let qvalues = storey_qvalues(&p_values, 0.5).unwrap();
583 for &q in &qvalues {
585 assert!((0.0..=1.0).contains(&q));
586 }
587 assert!(qvalues[0] <= qvalues[2]);
589 assert!(qvalues[3] <= qvalues[4]);
590 }
591
592 #[test]
593 fn test_invalid_inputs() {
594 assert!(bonferroni_correction::<f32>(&[]).is_err());
596 assert!(benjamini_hochberg_correction::<f32>(&[]).is_err());
597 assert!(holm_bonferroni_correction::<f32>(&[]).is_err());
598
599 assert!(storey_qvalues(&[0.5], -0.1).is_err());
601 assert!(storey_qvalues(&[0.5], 1.0).is_err());
602
603 let invalid_p = vec![-0.1, 0.5, 1.1];
605 assert!(bonferroni_correction(&invalid_p).is_err());
606 assert!(benjamini_hochberg_correction(&invalid_p).is_err());
607 }
608}