pub const PROB_FLOOR: f32 = 1e-12;
pub const LOG_PROB_FLOOR: f64 = -27.631_021_115_928_547;
#[derive(Clone, Copy, Debug, Default)]
pub struct CellAgreement {
pub spearman: f32,
pub pearson_log1p: f32,
}
#[must_use]
pub fn rate_to_counts(observed: &[f32], rate: &[f32]) -> Vec<f32> {
let count: f32 = observed.iter().sum();
let z: f32 = rate.iter().sum::<f32>().max(1e-12);
let scale = count / z;
rate.iter().map(|r| r * scale).collect()
}
#[must_use]
pub fn agreement_from_rate(observed: &[f32], rate: &[f32]) -> CellAgreement {
let predicted_counts = rate_to_counts(observed, rate);
CellAgreement {
spearman: spearman(observed, &predicted_counts),
pearson_log1p: pearson_log1p(observed, &predicted_counts),
}
}
#[must_use]
pub fn agreement_from_log_rate(observed: &[f32], log_rate: &[f32]) -> CellAgreement {
let max_logit = log_rate.iter().copied().fold(f32::NEG_INFINITY, f32::max);
let rate: Vec<f32> = log_rate.iter().map(|l| (l - max_logit).exp()).collect();
agreement_from_rate(observed, &rate)
}
#[must_use]
pub fn pearson_log1p(observed: &[f32], predicted: &[f32]) -> f32 {
if observed.len() < 2 || observed.len() != predicted.len() {
return f32::NAN;
}
let log1p =
|v: &[f32]| -> Vec<f64> { v.iter().map(|x| f64::from(x.max(0.0)).ln_1p()).collect() };
pearson(&log1p(observed), &log1p(predicted))
}
#[must_use]
pub fn spearman(observed: &[f32], predicted: &[f32]) -> f32 {
if observed.len() < 2 || observed.len() != predicted.len() {
return f32::NAN;
}
pearson(&average_ranks(observed), &average_ranks(predicted))
}
fn average_ranks(values: &[f32]) -> Vec<f64> {
let mut order: Vec<usize> = (0..values.len()).collect();
order.sort_by(|&i, &j| {
values[i]
.partial_cmp(&values[j])
.unwrap_or(std::cmp::Ordering::Equal)
});
let mut ranks = vec![0f64; values.len()];
let mut run_start = 0;
while run_start < order.len() {
let mut run_end = run_start + 1;
while run_end < order.len() && values[order[run_end]] == values[order[run_start]] {
run_end += 1;
}
let shared = (run_start + 1 + run_end) as f64 / 2.0;
for &position in &order[run_start..run_end] {
ranks[position] = shared;
}
run_start = run_end;
}
ranks
}
fn pearson(a: &[f64], b: &[f64]) -> f32 {
debug_assert_eq!(a.len(), b.len(), "pearson: length mismatch");
let n = a.len() as f64;
let (ma, mb) = (a.iter().sum::<f64>() / n, b.iter().sum::<f64>() / n);
let mut sab = 0.0;
let mut saa = 0.0;
let mut sbb = 0.0;
for (&x, &y) in a.iter().zip(b) {
let (dx, dy) = (x - ma, y - mb);
sab += dx * dy;
saa += dx * dx;
sbb += dy * dy;
}
let denom = (saa * sbb).sqrt();
if denom > 0.0 {
(sab / denom) as f32
} else {
f32::NAN
}
}
#[cfg(test)]
mod tests;