single_statistics/testing/effect/
mod.rs1use nalgebra_sparse::CsrMatrix;
2use single_utilities::traits::{FloatOps, FloatOpsTS};
3
4pub fn calculate_log2_fold_change<T>(
6 matrix: &CsrMatrix<T>,
7 col: usize,
8 group1_indices: &[usize], group2_indices: &[usize], pseudo_count: T, ) -> anyhow::Result<T>
12where
13 T: FloatOps,
14{
15 if group1_indices.is_empty() || group2_indices.is_empty() {
16 return Err(anyhow::anyhow!("Group indices cannot be empty"));
17 }
18
19 let mut sum1 = T::zero();
20 let mut _count1 = 0;
21 for &row in group1_indices {
22 if let Some(entry) = matrix.get_entry(row, col) {
23 let value = entry.into_value();
24 sum1 += value;
25 _count1 += 1;
26 }
27 }
28
29 let mut sum2 = T::zero();
30 let mut _count2 = 0;
31 for &row in group2_indices {
32 if let Some(entry) = matrix.get_entry(row, col) {
33 let value = entry.into_value();
34 sum2 += value;
35 _count2 += 1;
36 }
37 }
38 let go_f64 = T::from(group1_indices.len()).unwrap();
39 let gt_f64 = T::from(group2_indices.len()).unwrap();
40
41 let mean1 = sum1 / go_f64 + pseudo_count;
42 let mean2 = sum2 / gt_f64 + pseudo_count;
43
44 let log2_fc = (mean1 / mean2).log2();
45
46 Ok(log2_fc)
47}
48
49pub fn calculate_cohens_d<T>(
51 matrix: &CsrMatrix<T>,
52 row: usize,
53 group1_indices: &[usize],
54 group2_indices: &[usize],
55) -> anyhow::Result<T>
56where
57 T: FloatOpsTS,
58{
59 if group1_indices.len() < 2 || group2_indices.len() < 2 {
60 return Err(anyhow::anyhow!(
61 "Each group must have at least 2 samples for Cohen's d"
62 ));
63 }
64
65 let mut sum_g1 = T::zero();
67 let mut group1_values = Vec::with_capacity(group1_indices.len());
68 for &col in group1_indices {
69 if let Some(entry) = matrix.get_entry(row, col) {
70 let value = entry.into_value();
71 sum_g1 += value;
72 group1_values.push(value);
73 } else {
74 group1_values.push(T::zero());
75 }
76 }
77
78 let mut sum_g2 = T::zero();
80 let mut group2_values = Vec::with_capacity(group2_indices.len());
81 for &col in group2_indices {
82 if let Some(entry) = matrix.get_entry(row, col) {
83 let value = entry.into_value();
84 sum_g2 += value;
85 group2_values.push(value);
86 } else {
87 group2_values.push(T::zero());
88 }
89 }
90
91 let go_t = T::from(group1_indices.len()).unwrap();
92 let gt_t = T::from(group2_indices.len()).unwrap();
93
94 let mean1 = sum_g1 / go_t;
96 let mean2 = sum_g2 / gt_t;
97 let var1 = group1_values
99 .iter()
100 .map(|&x| num_traits::Float::powi(x - mean1, 2))
101 .sum::<T>()
102 / (go_t - T::one());
103
104 let var2 = group2_values
105 .iter()
106 .map(|&x| num_traits::Float::powi(x - mean2, 2))
107 .sum::<T>()
108 / (gt_t - T::one());
109
110 let pooled_sd = (((go_t - T::one()) * var1 + (gt_t - T::one()) * var2) / (go_t + gt_t - T::from(2.0).unwrap())).sqrt();
112
113 let d = (mean2 - mean1) / pooled_sd;
115
116 Ok(d)
117}
118
119pub fn calculate_hedges_g<T>(
121 matrix: &CsrMatrix<T>,
122 row: usize,
123 group1_indices: &[usize],
124 group2_indices: &[usize],
125) -> anyhow::Result<T>
126where
127 T: FloatOpsTS,
128{
129 let d = calculate_cohens_d(matrix, row, group1_indices, group2_indices)?;
131 let one = T::one();
132 let two = T::from(2.0).unwrap();
133 let three = T::from(3.0).unwrap();
134 let four = T::from(4.0).unwrap();
135 let n1 = T::from(group1_indices.len()).unwrap();
137 let n2 = T::from(group2_indices.len()).unwrap();
138 let n = n1 + n2;
139
140 let j = one - three / (four * (n - two) - one);
142
143 let g = j * d;
145
146 Ok(g)
147}
148
149