From c5dd85bf952d6b3fdacdc31faf64606cca1ccb9d Mon Sep 17 00:00:00 2001 From: Sagar Gupta Date: Mon, 2 Mar 2026 07:04:06 +0530 Subject: [PATCH 1/2] feat: add confusion matrix with precision, recall, and F1 score Add classification evaluation metrics: - confusion_matrix: binary and multiclass support - precision: TP / (TP + FP) - recall (sensitivity): TP / (TP + FN) - f1_score: harmonic mean of precision and recall All functions include doctests. --- machine_learning/confusion_matrix.py | 136 +++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 machine_learning/confusion_matrix.py diff --git a/machine_learning/confusion_matrix.py b/machine_learning/confusion_matrix.py new file mode 100644 index 000000000000..8ee3ac86233a --- /dev/null +++ b/machine_learning/confusion_matrix.py @@ -0,0 +1,136 @@ +""" +Confusion Matrix implementation for evaluating classification models. + +A confusion matrix is a table used to evaluate the performance of a +classification algorithm by comparing predicted labels against actual labels. + +Reference: https://en.wikipedia.org/wiki/Confusion_matrix +""" + +import numpy as np + + +def confusion_matrix(actual: list, predicted: list) -> np.ndarray: + """ + Calculate the confusion matrix for binary or multiclass classification. + + Args: + actual: List of actual class labels. + predicted: List of predicted class labels. + + Returns: + A 2D numpy array representing the confusion matrix. + + Examples: + >>> actual = [1, 0, 1, 1, 0, 1] + >>> predicted = [1, 0, 0, 1, 0, 0] + >>> confusion_matrix(actual, predicted) + array([[2, 0], + [2, 2]]) + + >>> actual = [0, 0, 1, 1, 2, 2] + >>> predicted = [0, 1, 1, 2, 2, 0] + >>> confusion_matrix(actual, predicted) + array([[1, 1, 0], + [0, 1, 1], + [1, 0, 1]]) + """ + classes = sorted(set(actual) | set(predicted)) + n = len(classes) + class_to_index = {c: i for i, c in enumerate(classes)} + + matrix = np.zeros((n, n), dtype=int) + for a, p in zip(actual, predicted): + matrix[class_to_index[a]][class_to_index[p]] += 1 + + return matrix + + +def precision(actual: list, predicted: list, positive_label: int = 1) -> float: + """ + Calculate precision: TP / (TP + FP). + + Args: + actual: List of actual class labels. + predicted: List of predicted class labels. + positive_label: The label considered as positive class. + + Returns: + Precision score as a float. + + Examples: + >>> actual = [1, 0, 1, 1, 0, 1] + >>> predicted = [1, 0, 0, 1, 0, 0] + >>> precision(actual, predicted) + 1.0 + + >>> actual = [1, 0, 1, 1, 0, 1] + >>> predicted = [1, 1, 0, 1, 0, 0] + >>> precision(actual, predicted) + 0.6666666666666666 + """ + tp = sum(1 for a, p in zip(actual, predicted) if a == positive_label and p == positive_label) + fp = sum(1 for a, p in zip(actual, predicted) if a != positive_label and p == positive_label) + return tp / (tp + fp) if (tp + fp) > 0 else 0.0 + + +def recall(actual: list, predicted: list, positive_label: int = 1) -> float: + """ + Calculate recall (sensitivity): TP / (TP + FN). + + Args: + actual: List of actual class labels. + predicted: List of predicted class labels. + positive_label: The label considered as positive class. + + Returns: + Recall score as a float. + + Examples: + >>> actual = [1, 0, 1, 1, 0, 1] + >>> predicted = [1, 0, 0, 1, 0, 0] + >>> recall(actual, predicted) + 0.5 + + >>> actual = [1, 0, 1, 1, 0, 1] + >>> predicted = [1, 1, 1, 1, 0, 1] + >>> recall(actual, predicted) + 1.0 + """ + tp = sum(1 for a, p in zip(actual, predicted) if a == positive_label and p == positive_label) + fn = sum(1 for a, p in zip(actual, predicted) if a == positive_label and p != positive_label) + return tp / (tp + fn) if (tp + fn) > 0 else 0.0 + + +def f1_score(actual: list, predicted: list, positive_label: int = 1) -> float: + """ + Calculate F1 score: harmonic mean of precision and recall. + + Args: + actual: List of actual class labels. + predicted: List of predicted class labels. + positive_label: The label considered as positive class. + + Returns: + F1 score as a float. + + Examples: + >>> actual = [1, 0, 1, 1, 0, 1] + >>> predicted = [1, 0, 0, 1, 0, 0] + >>> round(f1_score(actual, predicted), 4) + 0.6667 + + >>> actual = [1, 0, 1, 1, 0, 1] + >>> predicted = [1, 0, 1, 1, 0, 1] + >>> f1_score(actual, predicted) + 1.0 + """ + p = precision(actual, predicted, positive_label) + r = recall(actual, predicted, positive_label) + return 2 * p * r / (p + r) if (p + r) > 0 else 0.0 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 9ddb954cd0393febafd80442aea9482c64e3e20f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 01:34:31 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- machine_learning/confusion_matrix.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/machine_learning/confusion_matrix.py b/machine_learning/confusion_matrix.py index 8ee3ac86233a..78439aca716f 100644 --- a/machine_learning/confusion_matrix.py +++ b/machine_learning/confusion_matrix.py @@ -69,8 +69,16 @@ def precision(actual: list, predicted: list, positive_label: int = 1) -> float: >>> precision(actual, predicted) 0.6666666666666666 """ - tp = sum(1 for a, p in zip(actual, predicted) if a == positive_label and p == positive_label) - fp = sum(1 for a, p in zip(actual, predicted) if a != positive_label and p == positive_label) + tp = sum( + 1 + for a, p in zip(actual, predicted) + if a == positive_label and p == positive_label + ) + fp = sum( + 1 + for a, p in zip(actual, predicted) + if a != positive_label and p == positive_label + ) return tp / (tp + fp) if (tp + fp) > 0 else 0.0 @@ -97,8 +105,16 @@ def recall(actual: list, predicted: list, positive_label: int = 1) -> float: >>> recall(actual, predicted) 1.0 """ - tp = sum(1 for a, p in zip(actual, predicted) if a == positive_label and p == positive_label) - fn = sum(1 for a, p in zip(actual, predicted) if a == positive_label and p != positive_label) + tp = sum( + 1 + for a, p in zip(actual, predicted) + if a == positive_label and p == positive_label + ) + fn = sum( + 1 + for a, p in zip(actual, predicted) + if a == positive_label and p != positive_label + ) return tp / (tp + fn) if (tp + fn) > 0 else 0.0