← Part of Lysen · lysen.org
Lysen ML
← All algorithms Shared · classification

Evaluation and comparison

The problem it solves: you have three classifiers that all say they are about 93 percent accurate, and you have to pick one. This page is the two things that question needs. First, how to score a classifier so the number means something, because accuracy on its own routinely does not. Then a straight comparison of K-nearest neighbours, the decision tree and logistic regression across the things that actually decide the choice.

Why evaluation comes first: the comparison table has a row for probability quality, and that row is meaningless until you have seen a threshold move. So the scoring comes first and the table pays it off. If you only want the table, the second tab is right there.

Shared by K-nearest neighboursdecision treeslogistic regressionanything else that predicts a label

Running example for the whole evaluation section: a screening test for a condition, run on 600 people. Each person arrives as two numbers, fasting glucose in mg/dL and BMI, and either has the condition or does not. About one in six do, which is the ordinary state of screening data and the reason accuracy is about to embarrass itself. Seventy percent of the people are used for fitting and the rest are held back for scoring. Everything is generated in your browser from a seed, and the three classifiers are fitted live on it, so every number below came out of a model that actually ran.

01

Accuracy

A classifier makes a prediction for every person. Some of those predictions are right. Accuracy is the share that are right, and nothing more complicated than that.

accuracy = correct predictions ÷ total predictions

Below is a screening population split evenly, half with the condition and half without, and a logistic regression fitted on it. Each dot is one person. The line is where the model changes its mind.

has the condition does not the model's boundary
…accuracy on people it has never seen …correct out of the held-out group
…

One number, easy to explain to anybody, and it goes up when the model gets better. It is the first thing every library prints and the first thing every tutorial reports, and on data shaped like this it is a perfectly reasonable summary.

Hold onto that. It is about to stop working, and it matters that you accepted it as reasonable first, because the failure is not that accuracy is a silly idea. The failure is that it is a reasonable idea applied to data it does not suit, which is a much harder thing to notice.

02

Why accuracy breaks

Two separate failures. The first is famous and easy to demonstrate. The second is quieter, survives every fix for the first, and is the one that actually decides which model you should ship.

2a One class is much rarer than the other

The even split above was a convenience. Real screening data is not like that: most people do not have the condition. So take the same generator and slide the population from an even split towards the lopsided one you would actually meet.

Two models are running. One is the fitted logistic regression. The other is a model of studied stupidity: it always says "no condition", for everyone, forever. It has no features, no fitting and no thought in it at all.

The population at50% without the condition

What each model scores

…accuracy of the fitted model …accuracy of "nobody has it" …cases the fitted model actually catches
…
has the condition does not the fitted model "nobody has it"

Take the slider to 99 and read the two numbers next to each other. The model that has learned nothing scores 99 percent. Not because it is good. Because 99 percent of the answers are "no", and it says "no".

This is the trap in one line: on lopsided data, accuracy measures the lopsidedness. A published accuracy figure with no class balance beside it is not a result, it is a number, and you cannot tell the two apart from the outside. Whenever you see one, the first question is always what the majority class would have scored on its own.

2b The two mistakes are not worth the same

Now the deeper problem, and the reason rebalancing your data does not fix everything. Accuracy adds the two kinds of mistake together as though they were interchangeable. They almost never are.

A screening test can be wrong in two directions. It can raise a false alarm, sending somebody healthy for a confirmatory test they did not need, which costs an appointment and a bad week. Or it can miss a case, telling somebody who has the condition that they are fine, which costs them the months before anybody looks again.

Here are two settings of the same fitted model on the same held-out people. They score within a percentage point of each other on accuracy. They are not remotely the same test.

Both lines start from the same accuracy. Only the mix of mistakes differs.

…cautious test, total cost …strict test, total cost
…
TestAccuracyFalse alarmsMissed cases
Cautiousflags anything doubtful………
Strictflags only the obvious………

The strict test is the more accurate of the two, by one person. It also misses several times as many cases. Push the slider one notch past 1, which is to say assume that missing a case is worse than a false alarm at all, and the ranking flips and never flips back.

Note what this failure does not depend on. It is not about imbalance: it would happen on a perfectly even split. Rebalancing your classes does not touch it. Accuracy is throwing away the distinction between the two mistakes at the moment it adds them up, and once they are added there is no way to get them back out.

So we need a score that refuses to add them up. That is the next step, and it is the last new object on this page. Everything after it is a reading taken off it.

03

The confusion matrix

The fix is to stop summarising and start counting. There are four things that can happen to one person, so count all four separately and never add them together again. Laid out as a two-by-two grid, that is the confusion matrix.

