Skip to main content

xcell/
transform.rs

1//! Stage 2 of xCell: `transformScores`.
2//!
3//! Calibrates each cell type's raw enrichment with a per-cell-type power curve.
4
5use gsva::EnrichmentResult;
6
7use crate::data::SpillModel;
8
9/// Mirrors xCell 1.1.0 `transformScores`. For each cell type present in `fv`:
10/// `t = max(0, (raw - rowMin) / 5000)`, then `t^V2 / (V3 * 2)`. With
11/// `scale = false`, `V3` is forced to 1 (so the divisor is exactly 2).
12pub fn transform_scores(
13    raw: &EnrichmentResult,
14    spill: &SpillModel,
15    scale: bool,
16) -> EnrichmentResult {
17    let nsamp = raw.samples.len();
18    let mut out_types = Vec::new();
19    let mut scores = Vec::new();
20
21    for (i, ct) in raw.gene_sets.iter().enumerate() {
22        // rows <- rownames(scores)[rownames(scores) %in% rownames(fit.vals)]
23        let Some(v2) = spill.v2(ct) else { continue };
24        let v3 = if scale {
25            spill.v3(ct).expect("fv has V2 but not V3")
26        } else {
27            1.0
28        };
29        let row = &raw.scores[i * nsamp..(i + 1) * nsamp];
30        let mn = row.iter().copied().fold(f64::INFINITY, f64::min);
31        let denom = v3 * 2.0;
32
33        out_types.push(ct.clone());
34        for &x in row {
35            let mut t = (x - mn) / 5000.0;
36            if t < 0.0 {
37                t = 0.0;
38            }
39            scores.push(t.powf(v2) / denom);
40        }
41    }
42
43    EnrichmentResult {
44        gene_sets: out_types,
45        samples: raw.samples.clone(),
46        scores,
47    }
48}