bixverse_methods/methods/
coremo.rs1use faer::MatRef;
5use rayon::prelude::*;
6use rustc_hash::FxHashMap;
7use std::collections::VecDeque;
8
9use crate::prelude::*;
10
11pub fn intersection_size_sorted(a: &[usize], b: &[usize]) -> usize {
26 let mut count = 0;
27 let mut i = 0;
28 let mut j = 0;
29
30 while i < a.len() && j < b.len() {
31 match a[i].cmp(&b[j]) {
32 std::cmp::Ordering::Equal => {
33 count += 1;
34 i += 1;
35 j += 1;
36 }
37 std::cmp::Ordering::Less => i += 1,
38 std::cmp::Ordering::Greater => j += 1,
39 }
40 }
41
42 count
43}
44
45pub fn cluster_stability<T>(cluster_matrix: &MatRef<i32>) -> Vec<(T, T)>
57where
58 T: BixverseFloat,
59{
60 let n_features = cluster_matrix.nrows();
61 let n_iter = cluster_matrix.ncols();
62
63 let bootstrap_cluster_maps: Vec<FxHashMap<i32, Vec<usize>>> = (0..n_iter)
65 .into_par_iter()
66 .map(|boot_idx| {
67 let mut clusters_map: FxHashMap<i32, Vec<usize>> = FxHashMap::default();
68 for feature_idx in 0..n_features {
69 let cluster_id = cluster_matrix[(feature_idx, boot_idx)];
70 clusters_map
71 .entry(cluster_id)
72 .or_default()
73 .push(feature_idx);
74 }
75 clusters_map
76 })
77 .collect();
78
79 (0..n_features)
81 .into_par_iter()
82 .map(|feature_idx| {
83 let n_pairs = (n_iter * (n_iter - 1)) / 2;
84 let mut jaccard_scores = Vec::with_capacity(n_pairs);
85
86 for i in 0..(n_iter - 1) {
87 for j in (i + 1)..n_iter {
88 let cluster_i = cluster_matrix[(feature_idx, i)];
89 let cluster_j = cluster_matrix[(feature_idx, j)];
90
91 let members_i = bootstrap_cluster_maps[i]
92 .get(&cluster_i)
93 .map(|v| v.as_slice())
94 .unwrap_or(&[]);
95
96 let members_j = bootstrap_cluster_maps[j]
97 .get(&cluster_j)
98 .map(|v| v.as_slice())
99 .unwrap_or(&[]);
100
101 let intersection_size = intersection_size_sorted(members_i, members_j);
102 let union_size = members_i.len() + members_j.len() - intersection_size;
103
104 let jaccard = if union_size == 0 {
105 0.0
106 } else {
107 intersection_size as f64 / union_size as f64
108 };
109 jaccard_scores.push(jaccard);
110 }
111 }
112
113 let mean_jaccard = jaccard_scores.iter().sum::<f64>() / jaccard_scores.len() as f64;
114 let variance = jaccard_scores
115 .iter()
116 .map(|x| (x - mean_jaccard).powi(2))
117 .sum::<f64>()
118 / jaccard_scores.len() as f64;
119 let std_jaccard = variance.sqrt();
120
121 (
122 T::from_f64(mean_jaccard).unwrap(),
123 T::from_f64(std_jaccard).unwrap(),
124 )
125 })
126 .collect()
127}
128
129pub fn split_cor_mat_by_sign<T>(cor_mat: &MatRef<T>) -> Vec<i32>
144where
145 T: BixverseFloat,
146{
147 let n = cor_mat.ncols();
150 let total_elem = n * (n - 1) / 2;
151 let mut off_diag = Vec::with_capacity(total_elem);
152
153 for i in 0..n {
154 for j in (i + 1)..n {
155 off_diag.push(*cor_mat.get(i, j));
156 }
157 }
158
159 let all_pos = off_diag.iter().all(|x| *x >= T::zero());
160 let all_neg = off_diag.iter().all(|x| *x <= T::zero());
161
162 if all_pos {
163 return vec![1; n];
164 }
165 if all_neg {
166 return vec![-1; n];
167 }
168
169 let mut visited = vec![false; n];
170 let mut groups = Vec::new();
171
172 for i in 0..n {
173 if !visited[i] {
174 let mut component = Vec::new();
175 let mut queue = VecDeque::new();
176 queue.push_back(i);
177 visited[i] = true;
178
179 while let Some(node) = queue.pop_front() {
180 component.push(node);
181
182 for j in 0..n {
183 if !visited[j] && *cor_mat.get(node, j) > T::zero() {
184 visited[j] = true;
185 queue.push_back(j);
186 }
187 }
188 }
189 groups.push(component);
190 }
191 }
192
193 let mut result = vec![-1; n];
194 for group in groups {
195 let has_positive_with_first =
197 group.contains(&0) || group.iter().any(|&idx| *cor_mat.get(0, idx) > T::zero());
198
199 let label = if has_positive_with_first { 1 } else { -1 };
200 for &idx in &group {
201 result[idx] = label;
202 }
203 }
204
205 result
206}