Below is the running dataset: … people held back from fitting, of whom … have the condition. Every one of them is a dot, placed left to right by the probability the model gives them. People who actually have the condition sit above the line, people who do not sit below it.

Drag the vertical threshold. Everything to the right of it the model is calling positive. The four quadrants that makes are the four cells of the matrix, which is the entire idea.

has the condition does not your threshold

Drag anywhere on the plot to move the threshold. It is the same threshold in step 4, so what you set here is what you will find there.

model says positive model says negative actually positive …TRUE POSITIVEhad it, and was flagged …FALSE NEGATIVEhad it, and was sent home actually negative …FALSE POSITIVEdid not have it, and was flagged anyway …TRUE NEGATIVEdid not have it, and was correctly cleared
…

Two naming traps, because beginners swap these constantly and so does everybody else. The first word says whether the model was right, and the second word says what the model predicted. A false negative is a negative prediction that was false, so it is a missed case. A true negative is a negative prediction that was true, so it is a healthy person correctly cleared. Nothing in the word "negative" refers to the person's actual state, only to what the model said about them.

Now read the diagonal. Accuracy is the two green cells added together and divided by all four, which is why it can be so high here: the true negative cell is enormous, because most people do not have the condition, and it drowns out everything else. The matrix does not hide that. It puts the number you should be worried about, the missed cases, in a box of its own where it cannot be averaged away.

Press "new people" and the counts all change. What does not change is the shape of the problem: a fat true negative cell, and a missed-case cell that stubbornly refuses to reach zero unless you accept a pile of false alarms. That shape is a property of the data, not of the draw.

04

Precision and recall

Two numbers, both read straight off the four cells you already have. They are not new information. They are the two questions worth asking about that grid, and each one deliberately ignores a different cell.

precision= true positives ÷ everything the model flagged // TP ÷ (TP + FP)
recall = true positives ÷ everyone who actually has it // TP ÷ (TP + FN)
  • Precision looks at the flagged column. Of everybody this test sent for a second appointment, what share genuinely needed one? Low precision means the clinic is full of people who were never at risk. Precision is the cost of false alarms.
  • Recall looks at the positive row. Of everybody who actually has the condition, what share did the test find? Low recall means people went home ill and unaware. Recall is the cost of misses.

The mnemonic worth keeping, because this is where everybody gets lost: precision is judged by the model's own claims, recall is judged by reality. Precision asks whether the model's flags were honest. Recall asks whether reality was covered.

Same widget, same threshold, same people. Now watch the two numbers as you drag.

has the condition does not your threshold
precision…
recall…
accuracy…
…

Drag it all the way right. Precision goes to a perfect 1.00, because the only people still being flagged are the ones the model is certain about, and it is right about them. Recall collapses, because almost nobody is being flagged at all. A test that is never wrong when it speaks, and almost never speaks.

They move in opposite directions, always, and that is not a defect of this model or this dataset. Raising the threshold can only ever remove flags. Removing flags can only ever make the flagged group purer and smaller. Purer is higher precision, smaller is lower recall. There is no setting where both are at their maximum, which is what makes this a genuine tradeoff rather than a tuning problem with a right answer waiting to be found.

Drag the threshold from one end to the other and every pair of values you pass through traces a curve. That curve is the whole tradeoff in one picture.

…precision and recall at your threshold …precision a coin flip would get

Every point on that line is one threshold. The marker is yours. Sliding along the curve is free, in the sense that it costs no data and no retraining: it is a decision, not a result.

The flat dashed line is the precision you would get by flagging everybody, which is just the share of people who have the condition. Any curve that sags to it has stopped being useful, and on lopsided data that baseline is low enough that a bad model can look impressive against nothing.

05

F1, and the average that does not work

Two numbers are harder to rank than one, so people want them combined. The obvious way to combine them is to take the average, and the obvious way is wrong in a way that is easiest to see by breaking it on purpose.

Set precision to 1.00 and recall to 0.00 with the sliders below. That is a real model, and a famously useless one: it flags exactly one person, it happens to be right about them, and it misses every other case in the dataset.

The surface is F1 across every combination. It falls away to nothing along both edges, which is the behaviour the plain average refuses to have.

…the plain average …F1
…
F1 = 2 × (precision × recall) ÷ (precision + recall) // the harmonic mean

The plain average says 0.5 for a model that finds nothing. F1 says 0. That contrast is the entire argument for the harmonic mean and it needs no algebra: the harmonic mean is dragged down by the smaller of its two inputs, so it cannot be rescued by the larger one. A model has to be decent at both to score at all.

