Choose metrics that track the decision the model actually drives, and report them with enough uncertainty that a difference is believable.
Scanned 9/10/2026
Install to Claude Code
npx -y skills add snoodleboot-io/prompticorn --skill minimal --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Minimal?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/snoodleboot-io-minimal-e8d265b5)More formats (shields.io, HTML) on the badges page.
# Model Evaluation (Minimal)
## Purpose
Choose metrics that track the decision the model actually drives, and report them
with enough uncertainty that a difference is believable.
## Core Techniques
### 1. Match the Metric to the Class Balance
| Metric | Reads well when | Fails when |
|---|---|---|
| Accuracy | Classes roughly balanced | 1% positives — predict all-negative scores 0.99 |
| ROC-AUC | Balanced, or you rank both classes | Heavy imbalance — flattering, see below |
| PR-AUC / average precision | Positives are rare and are what you care about | Not comparable across datasets with different base rates |
| F1 | You need one number at a fixed threshold | Hides which side of the trade-off you lost |
ROC-AUC flatters an imbalanced model because its x-axis is the false positive
rate, `FP / (FP + TN)`. With 99% negatives, `TN` is enormous, so thousands of
false positives barely move the FPR. Precision, `TP / (TP + FP)`, has no such
cushion — those false positives land directly in the denominator. A fraud model
at 0.5% prevalence can post ROC-AUC 0.97 while its precision at usable recall
is 0.08.
### 2. Report an Interval, Not a Point
```python
from sklearn.model_selection import cross_val_score, StratifiedKFold
import numpy as np
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=0)
s = cross_val_score(model, X, y, cv=cv, scoring="average_precision")
print(f"{s.mean():.3f} +/- {1.96 * s.std() / np.sqrt(len(s)):.3f}")
```
A single held-out number is a sample of size one from the split distribution. On
a 2,000-row test set the standard error on AUC is often ±0.02, so the "+0.004
improvement" you are about to ship is noise.
### 3. Split Along the Axis of Deployment
Random `train_test_split` is right only when rows are i.i.d. With time-ordered
data use `TimeSeriesSplit`; with repeated users, sessions, or patients use
`GroupKFold` so the same entity never straddles the split. A random split on
grouped data leaks and typically inflates scores by several points.
### 4. Tune the Threshold Separately from the Model
`predict()` hardcodes 0.5, which is almost never the operating point you want.
Pick the threshold on a validation set from the precision-recall curve given
your cost ratio, then measure it once on test.
### 5. Calibrate When You Consume the Probability
If a downstream rule multiplies the score by a dollar amount, the score must
mean something. Boosted trees and SVMs are typically miscalibrated; check with
`sklearn.calibration.calibration_curve`, fix with `CalibratedClassifierCV`
(`method="isotonic"` with ample data, `"sigmoid"` when small).
### 6. Compare Against a Real Baseline
`DummyClassifier(strategy="prior")` and last-week's-value for forecasting. A
model that cannot beat the naive baseline by more than the confidence interval
is not a model.
## Warning Signs
- Accuracy quoted on a dataset with strong class imbalance
- One test-set number with no interval, no repeats, no seed variance
- Test set consulted more than once during model selection
- Metric computed on preprocessed-then-split data (scaler fit on all rows)
- Threshold left at 0.5 while the cost of FP and FN differ by 10x
- Offline metric improves, the business metric does not, and nobody investigates
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!