use super::*;
#[derive(Clone, Debug)]
pub enum RowFilter {
Mask(Vec<bool>),
Names(regex::Regex),
Broad { share: f64, groups: Vec<Box<str>> },
}
#[derive(Clone, Debug)]
pub struct ColumnScales {
pub factor: Vec<f32>,
pub ignored_rows: Vec<bool>,
}
impl SparseIoVec {
pub fn depth_scales(
&self,
ignore: &[RowFilter],
block_size: Option<usize>,
) -> anyhow::Result<ColumnScales> {
anyhow::ensure!(
self.column_scales.is_none(),
"depth_scales on a scaled vector: read the depths before set_column_scales"
);
let (n_rows, n_cols) = (self.num_rows(), self.num_columns());
let blocks = generate_minibatch_intervals(n_cols, n_rows, block_size);
let mut ignored = vec![false; n_rows];
for filter in ignore {
match filter {
RowFilter::Mask(mask) => {
anyhow::ensure!(
mask.len() == n_rows,
"row mask has {} flags for {n_rows} rows",
mask.len()
);
ignored.iter_mut().zip(mask).for_each(|(i, &o)| *i |= o);
}
RowFilter::Names(re) => {
let names = self.row_names()?;
ignored
.iter_mut()
.zip(&names)
.for_each(|(i, n)| *i |= re.is_match(n));
}
RowFilter::Broad { share, groups } => {
let broad = self.broad_rows(*share, groups, &blocks)?;
ignored.iter_mut().zip(broad).for_each(|(i, o)| *i |= o);
}
}
}
let kept: Vec<usize> = (0..n_rows).filter(|&r| !ignored[r]).collect();
let (_, depth, _, _) =
crate::qc::collect_column_stat_across_vec(self, Some(&kept), block_size)?.to_f32_vecs();
Ok(ColumnScales {
factor: factors(&depth),
ignored_rows: ignored,
})
}
fn broad_rows(
&self,
share: f64,
groups: &[Box<str>],
blocks: &[(usize, usize)],
) -> anyhow::Result<Vec<bool>> {
let (n_rows, n_cols) = (self.num_rows(), self.num_columns());
anyhow::ensure!(
share.is_finite() && (0.0..1.0).contains(&share),
"broad share {share} is not in [0, 1)"
);
anyhow::ensure!(
groups.len() == n_cols,
"{} group labels for {n_cols} columns",
groups.len()
);
let mut index: HashMap<&str, usize> = HashMap::default();
let g: Vec<usize> = groups
.iter()
.map(|l| {
let next = index.len();
*index.entry(l).or_insert(next)
})
.collect();
let n_groups = index.len();
let mut size = vec![0usize; n_groups];
g.iter().for_each(|&k| size[k] += 1);
let zero = || vec![0u32; n_groups * n_rows];
let detected = blocks
.par_iter()
.try_fold(zero, |mut d, &(lb, ub)| -> anyhow::Result<Vec<u32>> {
let x = self.read_columns_csc(lb..ub)?;
for (k, col) in x.col_iter().enumerate() {
let dk = &mut d[g[lb + k] * n_rows..][..n_rows];
for (&r, &v) in col.row_indices().iter().zip(col.values()) {
if v > 0.0 {
dk[r] += 1;
}
}
}
Ok(d)
})
.try_reduce(zero, |mut a, b| {
a.iter_mut().zip(b).for_each(|(a, b)| *a += b);
Ok(a)
})?;
Ok((0..n_rows)
.map(|r| {
(0..n_groups).all(|k| detected[k * n_rows + r] as f64 > share * size[k] as f64)
})
.collect())
}
pub fn set_column_scales(&mut self, factor: Option<Vec<f32>>) -> anyhow::Result<()> {
if let Some(f) = &factor {
anyhow::ensure!(
f.len() == self.num_columns(),
"{} column scales for {} columns",
f.len(),
self.num_columns()
);
if let Some((c, v)) = f
.iter()
.enumerate()
.find(|(_, v)| !(v.is_finite() && **v > 0.0))
{
anyhow::bail!("column {c} has scale {v}; scales must be positive and finite");
}
}
self.column_scales = factor;
Ok(())
}
pub fn column_scales(&self) -> Option<&[f32]> {
self.column_scales.as_deref()
}
}
fn factors(depth: &[f32]) -> Vec<f32> {
let positive: Vec<f32> = depth.iter().copied().filter(|&d| d > 0.0).collect();
if positive.is_empty() {
return vec![1.0; depth.len()];
}
let median = median(&positive);
depth
.iter()
.map(|&d| if d > 0.0 { d / median } else { 1.0 })
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sparse_io::{create_sparse_from_triplets, SparseIoBackend};
#[test]
fn a_matched_column_read_divides_by_its_factor() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("a.zarr");
let t = [(0, 0, 4.0), (1, 1, 6.0), (0, 2, 9.0)];
let mut b = create_sparse_from_triplets(
&t,
(2, 3, t.len()),
Some(path.to_str().unwrap()),
Some(&SparseIoBackend::Zarr),
)
.unwrap();
b.register_row_names_vec(&["R0".into(), "R1".into()]);
b.register_column_names_vec(&["c0".into(), "c1".into(), "c2".into()]);
let mut data = SparseIoVec::new();
data.push(Arc::from(b), None).unwrap();
data.set_column_scales(Some(vec![2.0, 3.0, 9.0])).unwrap();
let matched = data
.read_matched(vec![0, 0], vec![2, 1], vec![0.0, 0.0])
.unwrap();
assert_eq!(matched.shape, (2, 2));
let mut out = matched.triplets;
out.sort_by_key(|&(r, c, _)| (c, r));
assert_eq!(out, [(0, 0, 1.0), (1, 1, 2.0)]);
}
}