Skip to content
  • Regression Metrics · Machine Learning

Regression Metrics

LearnViaNotes @learnvianotes· 3 min read

Regression metrics measure how far predictions are from the true values. They differ in how they punish large errors, in their units, and in what baseline they compare against.

MetricFormulaUnitsNotes
MAEmean |y − ŷ|Target'sEvery unit of error counts the same; less sensitive to outliers than MSE
MSEmean (y − ŷ)²Target's squaredPunishes large errors heavily; the usual training loss
RMSE√MSETarget'sLike MSE but interpretable; always ≥ MAE
R²1 − SS_res / SS_totUnitlessShare of variance explained relative to predicting the mean
Adjusted R²1 − (1 − R²)(n − 1)/(n − p − 1)UnitlessPenalises extra predictors
MAPEmean |y − ŷ| / |y|%Scale-free, but undefined at y = 0, explodes near 0, asymmetric
RMSLERMSE of log(1 + y)≈ relativeFor targets spanning orders of magnitude; penalises under-prediction more
Median absolute errormedian |y − ŷ|Target'sRobust to a few huge errors

SS_res = Σ(yᵢ − ŷᵢ)² SS_tot = Σ(yᵢ − ȳ)² R² = 1 − SS_res / SS_tot

Python
import numpy as np
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score, root_mean_squared_error

y = np.array([100, 150, 200, 250, 300])
y_hat = np.array([110, 140, 210, 240, 380])            # one big miss

print(mean_absolute_error(y, y_hat), mean_squared_error(y, y_hat))    # 24.0 1360.0
print(round(root_mean_squared_error(y, y_hat), 2))                    # 36.88 ← pulled up by the 80 error
ss_res, ss_tot = np.sum((y - y_hat) ** 2), np.sum((y - y.mean()) ** 2)
print(round(1 - ss_res / ss_tot, 4), round(r2_score(y, y_hat), 4))    # 0.728 0.728

R² can be negative

R² compares the model with always predicting the mean of the true values. A model worse than that baseline has R² < 0 — common on a test set when a model has overfit or the data has drifted. R² = 1 is perfect; there is no lower bound.

Python
import numpy as np
from sklearn.metrics import r2_score

y = np.array([3.0, 5.0, 7.0, 9.0])
print(r2_score(y, np.full(4, y.mean())))              # 0.0 ← predicting the mean
print(r2_score(y, np.array([9.0, 7.0, 5.0, 3.0])))    # -3.0 ← worse than the mean

MAE vs RMSE, and the MAPE trap

Python
import numpy as np
from sklearn.metrics import mean_absolute_error, mean_absolute_percentage_error, root_mean_squared_error

y = np.array([10.0, 10.0, 10.0, 10.0])
spread = np.array([12.0, 8.0, 12.0, 8.0])      # four errors of 2
one_big = np.array([10.0, 10.0, 10.0, 18.0])   # one error of 8
print(mean_absolute_error(y, spread), mean_absolute_error(y, one_big))            # 2.0 2.0 ← MAE: same
print(root_mean_squared_error(y, spread), root_mean_squared_error(y, one_big))    # 2.0 4.0 ← RMSE prefers spread-out errors

demand = np.array([0.5, 100.0])                 # a near-zero actual
print(round(mean_absolute_percentage_error(demand, np.array([1.5, 100.0])), 2))   # 1.0 ← off by 1 unit → 100% MAPE
ChooseWhen
MAEErrors cost proportionally; outliers shouldn't dominate; you want the median-type prediction
RMSELarge errors are disproportionately bad; matches a squared-error training loss
R²Communicating fit relative to a naive baseline (always report an error metric too)
MAPE / sMAPE / WAPEStakeholders think in percentages and actuals are far from 0 (WAPE = Σ|e| / Σ|y| is more stable)
RMSLETargets such as prices or counts spanning orders of magnitude

Can R² be negative?

MAE or RMSE?

Free for students and teachers

Write notes like this one

Cards for every idea, quizzes and flashcards, read aloud — on the web and your phone.

Something wrong with this note? Open it to report