Classification Metrics and Decision Thresholds › Example
Machine Learning from ScratchAll chapters Classification Metrics and Decision Thresholds › ExampleSection view · Sections in this chapter6 / 10 ↓
Reading tools Definitions, links & notes
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
The simulated data shows the machinery at scale, but its numbers arrive from code. A tiny hand-worked case shows where each number comes from and makes the formulas concrete. Sixteen accounts have been scored, four of them are fraudulent, and the scores are sorted from highest to lowest. Every number below is small enough for you to check with a calculator.
| rank | score | rank | score | ||
|---|---|---|---|---|---|
| 1 | 0.94 | 1 | 9 | 0.31 | 0 |
| 2 | 0.88 | 1 | 10 | 0.27 | 0 |
| 3 | 0.81 | 0 | 11 | 0.21 | 0 |
| 4 | 0.70 | 1 | 12 | 0.16 | 0 |
| 5 | 0.62 | 0 | 13 | 0.11 | 0 |
| 6 | 0.58 | 0 | 14 | 0.07 | 0 |
| 7 | 0.45 | 1 | 15 | 0.04 | 0 |
| 8 | 0.38 | 0 | 16 | 0.01 | 0 |
There are positives and negatives, so the prevalence is . The costs are dollars for reviewing an account that turns out to be clean and dollars for missing a fraudulent one. In other words, each false positive costs 5 dollars and each false negative costs 150 dollars.
Start at the default threshold . The rule flags the first six accounts, since their scores are at least 0.50. Of those six, three are fraudulent (ranks 1, 2, 4) and three are not (ranks 3, 5, 6). The one remaining fraud sits at rank 7 with a score of 0.45, so it is missed. These four counts are exactly the four cells of the confusion matrix, and they are complete: every account appears once.
The metrics follow mechanically. Precision is , recall is , the false positive rate is , and , which the count form confirms: . The expected cost per account is
Now lower the threshold to 0.45, which adds rank 7 to the alert queue. Nothing else changes, because no other score lies between 0.45 and 0.50. The confusion matrix becomes
so precision rises to , recall rises to , the false positive rate is unchanged at , and .
where:
- : the number of flagged frauds after including rank 7
- : the number of flagged clean accounts (ranks 3, 5, 6)
- : no fraud remains unflagged
- : the number of unflagged clean accounts
The cost drops to
Recall rises and precision also rises here because the one added alert is a true positive, which is the opposite of the general tendency: we noted earlier that raising the threshold from 0.45 to 0.50 reduced precision from 4/7 to 1/2 while reducing recall. When moving toward lower thresholds, adding a true positive can lift both metrics, while adding a false positive leaves recall unchanged and lowers precision when the queue already contains a true positive. This example shows why precision need not move in the opposite direction from recall. The cost, however, falls by more than a factor of ten. Multiplying this observed difference by a hypothetical 10,000 accounts gives $93,750. Interpreting that as a future daily saving would additionally require the example error rates and stated costs to transfer to the future stream.
The threshold need not be exactly 0.45. Any value in the interval between the seventh and eighth scores, , produces the same predictions and therefore the same cost. This is the cost plateau of the previous section, and it means the reported threshold should be understood as a representative of an interval. The midpoint rule used in the code would report 0.415.
Next we check the curve summaries against the same sixteen points. Ordering the positives by rank, the true positive count after each is at ranks , so recall takes the values 0.25, 0.50, 0.75, and 1.00 with precisions 1/1, 2/2, 3/4, and 4/7. Average precision sums the recall increments weighted by the precision achieved:
For the AUC we count, for each positive, how many negatives it outranks. Rank 1 outranks all twelve, rank 2 also outranks all twelve, rank 4 outranks the eleven negatives below it, and rank 7 outranks the nine below it:
The trapezoidal area under the same precision-recall points is a different number. The horizontal segments contribute and , the segment from recall 0.50 to 0.75 connects precision 2/3 to 3/4, and the segment from 0.75 to 1.00 connects precision 1/2 to 4/7:
The two do not agree in this case; the trapezoid differs from average precision. Here average precision is about 0.8304 and the trapezoidal area about 0.8110, a difference of . The definitions can sometimes yield the same number, even when precision is not constant, because segment differences can cancel. Name the convention rather than treating the two definitions as interchangeable.
Finally, the closed form for calibrated scores gives . Applying it here would flag every account scoring at least 0.0323, which is ranks 1 through 15, giving and for a cost of . The toy scores are used here as an arbitrary ranking, so we have not established the conditional-probability assumption for that cutoff. Its observed cost is larger than the empirical optimum of 0.9375. That finite-sample comparison alone would not prove miscalibration: a sample-selected rule can also beat a population-optimal rule on the sample used to select it.
We close the example by checking the hand arithmetic against the code, which is the habit that keeps your worked examples honest.
ConsoleOut [17]Expand
threshold TP FP FN TN precision recall F1 cost
0.5000 3 3 1 9 0.5000 0.75 0.6000 10.3125
0.4500 4 3 0 9 0.5714 1.00 0.7273 0.9375
0.0323 4 11 0 1 0.2667 1.00 0.4211 3.4375ConsoleOut [18]Expand
AUC (rank/trapezoid) : 0.9167 average precision : 0.8304 PR trapezoid area : 0.8110 closed-form threshold: 0.0323
The preceding code checks the hand calculation against the same definitions. Agreement on this example is a useful implementation check, rather than proof that every later use is correct.
The four counts at the default threshold can be read as one grid. This grid summarizes the aggregate unweighted binary outcomes at that cut, before any ratio such as precision or recall is formed.
PythonIn [19]Expand
cm_default = confusion_counts(toy_label, toy_score, 0.50)
cm_names = [["TN", "FP"], ["FN", "TP"]]
cm_annotations = [
[f"{cm_names[i][j]}={int(cm_default[i, j])}" for j in range(2)]
for i in range(2)
]VisualizationOut [20]Expand

Comments
Add to the conversation
A question, a correction, or an example from your own work.
No comments yet. Be the first to share your thoughts!