use crate::matrix::ExprMatrix;
use crate::prep;
use crate::result::EnrichmentResult;
#[derive(Clone, Debug)]
pub struct ZscoreParams {
pub min_size: usize,
pub max_size: usize,
}
impl Default for ZscoreParams {
fn default() -> Self {
ZscoreParams {
min_size: 1,
max_size: usize::MAX,
}
}
}
pub fn zscore(
expr: &ExprMatrix,
gene_sets: &crate::geneset::GeneSets,
params: &ZscoreParams,
) -> EnrichmentResult {
let filtered = prep::filter_constant_rows(expr);
let (names, indices) =
prep::map_and_filter_sets(&filtered, gene_sets, params.min_size, params.max_size);
let z = prep::scale_rows(&filtered);
let n = filtered.ncol();
let mut scores = vec![0.0f64; names.len() * n];
crate::par::fill_chunks_mut(&mut scores, n, |s, out| {
let idx = &indices[s];
let denom = (idx.len() as f64).sqrt();
for (j, slot) in out.iter_mut().enumerate() {
let mut sum = 0.0f64;
let mut comp = 0.0f64;
for &g in idx {
let v = z[g * n + j];
let t = sum + v;
if sum.abs() >= v.abs() {
comp += (sum - t) + v;
} else {
comp += (v - t) + sum;
}
sum = t;
}
*slot = (sum + comp) / denom;
}
});
EnrichmentResult {
gene_sets: names,
samples: filtered.col_names().to_vec(),
scores,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::geneset::{GeneSet, GeneSets};
#[test]
fn small_zscore_shapes() {
let expr = ExprMatrix::new(
vec!["G1".into(), "G2".into(), "G3".into(), "G4".into()],
vec!["S1".into(), "S2".into(), "S3".into()],
vec![
1.0, 2.0, 3.0, 4.0, 6.0, 8.0, 3.0, 2.0, 1.0, 10.0, 5.0, 1.0, ],
);
let gs = GeneSets::new(vec![GeneSet::new("SET", vec!["G1".into(), "G2".into()])]);
let res = zscore(&expr, &gs, &ZscoreParams::default());
assert_eq!(res.gene_sets, ["SET"]);
assert_eq!(res.samples, ["S1", "S2", "S3"]);
assert_eq!(res.scores.len(), 3);
assert!(res.score(0, 0) < 0.0);
assert!(res.score(0, 2) > 0.0);
assert!(res.score(0, 1).abs() < 1e-12);
}
}