Then the honest part, which most explanations skip. F1 assumes precision and recall matter equally. In this running example they plainly do not: a false alarm is an unnecessary appointment and a missed case is an undetected condition, and treating those as interchangeable is exactly the mistake step 2b was about. F1 has quietly reintroduced it, one level up.

The knob for that is F-beta, where beta says how many times more you care about recall than precision. Beta of 2 weights recall four times as heavily and is the usual choice for screening. Beta of 0.5 leans towards precision, for when a false alarm is the expensive one, such as a fraud flag that freezes somebody's account. F1 is simply F-beta with beta set to 1, and that setting is a decision somebody made, not a default that came from the mathematics.

06

Thresholds, ROC, and where the three classifiers differ

Look back at what every step since the matrix has depended on. The four cells needed a threshold. Precision and recall needed a threshold. The precision-recall curve was a threshold being dragged. None of it is possible unless the model hands you a probability rather than a verdict.

This is where the three classifiers stop being interchangeable, and it has nothing to do with which is more accurate. They differ in how many different probabilities they are capable of saying. That count is the number of usable thresholds you have, and it is decided by the mechanism, not by the data.

6a How many answers can each one give?

  • Logistic regression puts a weighted sum through a sigmoid, and a sum of continuous numbers is a continuous number. Every person gets their own probability, so with … held-out people there are … distinct answers and just as many places to put a threshold. The dial is a real dial.
  • KNN counts how many of the k neighbours are positive and divides by k. With k = 5 the only answers that exist are 0, 0.2, 0.4, 0.6, 0.8 and 1. Six values, whatever the dataset, whatever the person. Move the threshold from 0.61 to 0.79 and nothing happens at all, because no person is sitting in that gap. The only way to buy resolution is to raise k.
  • A decision tree reports the mix of training rows in whichever leaf you land in. Grow it deep enough and every leaf ends up holding rows of a single class, so the mix is 0 or 1 and nothing else. The model has opinions but no doubt, and there is nothing left to threshold.

6b The ROC curve, and three mechanisms drawing it

Sweep the threshold from one end to the other and plot, for each setting, the share of real cases caught against the share of healthy people falsely flagged. That is the ROC curve. Up and to the left is good: catching everybody while alarming nobody. The diagonal is what a coin flip gets.

All three curves below come out of models fitted live on the same held-out people. The shapes are not drawn, they are consequences.

logistic regression K-nearest neighbours decision tree a coin flip

Up the side is recall, the share of real cases caught. Along the bottom is the share of healthy people falsely flagged. Every dot is one place a threshold could go, so counting the dots counts the resolution of the model.

…AUC, logistic regression …AUC, KNN …AUC, tree
…answers logistic regression has …answers KNN has …answers the tree has
…

Three things to do to those sliders.

  • Leave k at 5 and count the corners. The KNN curve is a staircase with six steps, one per possible answer, and between the corners there is nothing. Now raise k. The steps multiply, the staircase smooths into something that looks like a curve, and the AUC usually improves as a side effect. You are not making KNN smarter, you are giving it more things it is allowed to say.
  • Take the tree to depth 16. The curve stops being a curve. It becomes two straight segments meeting at a single corner, because the model only ever emits 0 or 1 and a threshold anywhere between them does exactly the same thing. One operating point, take it or leave it. Many people have met this in the wild as "why is my tree's AUC so bad" and never found out that the answer had nothing to do with how well it classifies.
  • Now walk the depth back down. The curve opens up as leaves start holding mixed rows again, and it peaks somewhere in the middle. Keep going to depth 1 and it collapses a second time, for the opposite reason: a stump has two leaves, so it has two answers. What a tree can say is limited by how many of its leaves hold genuinely mixed rows, and that is squeezed from both ends.

AUC is the area under the curve, and it summarises every threshold at once. Read it as the probability that the model gives a randomly chosen positive person a higher score than a randomly chosen negative one. 0.5 is a coin flip and 1.0 is perfect ordering. Because it never commits to a threshold, it is the fairer number for comparing models that nobody has tuned yet, which is exactly the situation you are in when you are still choosing between them.

It is also the number to stop using the moment you have chosen. AUC averages over thresholds you would never ship, including the ones that flag ninety percent of a healthy population. Once the model is picked, go back to the matrix and set the threshold on purpose.

And notice the trap this whole step has been walking you towards. A deep tree can have a poor AUC and a perfectly good accuracy at the same time, on the same predictions. Neither number is lying. They are answering different questions, and the tree's answer is bad only to the question about ranking, which is the question you are asking when you plan to tune a threshold later.