xcell-rust 0.1.0

Pure-Rust port of xCell (Aran et al. 2017) cell-type enrichment — ssGSEA, spillover-corrected — validated for numeric parity against the R xCell package. Built on gsva-rust.
Documentation
//! Stage 4 of xCell: `microenvironmentScores`.
//!
//! Appends three aggregate rows — `ImmuneScore`, `StromaScore`, and
//! `MicroenvironmentScore` — to the spillover-adjusted scores.

use gsva::EnrichmentResult;

/// Immune cell types summed into `ImmuneScore` (then divided by 1.5), in xCell's
/// listing order.
const IMMUNE: [&str; 10] = [
    "B-cells",
    "CD4+ T-cells",
    "CD8+ T-cells",
    "DC",
    "Eosinophils",
    "Macrophages",
    "Monocytes",
    "Mast cells",
    "Neutrophils",
    "NK cells",
];

/// Stromal cell types summed into `StromaScore` (then divided by 2).
const STROMA: [&str; 3] = ["Adipocytes", "Endothelial cells", "Fibroblasts"];

/// Mirrors xCell 1.1.0 `microenvironmentScores`: `ImmuneScore = Σ immune / 1.5`,
/// `StromaScore = Σ stroma / 2`, `MicroenvironmentScore = Immune + Stroma`,
/// appended as three extra rows.
pub fn microenvironment_scores(adjusted: &EnrichmentResult) -> EnrichmentResult {
    let nsamp = adjusted.samples.len();
    let row_of = |name: &str| adjusted.gene_sets.iter().position(|g| g == name);

    let mut immune = vec![0.0f64; nsamp];
    for &ct in &IMMUNE {
        if let Some(i) = row_of(ct) {
            let base = i * nsamp;
            for (j, v) in immune.iter_mut().enumerate() {
                *v += adjusted.scores[base + j];
            }
        }
    }
    for v in &mut immune {
        *v /= 1.5;
    }

    let mut stroma = vec![0.0f64; nsamp];
    for &ct in &STROMA {
        if let Some(i) = row_of(ct) {
            let base = i * nsamp;
            for (j, v) in stroma.iter_mut().enumerate() {
                *v += adjusted.scores[base + j];
            }
        }
    }
    for v in &mut stroma {
        *v /= 2.0;
    }

    let mut gene_sets = adjusted.gene_sets.clone();
    let mut scores = adjusted.scores.clone();
    gene_sets.push("ImmuneScore".into());
    scores.extend_from_slice(&immune);
    gene_sets.push("StromaScore".into());
    scores.extend_from_slice(&stroma);
    gene_sets.push("MicroenvironmentScore".into());
    for j in 0..nsamp {
        scores.push(immune[j] + stroma[j]);
    }

    EnrichmentResult {
        gene_sets,
        samples: adjusted.samples.clone(),
        scores,
    }
}