From 0a384297416538d8027cbb9005268399cf5de618 Mon Sep 17 00:00:00 2001 From: Teddy Tennant Date: Wed, 8 Jul 2026 12:29:16 -0400 Subject: [PATCH] fix(metrics): correct explained_variance residual-variance term The residual-variance numerator in explained_variance subtracted the raw residual mean from a sum of squared residuals, mixing units of y with units of y^2. Explained variance is 1 - Var(y_true - y_pred) / Var(y_true), so the numerator must be the sum of squared deviations of the residuals from their mean. For y_true=[0,0.1,0.2,0.3,0.4], y_pred=[0.1,0.3,0.2,0.5,0.7] the function returned 0.8; the correct explained variance is 0.48, matching sklearn.metrics.explained_variance_score. Update the test accordingly. --- src/metrics_regression.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/metrics_regression.rs b/src/metrics_regression.rs index c15c8db08..6af225f32 100644 --- a/src/metrics_regression.rs +++ b/src/metrics_regression.rs @@ -137,7 +137,9 @@ pub trait SingleTargetRegression>: let mean_error = diff.mean().ok_or(Error::NotEnoughSamples)?; Ok(F::one() - - (diff.mapv_into(|x| x * x).sum() - mean_error) + - diff + .mapv_into(|x| (x - mean_error) * (x - mean_error)) + .sum() / (single_target_compare_to .mapv(|x| (x - mean) * (x - mean)) .sum() @@ -474,7 +476,10 @@ mod tests { let abs_err_from_arr1 = prediction.explained_variance(st_dataset.targets()).unwrap(); let prediction: DatasetBase<_, _> = (records.view(), prediction).into(); let abs_err_from_ds = prediction.explained_variance(&st_dataset).unwrap(); - assert_abs_diff_eq!(abs_err_from_arr1, 0.8, epsilon = 1e-5); + // Explained variance is 1 - Var(y_true - y_pred) / Var(y_true), matching + // sklearn.metrics.explained_variance_score([0.0,0.1,0.2,0.3,0.4], + // [0.1,0.3,0.2,0.5,0.7]) == 0.48 + assert_abs_diff_eq!(abs_err_from_arr1, 0.48, epsilon = 1e-5); assert_abs_diff_eq!(abs_err_from_arr1, abs_err_from_ds); }