Skip to main content

How to Calculate MAE, RMSE and R² in scikit-learn

· 6 min read
Jagdish Kumawat
Founder @ Dewiride

A regression model predicts numbers, so you measure it by how far its numbers are from the real ones. Three scores do most of the work: MAE, RMSE and R². This guide calculates all three with scikit-learn for a real house price model, compares them against a model that always guesses the average, and explains what each number says in plain words.

Syntax​

Syntax
from sklearn.metrics import mean_absolute_error, r2_score, root_mean_squared_error

mae = mean_absolute_error(y_true, y_pred)
rmse = root_mean_squared_error(y_true, y_pred)
r2 = r2_score(y_true, y_pred)
PartWhat it means
y_trueThe real values, such as the test set's target. It always comes first
y_predThe model's predictions for the same rows, in the same order
mean_absolute_errorMAE: the average size of the errors, in the target's own units. "The best value is 0.0"
root_mean_squared_errorRMSE: like MAE, but large errors count more. Also best at 0.0
r2_scoreR²: how much of the variation the model explains. "Best possible score is 1.0 and it can be negative"

Prerequisites​

Step 1: Score Two Models​

A score on its own is hard to judge, so this script scores two models: a random forest, and a baseline that ignores the features and always predicts the average house value of the training rows (DummyRegressor).

regression_metrics.py
from sklearn.datasets import fetch_california_housing
from sklearn.dummy import DummyRegressor
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import (
mean_absolute_error,
mean_squared_error,
r2_score,
root_mean_squared_error,
)
from sklearn.model_selection import train_test_split

frame = fetch_california_housing(as_frame=True).frame
X, y = frame.drop(columns="MedHouseVal"), frame["MedHouseVal"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

models = {
"Random forest": RandomForestRegressor(n_estimators=100, random_state=42, n_jobs=-1),
"Always guess the average": DummyRegressor(strategy="mean"),
}
for name, model in models.items():
predictions = model.fit(X_train, y_train).predict(X_test)
print(name)
print(f" MAE {mean_absolute_error(y_test, predictions):.3f}")
print(f" MSE {mean_squared_error(y_test, predictions):.3f}")
print(f" RMSE {root_mean_squared_error(y_test, predictions):.3f}")
print(f" R2 {r2_score(y_test, predictions):.3f}")

Every metric takes the same two things: the true values (y_test) first, then the predictions.

Terminal
uv run python regression_metrics.py
Output
Random forest
MAE 0.328
MSE 0.256
RMSE 0.506
R2 0.804
Always guess the average
MAE 0.906
MSE 1.311
RMSE 1.145
R2 -0.000

A terminal showing regression metrics for a random forest, MAE 0.328, MSE 0.256, RMSE 0.506 and R2 0.804, next to a baseline that always guesses the average, MAE 0.906, MSE 1.311, RMSE 1.145 and R2 -0.000.

Step 2: Read Each Score​

The house values are in units of $100,000, and so are MAE and RMSE.

MAE, mean absolute error. The average size of a miss. The forest's 0.328 means it's off by about $32,800 per district on average; the baseline is off by about $90,600. Lower is better.

MSE, mean squared error. The average of the squared misses. Squaring makes big misses count much more than small ones, but it also squares the units, so 0.256 is hard to picture. Lower is better.

RMSE, root mean squared error. The square root of MSE: √0.256 ≈ 0.506. It's back in the target's units, about $50,600, and like MSE it punishes big misses. RMSE is always at least as large as MAE; a big gap between them means a few large misses. Lower is better.

R², the coefficient of determination. How much of the variation in house values the model explains, compared with always guessing the average. 1.0 is perfect, 0 is no better than the average, and below 0 is worse than the average. The forest's 0.804 means it explains about 80% of the variation. The baseline's -0.000 is the average-guesser itself: about zero by definition, here a hair below, because the test rows' average differs slightly from the training rows'.

Step 3: Choose the Right Score​

ScoreUnitsBest for
MAESame as the targetExplaining a typical error to anyone: "off by about $32,800"
RMSESame as the targetWhen large misses are much worse than small ones
R²None (0 to 1, can go negative)Comparing how well models explain the data, across datasets

Track all three: the MLflow series logs MAE, RMSE and R² for every run and compares them side by side: MLflow Tutorial for Machine Learning in Python — Part 1.

Common Mistakes​

MistakeWhat happensDo this instead
mean_squared_error(y_true, y_pred, squared=False) from older tutorialsTypeError: got an unexpected keyword argument 'squared' in scikit-learn 1.9.1Use root_mean_squared_error: the Fixed post explains why
Passing the arguments in the wrong orderR² comes out wrong: 0.748 instead of 0.804 here, while MAE and RMSE don't changeAlways true values first: r2_score(y_test, predictions)
Scoring on the training rowsEvery score looks much better than realityScore on the test set
Reading MSE in dollarsIts units are squaredRead RMSE or MAE instead

Troubleshooting​

SymptomLikely causeFix
An ImportError for root_mean_squared_errorscikit-learn older than 1.4, the version that added itUpgrade scikit-learn
R² is negativeThe model is worse than always guessing the averageCheck the data split, the features and the model

FAQ​

Which score should I report?​

MAE for people, because it's in real units and easy to picture. Keep RMSE and R² too: RMSE catches large misses, and R² compares models.

Why is RMSE bigger than MAE?​

Squaring weights big misses more, so RMSE is always at least as large as MAE. They're equal only when every miss is the same size.

Is an R² of 0.804 good?​

It depends on the problem. Here it's far better than the baseline's 0, and more trees didn't improve it much, which suggests the remaining error comes from information the features don't contain.

Conclusion​

mean_absolute_error, root_mean_squared_error and r2_score measured the forest at an average miss of about $32,800, an RMSE of about $50,600 and an R² of 0.804, against $90,600, $114,500 and about 0 for a model that always guesses the average.

Additional Resources​