Why macro F1 measure can't be calculated from macro precision and recall?

Viewed 392

I'm interested in calculating macro f1-score by macro precision and recall manually. But the results aren't equal. What is the difference in the final formula between f1 and f1_new in code?

from sklearn.metrics import precision_score, recall_score, f1_score

y_true = [0, 1, 0, 1, 0 , 1, 1, 0]
y_pred = [0, 1, 0, 0, 1 , 1, 0, 0]

p = precision_score(y_true, y_pred, average='macro')
r = recall_score(y_true, y_pred, average='macro')
f1_new = (2 * p * r) / (p + r) # 0.6291390728476821

f1 = f1_score(y_true, y_pred, average='macro') #  0.6190476190476191

print(f1_new == f1) 
# False
1 Answers

The f1_score is calculated in scikit-learn as follows:

all_positives = 4
all_negatives = 4
true_positives = 2
true_negatives = 3

true_positive_rate = true_positives/all_positives = 2/4
true_negative_rate = true_negatives/all_negatives = 3/4

pred_positives = 3
pred_negatives = 5

positive_predicted_value = true_positives/pred_positives = 2/3
negative_predicted_value = true_negatives/pred_negatives = 3/5

f1_score_pos = 2 * true_positive_rate * positive_predicted_value / (true_positive_rate + positive_predicted_value)
             = 2 * 2/4 * 2/3 / (2/4 + 2/3)

f1_score_neg = 2 * true_negative_rate * negative_predicted_value / (true_negative_rate + negative_predicted_value)
             = 2 * 3/4 * 3/5 / (3/4 + 3/5)

f1 = average(f1_score_pos, f1_score_neg)
   = 2/4 * 2/3 / (2/4 + 2/3) + 3/4 * 3/5 / (3/4 + 3/5)
   = 0.6190476190476191

This matches the definition given in the documentation for the 'macro' parameter of skicit-learn's f1_score: Calculate metrics for each label, and find their unweighted mean. This definition also applies to precision_score and recall_score.

Your manual calculation of the F1-score is as follows:

precision = average(positive_predicted_value, negative_predicted_value)
          = average(2/3, 3/5)
          = 19/30

recall = average(true_positive_rate, true_negative_rate)
       = average(2/4, 3/4)
       = 5/8

f1_new = 2 * precision * recall / (precision + recall)
       = 2 * 19/30 * 5/8 / (19/30 + 5/8)
       = 0.6291390728476821

In fact, the general formula F1 = 2 * (precision * recall) / (precision + recall) as presented in the docs is only valid for average='binary' and average='micro', but not for average='macro' and average='weighted'. In that sense, as it is currently presented in scikit-learn, the formula is misleading as it suggests that it holds irrespective of the chosen parameters, which is not the case.

Related