How To Implement Machine Learning Metrics From Scratch in Python
Suresh Madhra·Feb 17, 2026·9 min read
A prediction without a metric is an opinion
Once your model produces predictions you need one number that says whether they are any good — and the choice of number quietly defines what your model will optimise for. This tutorial implements the four metrics you will use most, in plain Python, so their behaviour holds no surprises.
print("Lazy model on imbalanced data: %.1f%%"% accuracy_metric(imbalanced_actual, lazy_predictions))
print("...and it never once found the thing you care about.")
2. Confusion matrix
The confusion matrix is accuracy's honest cousin: it shows you which classes get confused with which. Everything else — precision, recall, F1 — is arithmetic on its four cells.
confusion_matrix.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
defconfusion_matrix(actual, predicted):
unique =sorted(set(actual))
index ={value: i for i, value inenumerate(unique)}
matrix =[[0for _ in unique]for _ in unique]
for a, p inzip(actual, predicted):
matrix[index[a]][index[p]]+=1
return unique, matrix
defprint_confusion_matrix(unique, matrix):
print(" "+" ".join("p=%-3s"% u for u in unique))
for i, u inenumerate(unique):
print("a=%-3s "% u +" ".join("%-5d"% v for v in matrix[i]))
print("Precision %.3f Recall %.3f F1 %.3f"%(precision, recall, f1))
3. Mean absolute error
MAE=n1i=1∑n∣y^i−yi∣
MAE is in the same units as your target and treats every unit of error equally. Report it when a stakeholder asks "how far off are we, typically?"
4. Root mean squared error
RMSE=n1i=1∑n(y^i−yi)2
RMSE squares errors before averaging, so large misses hurt disproportionately. Use it when a single big error is much worse than several small ones. RMSE is always greater than or equal to MAE; the gap between them tells you how spread out your errors are.
regression_metrics.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from math import sqrt
defmae_metric(actual, predicted):
returnsum(abs(p - a)for a, p inzip(actual, predicted))/float(len(actual))
defrmse_metric(actual, predicted):
total =sum((p - a)**2for a, p inzip(actual, predicted))
return sqrt(total /float(len(actual)))
defr_squared(actual, predicted):
mean =sum(actual)/float(len(actual))
ss_res =sum((a - p)**2for a, p inzip(actual, predicted))
ss_tot =sum((a - mean)**2for a in actual)
return1- ss_res / ss_tot
actual =[0.1,0.2,0.3,0.4,0.5]
steady =[0.11,0.19,0.29,0.41,0.5]
one_blowup=[0.1,0.2,0.3,0.4,1.5]
for name, pred in[("steady errors", steady),("one blow-up", one_blowup)]: