Sam Austinon October 24, 202614 min read

Weighted vs Macro vs Micro F1: Which Average Should You Use?

Weighted vs Macro vs Micro F1: Which Average Should You Use?
Contents

Figure 1: Weighted vs Macro vs Micro F1 — the same predictions, three different averages, three different stories

Your multiclass model reports an F1 of 0.94, and your team celebrates. Then someone checks the smallest class and finds an F1 of 0.29. How did a score that high hide a result that bad?

The answer lives in one scikit-learn argument: average. This guide explains micro, macro, and weighted F1, shows how each one works, and tells you when to use which. I ran every example, so the numbers below are real.

A Quick Refresher on F1

The F1 score combines precision and recall into one number. It equals the harmonic mean of the two, so a model needs both to score well. Precision asks how many flagged cases were correct, and recall asks how many real cases the model caught.

F1 works naturally for binary problems. Multiclass problems add a twist, because you now have one F1 score per class. Somebody has to boil those scores down to a single number, and that is where the three averages come in.

Set Up a Small Example

Let us build a three-class problem with heavy imbalance. Class 0 has 900 samples, class 1 has 80, and class 2 has just 20. I wrote the results as a confusion matrix, where rows show true classes and columns show predictions.

import numpy as np
from sklearn.metrics import f1_score, classification_report

cm = np.array([[880, 15, 5],
               [ 15, 60, 5],
               [ 10,  5, 5]])

y_true, y_pred = [], []
for i in range(3):
    for j in range(3):
        y_true += [i] * cm[i, j]
        y_pred += [j] * cm[i, j]

y_true, y_pred = np.array(y_true), np.array(y_pred)

The loop converts the matrix back into label arrays. Now we can ask scikit-learn for each score. First, let us look at the per-class results.

print(f1_score(y_true, y_pred, average=None))

The per-class F1 scores come out at 0.975, 0.750, and 0.286. The big class does great, the middle class does fine, and the tiny class does poorly. Keep those three numbers in mind, because the averages will tell very different stories about them.

Class True samples Precision Recall F1
0 900 0.972 0.978 0.975
1 80 0.750 0.750 0.750
2 20 0.333 0.250 0.286

Macro F1: Every Class Counts Equally

Macro F1 computes the F1 for each class and takes a plain average. Big classes and small classes carry the same weight. A class with 20 samples matters exactly as much as a class with 900.

print(f1_score(y_true, y_pred, average='macro'))   # 0.6703

Our macro F1 equals 0.670, which is the average of 0.975, 0.750, and 0.286. That number reflects the weak minority class honestly. It tells you right away that something in your model needs attention.

When Macro F1 Shines

Macro F1 works best when every class matters equally, no matter how rare. Think of rare disease detection or fraud types, where the small classes carry the highest stakes. It also punishes models that ignore minority classes, which makes it a strict but useful judge.

Weighted F1: Bigger Classes Count More

Weighted F1 also computes the F1 per class, but it weights each score by the class's support, meaning its number of true samples. Large classes dominate the average. The result tracks overall performance on typical data.

print(f1_score(y_true, y_pred, average='weighted'))   # 0.9433

Our weighted F1 equals 0.943. The big class holds 90% of the samples, so its excellent 0.975 pulls the average way up. The weak 0.286 for the tiny class barely moves the needle.

That is exactly how a team ends up celebrating a 0.94 while one class fails. Weighted F1 answers the question "how well does the model do on a randomly drawn sample?" It does not answer "how well does it serve every class?"

When Weighted F1 Shines

Weighted F1 fits problems where class frequency reflects real-world importance. If you care mostly about typical cases, and rare classes truly matter less, it gives a sensible summary. It also stays more stable than macro F1 when tiny classes make noisy estimates.

Micro F1: Pool Every Prediction

Micro F1 skips the per-class step. It adds up the true positives, false positives, and false negatives across all classes, then computes one global F1. Every sample counts equally, not every class.

print(f1_score(y_true, y_pred, average='micro'))   # 0.945

Our micro F1 equals 0.945, which matches the plain accuracy exactly. That is no coincidence. For single-label multiclass problems, each wrong prediction counts as one false positive for one class and one false negative for another. So micro precision, micro recall, micro F1, and accuracy all land on the same number.

This equality breaks in multilabel problems, where one sample can carry several labels. There, micro F1 differs from accuracy and becomes genuinely useful.

When Micro F1 Shines

Micro F1 suits multilabel tasks, and it suits single-label problems where you care about overall sample-level performance. If you already report accuracy, micro F1 adds nothing new for single-label data. In that case, pick macro or weighted instead.

Side-by-Side Comparison

Here are all three averages on our example:

Average Score What counts
Micro F1 0.945 every sample equally
Weighted F1 0.943 every class in proportion to its size
Macro F1 0.670 every class equally

Notice the huge gap. Micro and weighted both land above 0.94, while macro drops to 0.67. The gap itself carries information: a large distance between macro and weighted F1 warns you that minority classes struggle.

Compute All Three by Hand

Understanding beats memorising. You can reproduce all three averages from the confusion matrix with a few NumPy lines. I confirmed that these match scikit-learn.

tp = np.diag(cm)
fp = cm.sum(axis=0) - tp
fn = cm.sum(axis=1) - tp
support = cm.sum(axis=1)

precision = tp / (tp + fp)
recall = tp / (tp + fn)
f1 = 2 * precision * recall / (precision + recall)

macro = f1.mean()
weighted = (f1 * support).sum() / support.sum()

mp = tp.sum() / (tp.sum() + fp.sum())
mr = tp.sum() / (tp.sum() + fn.sum())
micro = 2 * mp * mr / (mp + mr)

The code shows the difference clearly. Macro averages the per-class scores directly, and weighted multiplies them by support first. Micro pools the raw counts before it ever computes a score.

What Happens When a Class Gets Ignored

Let us break the model on purpose. I built a second confusion matrix where the model never predicts class 2. Every sample in that class gets mislabelled as another class.

The per-class F1 for class 2 fell to 0.000. Here is how each average responded:

Average Before After abandoning class 2 Direction
Micro F1 0.945 0.950 went up
Weighted F1 0.943 0.940 barely moved
Macro F1 0.670 0.583 dropped sharply

That result should make you pause. A model that completely abandons one class scored a higher micro F1 than before. Only macro F1 sounded the alarm.

Scikit-learn also warns you when a class gets no predictions. Pass zero_division=0 to silence the warning and set those scores to zero deliberately. I prefer to read the warning and investigate.

Which Average Should You Use?

Match your choice to your goal:

  1. Every class matters equally: use macro F1. It exposes weak minority classes.
  2. Class frequency reflects real importance: use weighted F1. It summarises typical performance.
  3. Multilabel classification: use micro F1. It pools every label decision.
  4. Single-label with overall accuracy in mind: report accuracy, since micro F1 duplicates it.
  5. Not sure: report macro and weighted together and look at the gap.

No single average tells the whole story. Reporting macro F1 next to weighted F1 takes one extra line, and it catches the exact problem that opened this article.

Always Look at Per-Class Scores Too

Averages compress information, and compression loses details. Call classification_report to see precision, recall, and F1 for every class at once, plus all the averages.

print(classification_report(y_true, y_pred, digits=3))

That report shows our class 2 with a precision of 0.333 and a recall of 0.250. Those two numbers explain the weak F1: the model finds only a quarter of the true cases, and two-thirds of its flags are wrong. A single average could never tell you that.

Common Pitfalls

I see these mistakes constantly:

  1. Reporting only weighted F1 on imbalanced data. It hides minority class failures.
  2. Treating micro F1 as new information. In single-label problems, it equals accuracy.
  3. Skipping per-class scores. Averages hide which class struggles.
  4. Ignoring zero-division warnings. They signal a class that the model never predicts.
  5. Comparing averages across different class mixes. Weighted scores shift when class proportions change.
  6. Quoting a macro F1 computed over classes absent from the test split. scikit-learn counts labels it has seen in y_true or y_pred; a class missing from both silently disappears from the average.
  7. Reporting three decimals on a 20-sample class. A single flipped prediction moves that class's F1 by several points.

Best Book to Read

CoverBookDescriptionGet it
Cover of “Machine Learning for Imbalanced Data” Machine Learning for Imbalanced Databy Kumar Abhishek and Mounir Morcos covers exactly the reporting problem in this post: which metric reflects minority-class performance, and why a single aggregate number can hide a failed class. View on Amazon

As an Amazon Associate I earn from qualifying purchases. Links on this page are affiliate links, and I may earn a commission at no extra cost to you.

Wrapping Up

The three averages answer three different questions. Micro F1 asks how well the model does per sample, weighted F1 asks how well it does on typical data, and macro F1 asks how well it serves every class. Pick the question that matches your real goal.

So open your latest classification report and compare macro against weighted. If a big gap appears, you just found a class that needs attention. That ten-second check beats a lot of celebration over a flattering number.

If the operating point matters as much as the average, the threshold guide shows how to move it deliberately, and the classification recipes put this reporting inside a full workflow. When coefficients rather than scores are the question, the collinearity guide covers what a feature's effect can and cannot tell you.

Frequently Asked Questions

What is the difference between macro, micro and weighted F1?

Macro F1 computes F1 per class and averages the scores with equal weight, so a class with 20 rows counts as much as one with 900. Weighted F1 averages the same scores but weights each by class support, so large classes dominate. Micro F1 pools true positives, false positives and false negatives across all classes first, then computes one score, which makes every sample count equally.

Why does micro F1 equal accuracy for single-label multiclass problems?

Because every misclassification produces exactly one false positive for the predicted class and one false negative for the true class, so pooled precision and recall both reduce to the fraction of correctly classified samples. Micro F1 is then the harmonic mean of a number with itself, which is that same number. The equality breaks for multilabel data, where one sample can contribute several label decisions.

Which F1 average should I report on imbalanced data?

Report macro and weighted side by side, and look at the gap. Macro exposes a class the model has abandoned, as in the example where dropping a whole class raised micro F1 to 0.950 but pushed macro down to 0.583. Weighted alone would have shown 0.940 and hidden the failure. No single average answers both questions, and the gap between them is itself the diagnostic.

Why does scikit-learn warn about zero division in f1_score?

The warning means at least one class received no predictions, so its precision or F1 is undefined. Passing zero_division=0 silences it and sets those scores to zero. Do not silence it reflexively: the warning is often the first signal that a model has stopped predicting an entire class, which is exactly the failure the averages are supposed to surface.

When is micro F1 genuinely useful?

In multilabel classification, where each sample has several independent label decisions and pooling them changes the number. There micro F1 differs from any accuracy figure you could quote, and it reflects overall label-level performance. For single-label multiclass problems it adds nothing beyond accuracy, so spend the reporting slot on macro or weighted instead.

Can weighted F1 hide a broken class?

Yes, and that is its main weakness. With 900 of 1000 samples in one class, that class contributes 90% of the weighted average, so a minority class scoring 0.286 moves the total by about half a point. A model that never predicts the small class can still report 0.940 weighted F1. Always read the per-class report before trusting the average.

Sources

What are You Looking For?

esc