Classification Metrics and Decision Thresholds › Example

Michael BrenndoerferOctober 24, 20268 min read
Machine Learning from ScratchAll chapters
Part 4 of 15Model Evaluation and Selection
Chapter 32Classification Metrics and Decision Thresholds › Example
Reading section 6 of 10 · Example · 8 minSection 6 of 10 · 8 min

Section 6 of 10 · 8 min read

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

Notes & comments

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.

Sixteen scored accounts sorted by risk score with binary labels.
rankscoreyyrankscoreyy
10.94190.310
20.881100.270
30.810110.210
40.701120.160
50.620130.110
60.580140.070
70.451150.040
80.380160.010

There are N1=4N_1 = 4 positives and N0=12N_0 = 12 negatives, so the prevalence is π=4/16=0.25\pi = 4/16 = 0.25. The costs are cFP=5c_{FP} = 5 dollars for reviewing an account that turns out to be clean and cFN=150c_{FN} = 150 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 t=0.50t = 0.50. 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.

C(0.50)=[TNFPFNTP]=[9313].\mathbf{C}(0.50) = \begin{bmatrix} TN & FP \\ FN & TP \end{bmatrix} = \begin{bmatrix} 9 & 3 \\ 1 & 3 \end{bmatrix}.

The metrics follow mechanically. Precision is 3/(3+3)=0.5003/(3+3) = 0.500, recall is 3/(3+1)=0.7503/(3+1) = 0.750, the false positive rate is 3/(3+9)=0.2503/(3+9) = 0.250, and F1=2×0.5×0.75/(0.5+0.75)=0.600F_1 = 2 \times 0.5 \times 0.75/(0.5 + 0.75) = 0.600, which the count form confirms: 2×3/(2×3+3+1)=6/102 \times 3/(2 \times 3 + 3 + 1) = 6/10. The expected cost per account is

C^(0.50)=5×3+150×116=16516=10.3125.\hat{C}(0.50) = \frac{5 \times 3 + 150 \times 1}{16} = \frac{165}{16} = 10.3125.

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

C(0.45)=[9304],\mathbf{C}(0.45) = \begin{bmatrix} 9 & 3 \\ 0 & 4 \end{bmatrix},

so precision rises to 4/7≈0.5714/7 \approx 0.571, recall rises to 4/4=1.0004/4 = 1.000, the false positive rate is unchanged at 3/12=0.2503/12 = 0.250, and F1=2×4/(2×4+3+0)=8/11≈0.727F_1 = 2 \times 4/(2 \times 4 + 3 + 0) = 8/11 \approx 0.727.

where:

  • TP=4TP = 4: the number of flagged frauds after including rank 7
  • FP=3FP = 3: the number of flagged clean accounts (ranks 3, 5, 6)
  • FN=0FN = 0: no fraud remains unflagged
  • TN=9TN = 9: the number of unflagged clean accounts

The cost drops to

C^(0.45)=5×3+150×016=1516=0.9375.\hat{C}(0.45) = \frac{5 \times 3 + 150 \times 0}{16} = \frac{15}{16} = 0.9375.

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, t∈(0.38,0.45]t \in (0.38, 0.45], 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 1,2,3,41, 2, 3, 4 at ranks 1,2,4,71, 2, 4, 7, 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:

AP=14 ⁣(1+1+34+47)=12+316+17=93112≈0.8304.\begin{aligned} \text{AP} &= \tfrac14\!\left(1+1+\tfrac34+\tfrac47\right) \\ &= \tfrac12+\tfrac{3}{16}+\tfrac17 \\ &= \tfrac{93}{112} \approx 0.8304. \end{aligned}

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:

AUC^=12+12+11+912×4=4448≈0.9167.\widehat{\text{AUC}} = \frac{12 + 12 + 11 + 9}{12 \times 4} = \frac{44}{48} \approx 0.9167.

The trapezoidal area under the same precision-recall points is a different number. The horizontal segments contribute 0.25×10.25 \times 1 and 0.25×10.25 \times 1, 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:

PR^trap=0.25+0.25+0.25 ⁣(2/3+3/42)+0.25 ⁣(1/2+4/72)≈0.8110.\widehat{\text{PR}}_{\text{trap}} = 0.25 + 0.25 + 0.25\!\left(\tfrac{2/3 + 3/4}{2}\right) + 0.25\!\left(\tfrac{1/2 + 4/7}{2}\right) \approx 0.8110.

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 13/672≈0.019313/672\approx0.0193. 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 t∗=5/(5+150)≈0.0323t^{*} = 5/(5+150) \approx 0.0323. Applying it here would flag every account scoring at least 0.0323, which is ranks 1 through 15, giving TP=4TP = 4 and FP=11FP = 11 for a cost of (5×11+150×0)/16=3.4375(5 \times 11 + 150 \times 0)/16 = 3.4375. 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]
CollapseExpand
 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.4375
ConsoleOut [18]
CollapseExpand
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]
CollapseExpand
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]
CollapseExpand
Four-cell grid with TN 9, FP 3, FN 1, TP 3 at the default threshold.
Confusion matrix for the sixteen-account example at threshold 0.50. Rows are actual class and columns predicted class. The counts are TN 9, FP 3, FN 1 and TP 3, giving precision 0.500 and recall 0.750.

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!

Newsletter

Stay up to date

Get new articles, book updates, and notes on AI and technology.

Unsubscribe at any time. Privacy policy