Skip to content

fix(metrics): correct explained_variance residual-variance term#447

Open
teddytennant wants to merge 1 commit into
rust-ml:masterfrom
teddytennant:fix-explained-variance-residual
Open

fix(metrics): correct explained_variance residual-variance term#447
teddytennant wants to merge 1 commit into
rust-ml:masterfrom
teddytennant:fix-explained-variance-residual

Conversation

@teddytennant

Copy link
Copy Markdown

What

SingleTargetRegression::explained_variance computed the residual-variance numerator as diff.mapv_into(|x| x * x).sum() - mean_error, i.e. Σ(diffᵢ²) − mean(diff). That subtracts the raw residual mean (units of y) from a sum of squared residuals (units of y²), which is dimensionally inconsistent.

Explained variance is defined as:

EV = 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, Σ(diffᵢ − mean_error)². mean_error = diff.mean() was already computed but misused. This PR uses it correctly. Numerator and denominator are both sums over the same n samples, so the biased-variance 1/n factors cancel and the existing + 1e-10 denominator guard is preserved.

Why

For y_true = [0, 0.1, 0.2, 0.3, 0.4], y_pred = [0.1, 0.3, 0.2, 0.5, 0.7]:

quantity value
old explained_variance 0.8 (matches neither R² nor EV)
r2 −0.8
corrected explained_variance 0.48
sklearn.metrics.explained_variance_score 0.48

The old result of 0.8 corresponded to no standard metric. The corrected value matches scikit-learn's reference implementation.

Testing

  • Updated test_explained_variance_for_single_targets to assert the corrected value 0.48 (was 0.8), with a comment citing the scikit-learn reference.
  • test_same (explained_variance(ones, ones) == 1.0) still passes: diff == 0 ⇒ numerator 01 − 0/1e-10 = 1.0.
  • cargo test -p linfa explained_variance and the full cargo test -p linfa --lib metrics_regression suite (16 tests) pass.
  • cargo fmt --check and cargo clippy -p linfa --lib --tests -- -D warnings are clean.

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.
Copilot AI review requested due to automatic review settings July 8, 2026 16:33

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants