use scirs2_core::ndarray::{ArrayBase, Data, Dimension};
use scirs2_core::numeric::{Float, FromPrimitive, NumCast};
use super::{check_sameshape, mean};
use crate::error::{MetricsError, Result};
#[allow(dead_code)]
pub fn r2_score<F, S1, S2, D1, D2>(
y_true: &ArrayBase<S1, D1>,
y_pred: &ArrayBase<S2, D2>,
) -> Result<F>
where
F: Float + NumCast + std::fmt::Debug,
S1: Data<Elem = F>,
S2: Data<Elem = F>,
D1: Dimension,
D2: Dimension,
{
check_sameshape::<F, S1, S2, D1, D2>(y_true, y_pred)?;
let _n_samples = y_true.len();
let y_mean = mean(y_true);
let mut ss_total = F::zero();
let mut ss_residual = F::zero();
for (yt, yp) in y_true.iter().zip(y_pred.iter()) {
let residual = *yt - *yp;
ss_residual = ss_residual + residual * residual;
let deviation = *yt - y_mean;
ss_total = ss_total + deviation * deviation;
}
if ss_total < F::epsilon() {
return Err(MetricsError::InvalidInput(
"Total sum of squares is zero. R^2 score is not defined when all _true values are identical.".to_string(),
));
}
Ok(F::one() - ss_residual / ss_total)
}
#[allow(dead_code)]
pub fn adjusted_r2_score<F, S1, S2, D1, D2>(
y_true: &ArrayBase<S1, D1>,
y_pred: &ArrayBase<S2, D2>,
n_features: usize,
) -> Result<F>
where
F: Float + NumCast + std::fmt::Debug + FromPrimitive,
S1: Data<Elem = F>,
S2: Data<Elem = F>,
D1: Dimension,
D2: Dimension,
{
let n_samples = y_true.len();
if n_samples <= n_features + 1 {
return Err(MetricsError::InvalidInput(
"Number of samples must be greater than number of _features + 1".to_string(),
));
}
let r2 = r2_score(y_true, y_pred)?;
let n: F = NumCast::from(n_samples).expect("Operation failed");
let p = NumCast::from(n_features).expect("Operation failed");
let one = F::one();
let numerator = (one - r2) * (n - one);
let denominator = n - p - one;
Ok(one - numerator / denominator)
}
#[allow(dead_code)]
pub fn explained_variance_score<F, S1, S2, D1, D2>(
y_true: &ArrayBase<S1, D1>,
y_pred: &ArrayBase<S2, D2>,
) -> Result<F>
where
F: Float + NumCast + std::fmt::Debug,
S1: Data<Elem = F>,
S2: Data<Elem = F>,
D1: Dimension,
D2: Dimension,
{
check_sameshape::<F, S1, S2, D1, D2>(y_true, y_pred)?;
let n_samples = y_true.len();
let y_true_mean = mean(y_true);
let y_pred_mean = mean(y_pred);
let mut y_true_var = F::zero();
let mut y_pred_var = F::zero();
let mut covar = F::zero();
for (yt, yp) in y_true.iter().zip(y_pred.iter()) {
let true_dev = *yt - y_true_mean;
let pred_dev = *yp - y_pred_mean;
y_true_var = y_true_var + true_dev * true_dev;
y_pred_var = y_pred_var + pred_dev * pred_dev;
covar = covar + true_dev * pred_dev;
}
y_true_var = y_true_var / NumCast::from(n_samples).expect("Operation failed");
covar = covar / NumCast::from(n_samples).expect("Operation failed");
if y_true_var < F::epsilon() {
return Err(MetricsError::InvalidInput(
"Variance of y_true is zero. Explained variance score is not defined when all _true values are identical.".to_string(),
));
}
let explained_var = F::one() - (y_true_var - covar) / y_true_var;
Ok(explained_var.max(F::zero()).min(F::one()))
}
#[allow(dead_code)]
pub fn pearson_correlation<F, S1, S2, D1, D2>(
y_true: &ArrayBase<S1, D1>,
y_pred: &ArrayBase<S2, D2>,
) -> Result<F>
where
F: Float + NumCast + std::fmt::Debug,
S1: Data<Elem = F>,
S2: Data<Elem = F>,
D1: Dimension,
D2: Dimension,
{
check_sameshape::<F, S1, S2, D1, D2>(y_true, y_pred)?;
let y_true_mean = mean(y_true);
let y_pred_mean = mean(y_pred);
let mut numerator = F::zero();
let mut denom_true = F::zero();
let mut denom_pred = F::zero();
for (yt, yp) in y_true.iter().zip(y_pred.iter()) {
let true_dev = *yt - y_true_mean;
let pred_dev = *yp - y_pred_mean;
numerator = numerator + true_dev * pred_dev;
denom_true = denom_true + true_dev * true_dev;
denom_pred = denom_pred + pred_dev * pred_dev;
}
if denom_true < F::epsilon() || denom_pred < F::epsilon() {
return Err(MetricsError::InvalidInput(
"Pearson correlation is not defined when variance of either variable is zero."
.to_string(),
));
}
let correlation = numerator / (denom_true.sqrt() * denom_pred.sqrt());
Ok(correlation.max(-F::one()).min(F::one()))
}
#[allow(dead_code)]
pub fn spearman_correlation<F, S1, S2, D1, D2>(
y_true: &ArrayBase<S1, D1>,
y_pred: &ArrayBase<S2, D2>,
) -> Result<F>
where
F: Float + NumCast + std::fmt::Debug + FromPrimitive,
S1: Data<Elem = F>,
S2: Data<Elem = F>,
D1: Dimension,
D2: Dimension,
{
check_sameshape::<F, S1, S2, D1, D2>(y_true, y_pred)?;
let _n_samples = y_true.len();
let y_true_ranks = compute_ranks(y_true)?;
let y_pred_ranks = compute_ranks(y_pred)?;
pearson_correlation(&y_true_ranks, &y_pred_ranks)
}
#[allow(dead_code)]
fn compute_ranks<F, S, D>(x: &ArrayBase<S, D>) -> Result<scirs2_core::ndarray::Array1<F>>
where
F: Float + NumCast + std::fmt::Debug + FromPrimitive,
S: Data<Elem = F>,
D: Dimension,
{
let n = x.len();
let mut pairs: Vec<(F, usize)> = x.iter().cloned().zip(0..n).collect();
pairs.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
let mut ranks = vec![F::zero(); n];
let mut i = 0;
while i < n {
let val = pairs[i].0;
let mut j = i + 1;
while j < n && pairs[j].0 == val {
j += 1;
}
let rank_val = F::from(i + j - 1).expect("Failed to convert to float")
/ NumCast::from(2).expect("Operation failed")
+ F::one();
for k in i..j {
ranks[pairs[k].1] = rank_val;
}
i = j;
}
Ok(scirs2_core::ndarray::Array1::from_vec(ranks))
}
#[allow(dead_code)]
pub fn concordance_correlation<F, S1, S2, D1, D2>(
y_true: &ArrayBase<S1, D1>,
y_pred: &ArrayBase<S2, D2>,
) -> Result<F>
where
F: Float + NumCast + std::fmt::Debug + FromPrimitive,
S1: Data<Elem = F>,
S2: Data<Elem = F>,
D1: Dimension,
D2: Dimension,
{
check_sameshape::<F, S1, S2, D1, D2>(y_true, y_pred)?;
let y_true_mean = mean(y_true);
let y_pred_mean = mean(y_pred);
let mut y_true_var = F::zero();
let mut y_pred_var = F::zero();
let mut covar = F::zero();
for (yt, yp) in y_true.iter().zip(y_pred.iter()) {
let true_dev = *yt - y_true_mean;
let pred_dev = *yp - y_pred_mean;
y_true_var = y_true_var + true_dev * true_dev;
y_pred_var = y_pred_var + pred_dev * pred_dev;
covar = covar + true_dev * pred_dev;
}
let n = NumCast::from(y_true.len()).expect("Operation failed");
y_true_var = y_true_var / n;
y_pred_var = y_pred_var / n;
covar = covar / n;
if y_true_var < F::epsilon() || y_pred_var < F::epsilon() {
return Err(MetricsError::InvalidInput(
"Concordance correlation is not defined when variance of either variable is zero."
.to_string(),
));
}
let sd_true = y_true_var.sqrt();
let sd_pred = y_pred_var.sqrt();
let corr = covar / (sd_true * sd_pred);
let scale_shift = (F::from(2.0).expect("Failed to convert constant to float") * covar)
/ (y_true_var + y_pred_var + (y_true_mean - y_pred_mean).powi(2));
Ok(corr * scale_shift)
}