Classification evaluation metrics.
Scoring functions for models that predict a discrete class label. The module is organised around four groups:
Contingency summaries — confusion_matrix, classification_report and the raw cell counts (num_true_positives and friends) describe how a model is wrong, not just how often. Threshold-free scores — accuracy_score, balanced_accuracy_score, precision_score, recall_score, f1_score, matthews_corrcoef and cohen_kappa_score summarise a single set of hard predictions. Ranking scores — roc_curve, roc_auc_score, precision_recall_curve and average_precision_score work on continuous scores or probabilities and evaluate a model across all decision thresholds at once. Losses — log_loss, hamming_loss and zero_one_loss are lower-is-better quantities suitable for model selection.
On imbalanced data prefer balanced_accuracy_score, f1_score, matthews_corrcoef or average_precision_score over plain accuracy, which a majority-class predictor can trivially inflate.
Multiclass averaging is controlled by the average keyword shared by precision_score, recall_score and f1_score: 'binary' (default) scores only pos_label, 'macro' gives every class equal weight, 'weighted' weights by class support, and None returns the per-class array.
Call signatures follow the conventional metric(y_true, y_pred) ordering, so the functions drop into any evaluation loop unchanged.
Functions
Compute the confusion matrix of a classification result.
Entry C_{ij} counts the samples whose true class is labels[i] and whose predicted class is labels[j]. The diagonal therefore holds the correct predictions and every off-diagonal cell names a specific confusion.
For the binary case with labels sorted ascending, the layout is:
predicted 0 predicted 1 actual 0 TN FP actual 1 FN TP
Parameters
y_true
y_pred
labels
None the sorted union of the labels appearing in y_true and y_pred is used. Samples whose true or predicted label is absent from labels are skipped.
normalize
'true' divides each row by its sum (per-class recall), 'pred' divides each column by its sum (per-class precision), 'all' divides by the grand total. None (default) returns raw integer counts.
Returns
cm
dtype is int64 when normalize is None and float64 otherwise.
Notes
Complexity: O(n) time, O(k^2) memory for k classes.
When to use: whenever a single accuracy number is not enough — the matrix is the only view that tells you which classes a model trades off against each other.
See Also
>>> import numpy as np
>>> from tuiml.evaluation.metrics import confusion_matrix
>>> y_true = np.array([0, 1, 1, 0])
>>> y_pred = np.array([0, 1, 0, 0])
>>> confusion_matrix(y_true, y_pred).tolist()
[[2, 0], [1, 1]]
>>> confusion_matrix(y_true, y_pred, normalize='all').tolist()
[[0.5, 0.0], [0.25, 0.25]]
Compute the accuracy of a classification result.
Accuracy is the fraction (or, with normalize=False, the count) of samples whose predicted label matches the true label.
Parameters
y_true
y_pred
normalize
True return the fraction of correct predictions. If False return the (possibly weighted) number of correct predictions.
sample_weight
normalize=True, divided by the total weight.
Returns
score
normalize=True, otherwise a count.
Notes
Complexity: O(n) time, O(n) memory.
When to use: only when the classes are roughly balanced and every kind of error costs the same. On skewed data a constant majority-class predictor can score very high accuracy while being useless — reach for balanced_accuracy_score, f1_score or matthews_corrcoef instead.
See Also
>>> import numpy as np
>>> from tuiml.evaluation.metrics import accuracy_score
>>> y_true = np.array([0, 1, 1, 0])
>>> y_pred = np.array([0, 1, 0, 0])
>>> accuracy_score(y_true, y_pred)
0.75
>>> accuracy_score(y_true, y_pred, normalize=False)
3.0
Compute the balanced accuracy: the mean per-class recall.
Each class contributes equally regardless of how many samples it has, so a majority-class predictor scores 1/k rather than the majority frequency.
Parameters
y_true
y_pred
adjusted
True, rescale the result so that random guessing scores 0.0 by applying :math:`(b - 1/k) / (1 - 1/k)`. Perfect prediction still scores 1.0 and the adjusted score can go negative.
Returns
score
adjusted=True.
Notes
Complexity: O(kn) time for k classes, O(n) memory.
When to use: the default replacement for accuracy_score on imbalanced problems, and the natural choice when every class matters equally regardless of its frequency.
See Also
>>> import numpy as np
>>> from tuiml.evaluation.metrics import balanced_accuracy_score
>>> y_true = np.array([0, 1, 1, 0])
>>> y_pred = np.array([0, 1, 0, 0])
>>> balanced_accuracy_score(y_true, y_pred)
0.75
>>> balanced_accuracy_score(y_true, y_pred, adjusted=True)
0.5
Compute the precision: how many predicted positives are correct.
Precision is the metric to optimise when a false positive is expensive — flagging a legitimate transaction as fraud, or a healthy patient as sick.
Parameters
y_true
y_pred
**kwargs
_precision_recall_fscore_support. Recognised keys: average : {'binary', 'macro', 'weighted', 'micro'} or None, default='binary' How to reduce the per-class scores. 'binary' returns the score of pos_label only; 'macro' averages classes equally; 'weighted' averages by support; None returns the per-class array. pos_label : int, default=1 Class treated as positive when average='binary'. labels : np.ndarray, optional Label values and their order. zero_division : float, default=0.0 Value used when TP + FP == 0.
Returns
score
average=None.
Notes
Complexity: O(kn) time for k classes.
When to use: pair it with recall_score — precision alone is trivial to maximise by predicting the positive class almost never.
>>> import numpy as np
>>> from tuiml.evaluation.metrics import precision_score
>>> y_true = np.array([0, 1, 1, 0])
>>> y_pred = np.array([0, 1, 0, 0])
>>> precision_score(y_true, y_pred)
1.0
>>> round(precision_score(y_true, y_pred, average='macro'), 4)
0.8333
Compute the recall: how many actual positives were found.
Recall is the metric to optimise when a missed positive is expensive — an undiagnosed disease, or an undetected intrusion.
Parameters
y_true
y_pred
**kwargs
_precision_recall_fscore_support. Recognised keys are average, pos_label, labels and zero_division; see precision_score for their meaning.
Returns
score
average=None.
Notes
Complexity: O(kn) time for k classes.
When to use: alongside precision_score. Recall alone is trivial to maximise by predicting everything positive.
See Also
>>> import numpy as np
>>> from tuiml.evaluation.metrics import recall_score
>>> y_true = np.array([0, 1, 1, 0])
>>> y_pred = np.array([0, 1, 0, 0])
>>> recall_score(y_true, y_pred)
0.5
Compute the F1 score: the harmonic mean of precision and recall.
The harmonic mean is deliberately unforgiving: it stays near the smaller of the two, so a model cannot score well by sacrificing one for the other.
Parameters
y_true
y_pred
**kwargs
_precision_recall_fscore_support. Recognised keys are average, pos_label, labels, beta and zero_division; see precision_score for their meaning.
Returns
score
average=None.
Notes
Complexity: O(kn) time for k classes.
When to use: the standard single number for an imbalanced binary problem where the positive class is the one you care about. It ignores true negatives entirely — if those matter, use matthews_corrcoef.
See Also
>>> import numpy as np
>>> from tuiml.evaluation.metrics import f1_score
>>> y_true = np.array([0, 1, 1, 0])
>>> y_pred = np.array([0, 1, 0, 0])
>>> round(f1_score(y_true, y_pred), 4)
0.6667
>>> round(f1_score(y_true, y_pred, average='weighted'), 4)
0.7333
Compute the true positive rate (TPR, sensitivity, recall).
The fraction of actual positives that the model correctly flags; it is the y-axis of an ROC curve.
Parameters
y_true
y_pred
pos_label
Returns
rate
>>> import numpy as np
>>> from tuiml.evaluation.metrics import true_positive_rate
>>> y_true = np.array([0, 1, 1, 0])
>>> y_pred = np.array([0, 1, 0, 0])
>>> true_positive_rate(y_true, y_pred)
0.5
Compute the false positive rate (FPR, fall-out).
The fraction of actual negatives that the model wrongly flags as positive; it is the x-axis of an ROC curve.
Parameters
y_true
y_pred
pos_label
Returns
rate
0.0 when there are no negatives.
>>> import numpy as np
>>> from tuiml.evaluation.metrics import false_positive_rate
>>> y_true = np.array([0, 1, 1, 0])
>>> y_pred = np.array([0, 1, 0, 0])
>>> false_positive_rate(y_true, y_pred)
0.0
Compute the Matthews correlation coefficient (MCC).
MCC is the correlation between the true and predicted binary labels. Unlike F1 it uses all four cells of the confusion matrix, so it cannot be inflated by a model that simply predicts the majority class.
Parameters
y_true
y_pred
Returns
score
1.0 perfect agreement, 0.0 no better than chance, -1.0 total disagreement. Returns 0.0 when the denominator vanishes.
Notes
Complexity: O(n) time.
Only the binary case is implemented; for three or more classes this function currently returns 0.0 rather than the multiclass generalisation.
When to use: the most informative single number for an imbalanced binary problem, and the safest default when you are unsure which errors matter.
References
See Also
>>> import numpy as np
>>> from tuiml.evaluation.metrics import matthews_corrcoef
>>> y_true = np.array([0, 1, 1, 0])
>>> y_pred = np.array([0, 1, 0, 0])
>>> round(matthews_corrcoef(y_true, y_pred), 4)
0.5774
Compute Cohen's kappa: accuracy corrected for chance agreement.
Kappa compares the observed agreement p_o against the agreement p_e you would expect if the two labellings were independent with the same marginals.
Parameters
y_true
y_pred
Returns
score
1.0. 1.0 is perfect agreement, 0.0 is chance-level agreement, and negative values mean worse than chance. Returns 0.0 when :math:`p_e = 1`.
Notes
Complexity: O(n + k^2) time for k classes.
When to use: whenever a high raw accuracy might just reflect a skewed class distribution, and for inter-annotator agreement — kappa is symmetric in its two arguments. It supports multiclass input, unlike matthews_corrcoef.
References
See Also
>>> import numpy as np
>>> from tuiml.evaluation.metrics import cohen_kappa_score
>>> y_true = np.array([0, 1, 1, 0])
>>> y_pred = np.array([0, 1, 0, 0])
>>> cohen_kappa_score(y_true, y_pred)
0.5
Compute the area under the ROC curve (AUC).
AUC is the probability that a randomly chosen positive sample is ranked above a randomly chosen negative one, so it evaluates a model's ranking across every decision threshold at once rather than at one fixed cut-off.
Binary input accepts a 1-D score vector for the positive class. Multiclass input accepts a probability/score matrix of shape (n_samples, n_classes) and computes one-vs-rest AUC for each class.
Parameters
y_true
y_score
labels.
average
None returns the per-class array. Ignored for 1-D y_score.
labels
y_score. Defaults to np.unique(y_true).
Returns
score
0.5 is random ranking and 1.0 is perfect separation. An array is returned when average=None.
Raises
ValueError
y_score is given for more than two classes, if y_score is neither 1-D nor 2-D, or if its shape disagrees with y_true or labels.
Notes
Complexity: O(n \log n) time, dominated by the sort inside _binary_roc_curve.
When to use: for comparing rankers and probabilistic models independently of the operating threshold. On heavily imbalanced data AUC can look optimistic because the false positive rate has a very large denominator — prefer average_precision_score there.
>>> import numpy as np
>>> from tuiml.evaluation.metrics import roc_auc_score
>>> y_true = np.array([0, 0, 1, 1])
>>> y_score = np.array([0.1, 0.4, 0.35, 0.8])
>>> roc_auc_score(y_true, y_score)
0.75
Compute precision, recall, F-measure and support in a single pass.
precision_score, recall_score and f1_score separately.Parameters
y_true
y_pred
beta
beta > 1 favours recall, beta < 1 favours precision.
labels
y_true and y_pred.
pos_label
average='binary'.
average
None (default) keeps the per-class arrays.
zero_division
Returns
precision
average.
recall
average.
fscore
average.
support
average is not None.
Notes
Complexity: O(kn) time for k classes.
When to use: building a report, or logging several related metrics at once.
>>> import numpy as np
>>> from tuiml.evaluation.metrics import precision_recall_fscore_support
>>> y_true = np.array([0, 1, 1, 0])
>>> y_pred = np.array([0, 1, 0, 0])
>>> p, r, f, s = precision_recall_fscore_support(y_true, y_pred)
>>> np.round(p, 4).tolist()
[0.6667, 1.0]
>>> np.round(r, 4).tolist()
[1.0, 0.5]
>>> s.tolist()
[2.0, 2.0]
Build a text report of the main per-class classification metrics.
Parameters
y_true
y_pred
labels
y_true and y_pred.
target_names
labels. Defaults to str(label).
Returns
report
print.
Notes
Complexity: O(kn) time for k classes.
When to use: for a human-readable summary. Parse precision_recall_fscore_support instead if you need the numbers programmatically.
See Also
>>> import numpy as np
>>> from tuiml.evaluation.metrics import classification_report
>>> y_true = np.array([0, 1, 1, 0])
>>> y_pred = np.array([0, 1, 0, 0])
>>> print(classification_report(y_true, y_pred).rstrip()) # doctest: +NORMALIZE_WHITESPACE
class precision recall f1-score support
0 0.6667 1.0000 0.8000 2
1 1.0000 0.5000 0.6667 2
------------------------------------------------------------
accuracy 0.7500 4
macro avg 0.8333 0.7500 0.7333 4
Compute the F-beta score, a precision/recall trade-off.
The F-beta score is the weighted harmonic mean of precision and recall, with beta controlling how much more recall counts than precision.
Parameters
y_true
y_pred
beta
f1_score, so it always behaves as beta=1. Pass beta to precision_recall_fscore_support for a true F-beta score.
**kwargs
f1_score; see precision_score for the recognised keys.
Returns
score
average=None.
>>> import numpy as np
>>> from tuiml.evaluation.metrics import fbeta_score
>>> y_true = np.array([0, 1, 1, 0])
>>> y_pred = np.array([0, 1, 0, 0])
>>> round(fbeta_score(y_true, y_pred), 4)
0.6667
Compute the true negative rate (TNR, specificity).
The fraction of actual negatives that the model correctly leaves unflagged — the mirror image of recall.
Parameters
y_true
y_pred
pos_label
Returns
rate
0.0 when there are no negatives.
See Also
>>> import numpy as np
>>> from tuiml.evaluation.metrics import true_negative_rate
>>> y_true = np.array([0, 1, 1, 0])
>>> y_pred = np.array([0, 1, 0, 0])
>>> true_negative_rate(y_true, y_pred)
1.0
Compute the false negative rate (FNR, miss rate).
The fraction of actual positives the model misses, i.e. 1 - TPR.
Parameters
y_true
y_pred
pos_label
Returns
rate
>>> import numpy as np
>>> from tuiml.evaluation.metrics import false_negative_rate
>>> y_true = np.array([0, 1, 1, 0])
>>> y_pred = np.array([0, 1, 0, 0])
>>> false_negative_rate(y_true, y_pred)
0.5
Compute the sensitivity, the clinical name for the true positive rate.
Parameters
y_true
y_pred
pos_label
Returns
score
See Also
>>> import numpy as np
>>> from tuiml.evaluation.metrics import sensitivity_score
>>> y_true = np.array([0, 1, 1, 0])
>>> y_pred = np.array([0, 1, 0, 0])
>>> sensitivity_score(y_true, y_pred)
0.5
Compute the specificity, the clinical name for the true negative rate.
Parameters
y_true
y_pred
pos_label
Returns
score
See Also
>>> import numpy as np
>>> from tuiml.evaluation.metrics import specificity_score
>>> y_true = np.array([0, 1, 1, 0])
>>> y_pred = np.array([0, 1, 0, 0])
>>> specificity_score(y_true, y_pred)
1.0
Count the true positives: positives correctly predicted as positive.
Parameters
y_true
y_pred
pos_label
Returns
count
>>> import numpy as np
>>> from tuiml.evaluation.metrics import num_true_positives
>>> y_true = np.array([0, 1, 1, 0])
>>> y_pred = np.array([0, 1, 0, 0])
>>> num_true_positives(y_true, y_pred)
1
Count the true negatives: negatives correctly predicted as negative.
Parameters
y_true
y_pred
pos_label
Returns
count
>>> import numpy as np
>>> from tuiml.evaluation.metrics import num_true_negatives
>>> y_true = np.array([0, 1, 1, 0])
>>> y_pred = np.array([0, 1, 0, 0])
>>> num_true_negatives(y_true, y_pred)
2
Count the false positives: negatives wrongly predicted as positive.
Parameters
y_true
y_pred
pos_label
Returns
count
>>> import numpy as np
>>> from tuiml.evaluation.metrics import num_false_positives
>>> y_true = np.array([0, 1, 1, 0])
>>> y_pred = np.array([0, 1, 0, 0])
>>> num_false_positives(y_true, y_pred)
0
Count the false negatives: positives wrongly predicted as negative.
Parameters
y_true
y_pred
pos_label
Returns
count
>>> import numpy as np
>>> from tuiml.evaluation.metrics import num_false_negatives
>>> y_true = np.array([0, 1, 1, 0])
>>> y_pred = np.array([0, 1, 0, 0])
>>> num_false_negatives(y_true, y_pred)
1
Compute the receiver operating characteristic (ROC) curve.
+inf down through every distinct score and records the resulting (FPR, TPR) operating points. Plotting TPR against FPR shows the full trade-off a model offers; the diagonal is random guessing.Parameters
y_true
y_score
pos_label
Returns
fpr
tpr
thresholds
inf.
Raises
ValueError
y_score is not 1-D, the inputs differ in length, or y_true contains only one class.
Notes
Complexity: O(n \log n) time.
When to use: to pick an operating threshold, not just to score a model — the curve tells you what recall costs in false alarms.
See Also
>>> import numpy as np
>>> from tuiml.evaluation.metrics import roc_curve
>>> y_true = np.array([0, 0, 1, 1])
>>> y_score = np.array([0.1, 0.4, 0.35, 0.8])
>>> fpr, tpr, thresholds = roc_curve(y_true, y_score)
>>> fpr.tolist()
[0.0, 0.0, 0.5, 0.5, 1.0]
>>> tpr.tolist()
[0.0, 0.5, 0.5, 1.0, 1.0]
>>> thresholds.tolist()
[inf, 0.8, 0.4, 0.35, 0.1]
Compute the area under a curve by the trapezoidal rule.
Parameters
x
roc_curve).
y
Returns
area
x is decreasing.
Notes
Complexity: O(n) time.
When to use: to integrate any curve produced elsewhere in this module. roc_auc_score is the convenience wrapper for the ROC case.
>>> import numpy as np
>>> from tuiml.evaluation.metrics import auc, roc_curve
>>> y_true = np.array([0, 0, 1, 1])
>>> y_score = np.array([0.1, 0.4, 0.35, 0.8])
>>> fpr, tpr, _ = roc_curve(y_true, y_score)
>>> auc(fpr, tpr)
0.75
Compute the precision-recall curve.
Parameters
y_true
y_score
pos_label
Returns
precision
recall
thresholds
Notes
Complexity: O(n \log n) time, dominated by the sort.
When to use: on imbalanced problems, where a large true-negative pool makes an ROC curve look better than the model really is.
See Also
>>> import numpy as np
>>> from tuiml.evaluation.metrics import precision_recall_curve
>>> y_true = np.array([0, 0, 1, 1])
>>> y_score = np.array([0.1, 0.4, 0.35, 0.8])
>>> precision, recall, thresholds = precision_recall_curve(y_true, y_score)
>>> np.round(precision, 4).tolist()
[1.0, 0.5, 0.6667, 0.5]
>>> recall.tolist()
[0.5, 0.5, 1.0, 1.0]
Compute the average precision (AP), the area under the PR curve.
AP summarises precision_recall_curve as the precision achieved at each threshold, weighted by the gain in recall it produces.
Parameters
y_true
y_score
pos_label
Returns
score
0.5 as it is for ROC AUC.
Notes
Complexity: O(n \log n) time.
When to use: as the headline ranking metric on heavily imbalanced data, where roc_auc_score is dominated by the abundant negatives.
Warnings
Compute the logistic loss (cross-entropy) of predicted probabilities.
Log loss scores a probabilistic classifier: it rewards assigning high probability to the correct class and punishes confident mistakes severely, because the penalty grows without bound as the predicted probability of the true class approaches zero.
Parameters
y_true
y_pred_proba — so they must be integers 0 … n_classes - 1.
y_pred_proba
1 and expanded to two columns.
eps
[eps, 1 - eps] so that :math:`\log 0` never occurs.
Returns
loss
0.0 means every true class was predicted with probability 1.
Notes
Complexity: O(n) time.
When to use: whenever calibrated probabilities matter — ranking or thresholding metrics such as AUC cannot tell a well-calibrated model from an over-confident one with the same ordering.
See Also
>>> import numpy as np
>>> from tuiml.evaluation.metrics import log_loss
>>> y_true = np.array([0, 0, 1, 1])
>>> proba = np.array([[0.9, 0.1], [0.6, 0.4], [0.35, 0.65], [0.2, 0.8]])
>>> round(log_loss(y_true, proba), 4)
0.3175
Compute the Hamming loss: the fraction of mismatched labels.
For single-label input this is simply 1 - accuracy. For multi-label indicator arrays it is the fraction of individual label positions that disagree, which makes it more forgiving than an exact-match criterion.
Parameters
y_true
y_pred
y_true.
Returns
loss
Notes
Complexity: O(n) time.
When to use: for multi-label problems, where partial credit for getting most labels right is the behaviour you want.
See Also
>>> import numpy as np
>>> from tuiml.evaluation.metrics import hamming_loss
>>> y_true = np.array([0, 1, 1, 0])
>>> y_pred = np.array([0, 1, 0, 0])
>>> hamming_loss(y_true, y_pred)
0.25
Compute the 0-1 loss: the fraction (or count) of misclassifications.
The direct complement of accuracy_score; it charges 1 for every wrong prediction regardless of how wrong it was.
Parameters
y_true
y_pred
normalize
True return the fraction of misclassifications; if False return their count.
Returns
loss
normalize=False. Lower is better.
Notes
Complexity: O(n) time.
When to use: when every error costs the same. If errors have different costs, weight them via confusion_matrix instead.
>>> import numpy as np
>>> from tuiml.evaluation.metrics import zero_one_loss
>>> y_true = np.array([0, 1, 1, 0])
>>> y_pred = np.array([0, 1, 0, 0])
>>> zero_one_loss(y_true, y_pred)
0.25
>>> zero_one_loss(y_true, y_pred, normalize=False)
1.0