use ndarray::Array2;
pub fn kendall_tau(data: &Array2<f64>) -> Array2<f64> {
let cols = data.ncols();
let mut tau_matrix = Array2::<f64>::zeros((cols, cols));
for i in 0..cols {
for j in i..cols {
let col_i = data.column(i);
let col_j = data.column(j);
let mut concordant = 0;
let mut discordant = 0;
for k in 0..col_i.len() {
for l in (k + 1)..col_i.len() {
let x_diff = col_i[k] - col_i[l];
let y_diff = col_j[k] - col_j[l];
let sign = x_diff * y_diff;
if sign > 0.0 {
concordant += 1;
} else if sign < 0.0 {
discordant += 1;
}
}
}
let total_pairs = (col_i.len() * (col_i.len() - 1)) / 2;
let tau = (concordant as f64 - discordant as f64) / total_pairs as f64;
tau_matrix[[i, j]] = tau;
tau_matrix[[j, i]] = tau;
}
}
tau_matrix
}
pub fn tau_to_corr(tau: f64) -> f64 {
(std::f64::consts::FRAC_PI_2 * tau).sin()
}
pub fn corr_to_tau(rho: f64) -> f64 {
2.0 / std::f64::consts::PI * rho.clamp(-1.0, 1.0).asin()
}
pub fn tau_matrix_to_corr_matrix(tau: &Array2<f64>) -> Array2<f64> {
tau.mapv(tau_to_corr)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tau_corr_round_trip() {
for &t in &[-0.7, -0.3, 0.0, 0.2, 0.8] {
let r = tau_to_corr(t);
let t2 = corr_to_tau(r);
assert!(
(t - t2).abs() < 1e-12,
"round-trip failed: {t} -> {r} -> {t2}"
);
}
}
#[test]
fn tau_zero_means_corr_zero() {
assert!(tau_to_corr(0.0).abs() < 1e-15);
}
#[test]
fn tau_one_implies_corr_one() {
assert!((tau_to_corr(1.0) - 1.0).abs() < 1e-15);
assert!((tau_to_corr(-1.0) + 1.0).abs() < 1e-15);
}
}