0cae3b4227
Compute per-parameter importance scores by measuring the absolute Spearman rank correlation between each parameter's values and the objective across completed trials. Scores are normalized to sum to 1.0 and returned sorted by descending importance.
94 lines
2.6 KiB
Rust
94 lines
2.6 KiB
Rust
//! Parameter importance via Spearman rank correlation.
|
|
|
|
/// Assign average ranks to a slice of `f64` values (handles ties).
|
|
#[allow(clippy::cast_precision_loss, clippy::float_cmp)]
|
|
pub(crate) fn rank(values: &[f64]) -> Vec<f64> {
|
|
let n = values.len();
|
|
let mut indexed: Vec<(usize, f64)> = values.iter().copied().enumerate().collect();
|
|
indexed.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(core::cmp::Ordering::Equal));
|
|
|
|
let mut ranks = vec![0.0; n];
|
|
let mut i = 0;
|
|
while i < n {
|
|
// Find the run of tied values.
|
|
let mut j = i + 1;
|
|
while j < n && indexed[j].1 == indexed[i].1 {
|
|
j += 1;
|
|
}
|
|
// Average rank for the tie group (1-based ranks).
|
|
let avg = (i + 1..=j).sum::<usize>() as f64 / (j - i) as f64;
|
|
for item in &indexed[i..j] {
|
|
ranks[item.0] = avg;
|
|
}
|
|
i = j;
|
|
}
|
|
ranks
|
|
}
|
|
|
|
/// Pearson correlation coefficient on two equal-length slices.
|
|
#[allow(clippy::cast_precision_loss)]
|
|
fn pearson(x: &[f64], y: &[f64]) -> f64 {
|
|
let n = x.len() as f64;
|
|
let mean_x = x.iter().sum::<f64>() / n;
|
|
let mean_y = y.iter().sum::<f64>() / n;
|
|
|
|
let mut cov = 0.0;
|
|
let mut var_x = 0.0;
|
|
let mut var_y = 0.0;
|
|
for (xi, yi) in x.iter().zip(y.iter()) {
|
|
let dx = xi - mean_x;
|
|
let dy = yi - mean_y;
|
|
cov += dx * dy;
|
|
var_x += dx * dx;
|
|
var_y += dy * dy;
|
|
}
|
|
|
|
let denom = (var_x * var_y).sqrt();
|
|
if denom == 0.0 { 0.0 } else { cov / denom }
|
|
}
|
|
|
|
/// Spearman rank correlation (Pearson on ranks).
|
|
pub(crate) fn spearman(x: &[f64], y: &[f64]) -> f64 {
|
|
pearson(&rank(x), &rank(y))
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn rank_no_ties() {
|
|
let ranks = rank(&[30.0, 10.0, 20.0]);
|
|
assert_eq!(ranks, vec![3.0, 1.0, 2.0]);
|
|
}
|
|
|
|
#[test]
|
|
fn rank_with_ties() {
|
|
let ranks = rank(&[10.0, 20.0, 20.0, 30.0]);
|
|
assert_eq!(ranks, vec![1.0, 2.5, 2.5, 4.0]);
|
|
}
|
|
|
|
#[test]
|
|
fn perfect_positive_correlation() {
|
|
let x = vec![1.0, 2.0, 3.0, 4.0, 5.0];
|
|
let y = vec![2.0, 4.0, 6.0, 8.0, 10.0];
|
|
let r = spearman(&x, &y);
|
|
assert!((r - 1.0).abs() < 1e-10);
|
|
}
|
|
|
|
#[test]
|
|
fn perfect_negative_correlation() {
|
|
let x = vec![1.0, 2.0, 3.0, 4.0, 5.0];
|
|
let y = vec![10.0, 8.0, 6.0, 4.0, 2.0];
|
|
let r = spearman(&x, &y);
|
|
assert!((r + 1.0).abs() < 1e-10);
|
|
}
|
|
|
|
#[test]
|
|
fn zero_variance_returns_zero() {
|
|
let x = vec![5.0, 5.0, 5.0];
|
|
let y = vec![1.0, 2.0, 3.0];
|
|
assert!(spearman(&x, &y).abs() < f64::EPSILON);
|
|
}
|
|
}
|