A classifier like XGBoost does not output a decision. It outputs a score \(\hat p(x) \in [0,1]\), and a decision appears only when we choose a rule:
Libraries default to \(\tau = 0.5\). That default looks neutral, but it isn't: it encodes a specific belief about the cost of errors. In the Credit Default project, moving \(\tau\) from 0.50 to 0.24 raised recall on defaulters from 35.6% to 60.1% — a bigger change than any hyperparameter search delivered.
§1The cost view
Let \(c_{FP}\) be the cost of flagging a client who would have paid (false positive) and \(c_{FN}\) the cost of missing a defaulter (false negative). For a client with true default probability \(p\), the expected cost of each action is
Predicting “default” is the better action whenever \(p\,c_{FN} \ge (1-p)\,c_{FP}\), which gives the Bayes-optimal threshold
So \(\tau = 0.5\) is optimal only if both errors cost the same. Inverting (3), any threshold implies a cost ratio:
Read literally: choosing 0.24 says that missing a defaulter costs about three times as much as a false alarm. That is a statement a credit team can agree or disagree with — which is exactly the point. A threshold turns a business assumption into a number.
§2What the project actually did
No explicit costs were available, so the threshold was chosen by a proxy objective: maximize F1 on the validation split (4,800 clients, never used for the final evaluation). The grid search produced \(\tau = 0.24\) with validation F1 = 0.5405. On the untouched test set (6,000 clients), the same threshold gave F1 = 0.5396 and recall = 0.6006 — almost no degradation, a good sign that the choice was not overfitted.
The explorer below uses the test-set curve from the model-selection notebook (0.05 grid) to show the whole trade-off and the cost ratio each \(\tau\) implies:
§3A check from the literature
For a calibrated classifier, the F1-optimal threshold equals half the maximum attainable F1 (Lipton, Elkan & Narayanaswamy, 2014):
With validation \(F1^{*} \approx 0.54\), (5) predicts \(\tau \approx 0.27\). The empirical optimum, 0.24, is close. Update: the follow-up note checks calibration — the scores are well calibrated, and the gap is sampling noise: the bootstrap 95% interval of the F1-optimal threshold is [0.22, 0.33]. It also explains why F1-optimal thresholds are usually well below 0.5: F1 is at most 1, so for a calibrated model the optimal threshold can never exceed 0.5.
§4Takeaways
- A threshold is part of the model. Report it, justify it, and version it — the API returns it with every prediction for this reason.
- Choose \(\tau\) on validation data, never on the test set. The test set is for one final, honest number.
- If costs are known, use (3). If not, a proxy like F1 works — but translate the result back into an implied cost ratio with (4) and ask whether it makes sense.
- Check calibration before reading scores as probabilities.
Reference: Z. C. Lipton, C. Elkan, B. Narayanaswamy. Thresholding Classifiers to Maximize F1 Score. ECML PKDD, 2014. · Data: github.com/Igor-Millis/credit-default-api, notebooks 09 and 010.