Accuracy, F1, micro-F1, and macro-F1
Imagine a model routes eight bank transactions to normal, fraud, or review. Five predictions are correct.
1. Begin with the confusion matrix
Section titled “1. Begin with the confusion matrix”Rows are truth. Columns are predictions.
| Actual ↓ / Predicted → | normal | fraud | review | Support |
|---|---|---|---|---|
| normal | 3 | 1 | 0 | 4 |
| fraud | 1 | 1 | 0 | 2 |
| review | 1 | 0 | 1 | 2 |
The diagonal contains the five correct predictions. Every off-diagonal cell is a particular mistake that a product owner can name.
2. Accuracy asks one broad question
Section titled “2. Accuracy asks one broad question”accuracy = correct predictions / all predictions = 5 / 8 = 0.625Accuracy is easy to explain. It becomes misleading when one common class dominates. A system can label every rare fraud case normal and still look accurate in a mostly normal dataset.
3. Precision and recall choose one class
Section titled “3. Precision and recall choose one class”Treat fraud as positive and everything else as negative.
| Count | Meaning | Here |
|---|---|---|
| TP | Fraud correctly predicted as fraud | 1 |
| FP | Non-fraud incorrectly predicted as fraud | 1 |
| FN | Fraud incorrectly predicted as something else | 1 |
fraud precision = TP / (TP + FP) = 1 / 2 = 0.50fraud recall = TP / (TP + FN) = 1 / 2 = 0.50Precision asks, “When the system said fraud, how often was it right?” Recall asks, “Of the fraud cases that existed, how many did it find?” The denominator is the difference.
4. F1 requires both
Section titled “4. F1 requires both”F1 = 2 × precision × recall / (precision + recall) = 2TP / (2TP + FP + FN)For fraud, F1 is 0.50. A high precision cannot fully hide low recall, and high recall cannot fully hide low precision. F1 still does not include true negatives, probability calibration, or the different business cost of each error.
5. Micro, macro, and weighted answer different questions
Section titled “5. Micro, macro, and weighted answer different questions”First calculate F1 for each class by treating that class as positive.
| Class | Precision | Recall | F1 | Support |
|---|---|---|---|---|
| normal | 0.60 | 0.75 | 0.667 | 4 |
| fraud | 0.50 | 0.50 | 0.500 | 2 |
| review | 1.00 | 0.50 | 0.667 | 2 |
| Average | Calculation | Result | What it emphasizes |
|---|---|---|---|
| Micro-F1 | Add every class’s TP, FP, and FN, then calculate one F1 | 0.625 | Every sample-class decision |
| Macro-F1 | (0.667 + 0.500 + 0.667) / 3 |
0.611 | Every class equally |
| Weighted-F1 | Weight each class F1 by support: (4×.667 + 2×.500 + 2×.667) / 8 |
0.625 | Common classes more heavily |
For a single-label multiclass problem that includes every class, micro-precision, micro-recall, micro-F1, and accuracy are equal. This shortcut does not generally hold for multilabel classification, excluded labels, or unusual weighting.
Which score should I report?
Section titled “Which score should I report?”| Situation | Start with | Also inspect |
|---|---|---|
| Balanced classes and similar error costs | Accuracy | Per-class precision, recall, and confusion matrix |
| Rare classes matter | Macro-F1 | Rare-class recall and false-negative cases |
| Dataset frequency should affect the summary | Weighted-F1 | Macro-F1 so minority failure is not hidden |
| Missing positives is expensive | Recall for the important class | Precision, threshold curve, and cost of false positives |
| False alarms are expensive | Precision for the important class | Recall and the cases rejected incorrectly |
| One item may have several labels | Micro/macro/sample averages | Exact-match or subset accuracy |
No average decides the product trade-off for you. In loan, fraud, medical, or safety workflows, report the important class separately and evaluate the human-review route too.
Run the numbers
Section titled “Run the numbers”from collections import Counter
TRUTH = ["normal", "normal", "normal", "normal", "fraud", "fraud", "review", "review"]PREDICTED = ["normal", "normal", "normal", "fraud", "normal", "fraud", "review", "normal"]LABELS = ["normal", "fraud", "review"]
def safe_divide(numerator: int, denominator: int) -> float: return numerator / denominator if denominator else 0.0
def per_class_scores(truth: list[str], predicted: list[str]) -> dict[str, dict[str, float]]: scores = {} for label in LABELS: tp = sum(actual == label and guess == label for actual, guess in zip(truth, predicted)) fp = sum(actual != label and guess == label for actual, guess in zip(truth, predicted)) fn = sum(actual == label and guess != label for actual, guess in zip(truth, predicted)) precision = safe_divide(tp, tp + fp) recall = safe_divide(tp, tp + fn) f1 = safe_divide(2 * tp, 2 * tp + fp + fn) scores[label] = {"tp": tp, "fp": fp, "fn": fn, "precision": precision, "recall": recall, "f1": f1} return scores
scores = per_class_scores(TRUTH, PREDICTED)support = Counter(TRUTH)accuracy = sum(actual == guess for actual, guess in zip(TRUTH, PREDICTED)) / len(TRUTH)macro_f1 = sum(item["f1"] for item in scores.values()) / len(LABELS)weighted_f1 = sum(scores[label]["f1"] * support[label] for label in LABELS) / len(TRUTH)total_tp = sum(item["tp"] for item in scores.values())total_fp = sum(item["fp"] for item in scores.values())total_fn = sum(item["fn"] for item in scores.values())micro_f1 = safe_divide(2 * total_tp, 2 * total_tp + total_fp + total_fn)
print(f"accuracy: {accuracy:.3f}")print(f"micro F1: {micro_f1:.3f}")print(f"macro F1: {macro_f1:.3f}")print(f"weighted F1: {weighted_f1:.3f}")for label, item in scores.items(): print(f"{label:>6}: P={item['precision']:.3f} R={item['recall']:.3f} F1={item['f1']:.3f} support={support[label]}")
assert round(accuracy, 3) == 0.625assert micro_f1 == accuracy # True for this single-label multiclass task when every class is included.assert round(macro_f1, 3) == 0.611assert round(weighted_f1, 3) == 0.625In scikit-learn, use classification_report for the table and f1_score(y_true, y_pred, average="micro" | "macro" | "weighted") for a chosen average.1
A final memory aid
Section titled “A final memory aid”- Accuracy: How many rows were right?
- Precision: When I predicted this class, how often was I right?
- Recall: Of this class in reality, how much did I find?
- F1: Did precision and recall remain strong together?
- Micro: Pool decisions first.
- Macro: Score classes first, then give each one equal weight.
- Weighted: Score classes first, then weight by how often each occurs.
Footnotes
Section titled “Footnotes”-
scikit-learn,
f1_scoreand classification metrics. ↩