bixverse_sc_data/
plotting.rs1use indexmap::IndexSet;
6use rayon::prelude::*;
7
8use crate::prelude::*;
9use bixverse_core::utils::simd::*;
10
11pub struct GroupedGeneStats {
17 pub gene_indices: Vec<usize>,
19 pub group_labels: Vec<String>,
21 pub mean_expression: Vec<f32>,
24 pub pct_expressed: Vec<f32>,
26}
27
28fn scale_and_clip(data: &mut [f32], clip: Option<f32>) {
35 let n = data.len() as f32;
36 let mean = sum_simd_f32(data) / n;
37 let var = sum_squared_dev_simd_f32(data, mean) / n;
38 let sd = var.sqrt();
39 if sd > 1e-8 {
40 for x in data.iter_mut() {
41 *x = (*x - mean) / sd;
42 }
43 }
44 if let Some(c) = clip {
45 for x in data.iter_mut() {
46 *x = x.clamp(-c, c);
47 }
48 }
49}
50
51pub fn extract_raw_counts<S: SingleCellReading>(
68 reader: &S,
69 cell_indices: &[usize],
70 gene_index: usize,
71) -> Result<Vec<u32>, ScDataErrors> {
72 let cell_set: IndexSet<u32> = cell_indices.iter().map(|&x| x as u32).collect();
73
74 let mut chunk = reader
75 .read_gene_parallel(&[gene_index])?
76 .into_iter()
77 .next()
78 .unwrap();
79 chunk.filter_selected_cells(&cell_set);
80
81 let n = cell_indices.len();
82 let mut dense = vec![0u32; n];
83 for (i, &cell_idx) in chunk.indices.iter().enumerate() {
84 dense[cell_idx as usize] = chunk.data_raw.get(i);
85 }
86 Ok(dense)
87}
88
89pub fn extract_norm_counts<S: SingleCellReading>(
107 reader: &S,
108 cell_indices: &[usize],
109 gene_index: usize,
110 scale: bool,
111 clip: Option<f32>,
112) -> Result<Vec<f32>, ScDataErrors> {
113 let cell_set: IndexSet<u32> = cell_indices.iter().map(|&x| x as u32).collect();
114
115 let mut chunk = reader
116 .read_gene_parallel(&[gene_index])?
117 .into_iter()
118 .next()
119 .unwrap();
120 chunk.filter_selected_cells(&cell_set);
121
122 let n = cell_indices.len();
123 let mut dense = vec![0.0f32; n];
124 for (i, &cell_idx) in chunk.indices.iter().enumerate() {
125 dense[cell_idx as usize] = chunk.data_norm[i].to_f32();
126 }
127
128 if scale {
129 scale_and_clip(&mut dense, clip);
130 }
131
132 Ok(dense)
133}
134
135pub fn extract_norm_counts_multi<S: SingleCellReading>(
154 reader: &S,
155 cell_indices: &[usize],
156 gene_indices: &[usize],
157 scale: bool,
158 clip: Option<f32>,
159) -> Result<Vec<Vec<f32>>, ScDataErrors> {
160 let cell_set: IndexSet<u32> = cell_indices.iter().map(|&x| x as u32).collect();
161 let n = cell_indices.len();
162
163 let mut chunks = reader.read_gene_parallel(gene_indices)?;
164
165 chunks.par_iter_mut().for_each(|chunk| {
166 chunk.filter_selected_cells(&cell_set);
167 });
168
169 let res = chunks
170 .par_iter()
171 .map(|chunk| {
172 let mut dense = vec![0.0f32; n];
173 for (i, &cell_idx) in chunk.indices.iter().enumerate() {
174 dense[cell_idx as usize] = chunk.data_norm[i].to_f32();
175 }
176 if scale {
177 scale_and_clip(&mut dense, clip);
178 }
179 dense
180 })
181 .collect();
182
183 Ok(res)
184}
185
186pub fn extract_grouped_gene_stats<S: SingleCellReading>(
211 reader: &S,
212 cell_indices: &[usize],
213 gene_indices: &[usize],
214 group_ids: &[usize],
215 group_levels: &[String],
216) -> Result<GroupedGeneStats, ScDataErrors> {
217 assert_eq!(
218 cell_indices.len(),
219 group_ids.len(),
220 "cell_indices and group_ids must have equal length"
221 );
222
223 let cell_set: IndexSet<u32> = cell_indices.iter().map(|&x| x as u32).collect();
224 let n_groups = group_levels.len();
225
226 let mut group_sizes = vec![0usize; n_groups];
227 for &g in group_ids {
228 debug_assert!(
229 g < n_groups,
230 "group_id {} out of bounds for {} levels",
231 g,
232 n_groups
233 );
234 group_sizes[g] += 1;
235 }
236
237 let mut chunks = reader.read_gene_parallel(gene_indices)?;
238
239 chunks.par_iter_mut().for_each(|chunk| {
240 chunk.filter_selected_cells(&cell_set);
241 });
242
243 let n_genes = gene_indices.len();
244 let mut mean_expression = vec![0.0f32; n_genes * n_groups];
245 let mut pct_expressed = vec![0.0f32; n_genes * n_groups];
246
247 for (g, chunk) in chunks.iter().enumerate() {
248 let mut sums = vec![0.0f32; n_groups];
249 let mut counts = vec![0usize; n_groups];
250
251 for (i, &new_cell_idx) in chunk.indices.iter().enumerate() {
252 let grp = group_ids[new_cell_idx as usize];
253 sums[grp] += chunk.data_norm[i].to_f32();
254 counts[grp] += 1;
255 }
256
257 let offset = g * n_groups;
258 for grp in 0..n_groups {
259 if group_sizes[grp] > 0 {
260 let size = group_sizes[grp] as f32;
261 mean_expression[offset + grp] = sums[grp] / size;
262 pct_expressed[offset + grp] = counts[grp] as f32 / size;
263 }
264 }
265 }
266
267 Ok(GroupedGeneStats {
268 gene_indices: chunks.iter().map(|c| c.original_index).collect(),
269 group_labels: group_levels.to_vec(),
270 mean_expression,
271 pct_expressed,
272 })
273}