use crate::error::StatError;
use crate::transform::rank::{rank, Ties};
pub fn quantile_normalize(matrix: &[&[f64]]) -> Result<Vec<Vec<f64>>, StatError> {
if matrix.is_empty() {
return Err(StatError::EmptyInput);
}
let nrows = matrix[0].len();
for (_j, col) in matrix.iter().enumerate().skip(1) {
if col.len() != nrows {
return Err(StatError::MismatchedLengths {
a: nrows,
b: col.len(),
});
}
}
let ncols = matrix.len();
let mut finite_sorted: Vec<Vec<f64>> = Vec::with_capacity(ncols);
for col in matrix.iter() {
let mut pairs: Vec<(usize, f64)> = col
.iter()
.enumerate()
.filter(|(_, x)| x.is_finite())
.map(|(i, &x)| (i, x))
.collect();
pairs.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
finite_sorted.push(pairs.iter().map(|&(_, v)| v).collect());
}
let ref_len = finite_sorted.iter().map(|s| s.len()).max().unwrap_or(0);
let mut reference: Vec<f64> = vec![0.0; ref_len];
let mut ref_counts: Vec<usize> = vec![0; ref_len];
for sorted in &finite_sorted {
for (k, &val) in sorted.iter().enumerate() {
reference[k] += val;
ref_counts[k] += 1;
}
}
for (r, c) in reference.iter_mut().zip(ref_counts.iter()) {
if *c > 0 {
*r /= *c as f64;
}
}
let mut output: Vec<Vec<f64>> = matrix
.iter()
.map(|col| col.to_vec())
.collect();
for (j, col) in matrix.iter().enumerate() {
let finite_vals: Vec<f64> = col.iter().copied().filter(|x| x.is_finite()).collect();
if finite_vals.is_empty() {
continue; }
let n_finite = finite_vals.len();
let avg_ranks = rank(&finite_vals, Ties::Average)
.expect("rank cannot fail on a non-empty finite slice");
let finite_indices: Vec<usize> = col
.iter()
.enumerate()
.filter(|(_, x)| x.is_finite())
.map(|(i, _)| i)
.collect();
for (fi, &orig_idx) in finite_indices.iter().enumerate() {
let r = avg_ranks[fi]; let lo_idx = (r.floor() as usize).saturating_sub(1);
let hi_idx = (r.ceil() as usize).saturating_sub(1).min(n_finite - 1);
let frac = r - r.floor();
let val = if lo_idx == hi_idx {
reference[lo_idx]
} else {
reference[lo_idx] * (1.0 - frac) + reference[hi_idx] * frac
};
output[j][orig_idx] = val;
}
}
Ok(output)
}