Where the dial sits in the arc
b1 gave you the four cells and the accuracy paradox. This session is the middle of the classic trilogy: the threshold is the single control that trades the two error cells against each other, and precision/recall/F1/F-beta are the vocabulary for describing where you set it. b3 lifts the same idea into curves (every threshold at once); b5 replaces the vocabulary with prices. Same 2,000-session Lumen slice, same seed 42, same evaluator - tonight you learn to drive it blind.
Two formulas, one dial 8 min live
Both metrics share a numerator - the true positives - and differ only in what they divide by. Precision = TP / (TP + FP): of everything we flagged, how much was real? Its denominator is the model's own flag pile. Recall = TP / (TP + FN): of everything real, how much did we flag? Its denominator is fixed by the world - the 64 actual converters in Lumen's slice do not care where you put the cut. That asymmetry is the whole mechanism: raising the threshold makes fewer, surer flags. The flag pile shrinks and cleans up (precision rises), but real buyers slip under the cut (recall falls). Lower it and the trade runs backwards.
LiveF1: why harmonic, not arithmetic5 min▶
You often need precision and recall folded into one number - to sort candidate models, to put in a dashboard cell. F1 is the harmonic mean: F1 = 2PR / (P + R). The choice of mean is the entire point:
- Take a degenerate model: precision 1.0, recall 0.02 (it flags two sessions all year, both correct). The arithmetic mean says 0.51 - "roughly half good." Flattery.
- The harmonic mean says ~0.039 - roughly as useless as the model actually is.
Harmonic means punish lopsidedness: the score is dragged toward the WORSE of the two numbers, and a zero on either side gives F1 = 0. You cannot buy a good F1 by maxing one metric and abandoning the other - which is exactly the gaming an arithmetic mean would invite. On Lumen's slice at the default 0.50 cut: precision 21.5%, recall 63.6%, F1 0.321 - pulled down toward the weak precision, as it should be.
LiveF-beta: leaning the average when costs lean5 min▶
When you KNOW one error hurts more, F-beta tilts the harmonic mean. Beta scales how much recall counts relative to precision:
- beta = 2 - recall matters more (a miss is the expensive error: fraud, disease, Lumen's $74 lost orders).
- beta = 0.5 - precision matters more (a false alarm is the expensive error: spam filters, auto-takedowns).
- beta = 1 is plain F1.
from sklearn.metrics import fbeta_score, balanced_accuracy_score
fbeta_score(y_test, y_pred, beta=2) # recall-weighted: misses hurt more
fbeta_score(y_test, y_pred, beta=0.5) # precision-weighted: false alarms hurt more
balanced_accuracy_score(y_test, y_pred) # macro-average recall - the honest
# accuracy replacement on imbalanced data (b1's paradox, patched)
| beta | Leans | Reach for it when | Lumen reading |
|---|---|---|---|
| 2 | Recall | a miss is the expensive error - fraud, disease, churn | the right lean: FN ~$74 vs FP ~$2 |
| 1 | Neither (plain F1) | costs unknown, or genuinely equal | the silent equal-costs assumption |
| 0.5 | Precision | a false alarm is the expensive error - spam, takedowns | the wrong lean for Lumen |
An honest flag before you fall in love with it: F-beta is a soft version of what b5 does properly. Beta encodes "recall matters more-ish"; a cost matrix encodes "a miss costs $74 and a false alarm costs $2," and then the optimal threshold is a calculation, not a vibe. Use F-beta when you know the direction of the asymmetry but not its price. Once you have prices, graduate.
Self-studybalanced accuracy, one more look2 min read▶
balanced_accuracy_score is the macro-average of per-class recall: recall on the converters averaged with recall on the non-converters. The lazy "always predict no" model from b1 scores 96.8% on plain accuracy but exactly 0.5 on balanced accuracy - the same as coin-flipping. That single property is why sklearn's docs recommend it to avoid "inflated performance estimates on imbalanced datasets." It is the drop-in replacement when a stakeholder insists on "an accuracy number."
Pick the point on purpose 7 min live
Andrew Ng's Advanced Learning Algorithms (week 3) turns the mechanism into doctrine, in two moves. Move one: plot the tradeoff curve and pick the operating point deliberately. The threshold is not a training artifact to inherit at 0.5 - it is a decision, and the curve is the menu of every decision available. Move two: iterate against a single-number metric. When you are comparing model candidates daily, two numbers (P and R) stall every review in argument; one number (F1, F-beta, or later b5's cost) makes "did this change help?" answerable in seconds. Pick the point deliberately, then compress it to one number so iteration stays fast - those two habits carry the rest of this course.
Liveprecision_recall_curve, and the gotcha in its return6 min▶
sklearn hands you the whole menu in one call - every precision/recall pair the model can reach, plus the thresholds that reach them:
from sklearn.metrics import precision_recall_curve
scores = model.predict_proba(X_test)[:, 1]
prec, rec, thresholds = precision_recall_curve(y_test, scores)
# GOTCHA: thresholds is ONE SHORTER than prec and rec.
# The final (precision=1, recall=0) point is appended with no
# threshold attached - so zip against thresholds, not the metrics:
for p, r, t in zip(prec, rec, thresholds):
print(f"cut at {t:.2f} -> precision {p:.2f}, recall {r:.2f}")
# picking a point: e.g. the highest-recall cut with precision >= 0.30
import numpy as np
ok = np.where(prec[:-1] >= 0.30)[0]
best_t = thresholds[ok[0]]
The length mismatch is not trivia - off-by-one it and every threshold you report is shifted one row from the precision/recall pair you think it produces. You would ship a cut believing it clears a precision bar it does not clear. Zip against thresholds and the alignment is guaranteed.
The reviewed-and-still-wrong threshold memo. A retention team picked "the threshold where precision crosses 40%" from a notebook that zipped precision against the full arrays. The off-by-one shifted their chosen cut one step loose; live precision came in under the bar they had promised legal for a customer-contact program. The fix was one character in a zip. The habit: after picking a threshold from the curve, recompute P and R at that cut directly with precision_score / recall_score and confirm they match the row you selected.
Self-studySatisficing vs optimizing - the doctrine's second half2 min read▶
Ng's fuller version, from Structuring ML Projects: when several things matter, make ONE the optimizing metric and turn the rest into constraints - "maximize F2, subject to latency under 300ms." You will meet this again in b8, where latency and cost per query become first-class satisficing metrics for LLM systems, straight from Anthropic's own success-criteria list.
Drive the dial blind ★ 20 min · everyone runs it
Same evaluator as b1 - Lumen's 2,000 scored sessions, seed 42, every metric recomputed live on each drag. Last session you explored it; tonight you drill it. The bar is prediction, not observation: you say what the tiles will do BEFORE the slider moves, in four-cell vocabulary, and the evaluator grades you.
Drill 1 - call all five tiles. Before touching anything, write down which direction each of the five tiles (accuracy, precision, recall, F1, error cost) moves when the threshold rises from 0.50, and say each reason in cell language ("FP shrinks faster than TP, so..."). Then drag up and score yourself out of 5. Anything under 5/5: reread the strip diagram and repeat until the calls are boring.
Drill 2 - find the crossover by feel. Somewhere on the dial, precision and recall are equal. Hunt it by feel - overshoot, bracket it, close in. Notice how steep both metrics are around the crossing: small threshold moves, large metric moves. This is why "we use threshold 0.5" deserves the same scrutiny as any other shipped constant.
Drill 3 - the cost preview. Confirm the FN cost box reads 74 (Lumen's lost order). Read the error-cost tile at your crossover point, then drag left until recall hits 100% and read it again - at that floor the cost is $1,146, the cheapest point on tonight's dial even though precision has collapsed to single digits. Sit with that disagreement between the pretty-metrics point and the cheap point. Do NOT press the two buttons yet - they are b5's opening scene, and the punchline costs $704.
Try it yourself - this week ◐ 20-30 min total
- Run
precision_recall_curveon any classifier you own. Zip against the thresholds array, pick an operating point for a stated reason, then verify it with directprecision_score/recall_scorecalls at that cut. - For your own problem from b1's homework: is it a beta = 2 problem or a beta = 0.5 problem? Write one sentence defending the direction, then compute both
fbeta_scorevalues and see how far they disagree. - Do the arithmetic-vs-harmonic party trick on paper for P = 0.9, R = 0.1. Predict both means first, then compute. (Arithmetic: 0.5. Harmonic: 0.18.)
- Reread the b1 cheat sheet's threshold row - it should now feel like an understatement.
Official sources covered
This session teaches the precision/recall core of the sklearn metrics guide, the matching Google crash-course lesson, and Ng's tradeoff doctrine - drilled on the live evaluator rather than on slides. Certificates and graded assessments stay with the official providers. This page covers:
Three questions before you go 🎯 ◐ 90 seconds
1 · Why is F1 a harmonic mean instead of a plain average of precision and recall?
The harmonic mean is dragged toward the worse number, so you cannot score well by maxing one metric and abandoning the other. An arithmetic mean would call a two-flags-a-year model "half good" - the exact gaming F1 exists to prevent.
2 · Your team decides a missed converter hurts far more than a wasted outreach email. Which F-beta leans the right way?
beta above 1 favors recall, beta below 1 favors precision - beta = 2 for expensive misses, beta = 0.5 for expensive false alarms. And remember the honest flag: once you can PRICE the asymmetry ($2 vs $74), b5's cost matrix beats any beta.
3 · precision_recall_curve returns prec, rec, and thresholds. What is the trap?
sklearn appends the (precision=1, recall=0) endpoint with no threshold attached. Zip against the full metric arrays and every reported threshold is off by one - you ship a cut that does not clear the precision bar you promised. Zip against thresholds, then re-verify the chosen point directly.