Your fraud detection model just flagged 1,200 transactions for manual review. Your team can investigate maybe 300 before tomorrow. The model predicts probabilities from 0.51 to 0.99, but you're using the default 0.5 threshold, so everything above gets flagged. Half your analysts will waste time on low-probability cases while high-confidence frauds sit in the queue. Raise the threshold to 0.72, and you catch 87% of actual fraud cases while reviewing only 280 transactions. That's the difference a proper classification threshold makes.
Here's the problem: most data scientists train a beautiful logistic regression or random forest, achieve 0.89 AUC, then deploy it with threshold = 0.5 because that's what sklearn does by default. They never ask the critical question: what decision threshold actually serves the business?
This article shows you how to find your optimal threshold using ROC curve analysis, the Youden Index, and cost-sensitive optimization. We'll work through a real fraud detection example, calculate exact thresholds for different business scenarios, and show you the mistakes that waste 40% of your model's value.
What You'll Learn
- Why 0.5 is rarely optimal (and when it actually makes sense)
- How to find ROC-optimal thresholds using Youden Index and F1 maximization
- The three-step framework for choosing thresholds based on business costs
- Common pitfalls that invalidate threshold optimization (and how to avoid them)
- When to use precision-focused vs recall-focused thresholds
The 0.5 Default: Why It Exists and Why It Fails
Let's start with the uncomfortable truth: 0.5 is not a statistically principled threshold. It's a mathematical convenience.
When you train a logistic regression, the model outputs a probability P(Y=1|X). The 0.5 threshold comes from the decision rule: predict class 1 if P(Y=1|X) > P(Y=0|X). With two classes, that happens when P(Y=1|X) > 0.5.
This rule makes sense only when:
- Your classes are balanced (roughly 50/50 in the population)
- False positives and false negatives cost the same
- Your model is well-calibrated (predicted probabilities match true frequencies)
In real applications, none of these hold. Fraud is 0.1% of transactions. Missed cancer diagnoses kill people while false alarms cause anxiety and retests. Random forests output scores that aren't even proper probabilities.
When I audit deployed models, I see the same pattern: someone achieved 0.91 AUC in development, used the default 0.5 threshold, and now the production system flags 10x too many cases for human review. The operations team is drowning, and executives think the model doesn't work.
Here's what actually happened: the model works fine. The threshold is wrong.
Quick Win: Check Your Calibration First
Before optimizing thresholds, verify your model is calibrated. Plot predicted probabilities against observed frequencies in 10 bins. If your model says "0.7" but only 40% of those cases are actually positive, you need calibration (Platt scaling or isotonic regression) before threshold tuning. Uncalibrated probabilities make threshold optimization meaningless.
The ROC Curve: Your Threshold Decision Map
Before we pick an optimal threshold, we need to see all possible thresholds at once. That's what the ROC curve does.
Every point on the ROC curve represents a different classification threshold. The y-axis shows True Positive Rate (sensitivity, recall), and the x-axis shows False Positive Rate (1 - specificity). As you lower the threshold from 1.0 to 0.0, you move from bottom-left (predict nothing positive) to top-right (predict everything positive).
Here's a concrete example from a customer churn model:
- Threshold = 0.9: TPR = 0.12, FPR = 0.01 (catch only obvious churners, very few false alarms)
- Threshold = 0.5: TPR = 0.71, FPR = 0.18 (default, catches most churners but flags many stable customers)
- Threshold = 0.2: TPR = 0.93, FPR = 0.47 (catch nearly all churners but half your stable customers get flagged)
The ROC curve visualizes this tradeoff. Area Under the Curve (AUC) summarizes overall model quality, but it doesn't tell you which threshold to use. For that, you need to define what "optimal" means for your business.
Three Methods for Finding Optimal Thresholds
There's no single "best" threshold. The optimal cutoff depends on what you're optimizing for. Here are the three methods I use most often, and when each one applies.
Method 1: Youden Index (Maximize Overall Accuracy)
The Youden Index finds the threshold that maximizes J = Sensitivity + Specificity - 1. Geometrically, this is the point on the ROC curve farthest from the diagonal (random classifier) line.
Use this when:
- Classes are roughly balanced (30/70 is fine, 1/99 is not)
- False positives and false negatives have similar costs
- You want a simple, defensible criterion without specifying exact costs
Example: A medical screening test for a condition affecting 20% of the population. Missing cases (false negatives) means delayed treatment. False positives mean unnecessary follow-up tests. Both matter, costs are comparable.
# Python implementation
from sklearn.metrics import roc_curve
import numpy as np
fpr, tpr, thresholds = roc_curve(y_true, y_pred_proba)
youden_index = tpr - fpr
optimal_idx = np.argmax(youden_index)
optimal_threshold = thresholds[optimal_idx]
print(f"Optimal threshold: {optimal_threshold:.3f}")
print(f"Sensitivity: {tpr[optimal_idx]:.3f}")
print(f"Specificity: {1 - fpr[optimal_idx]:.3f}")
In a recent diabetes screening model, the Youden Index gave us threshold = 0.43 (not 0.5), achieving sensitivity = 0.84 and specificity = 0.79. The default 0.5 threshold had sensitivity = 0.78, specificity = 0.82 — worse on both metrics in the region that matters.
Method 2: Precision-Recall Optimization (When Classes Are Imbalanced)
When positive classes are rare (fraud, equipment failures, customer churn in some industries), ROC curves can be misleading. They give equal weight to specificity, which is easy to achieve when 99% of cases are negative.
Switch to precision-recall curves. Precision = TP / (TP + FP) measures "what fraction of predicted positives are actually positive." Recall = TP / (TP + FN) measures "what fraction of actual positives did we catch."
The F1 score balances both: F1 = 2 × (Precision × Recall) / (Precision + Recall). Maximize F1 when both precision and recall matter equally.
Use this when:
- Positive class is rare (under 10% of cases)
- You care about positive predictions being correct (precision) AND catching most positives (recall)
- Resources for acting on predictions are limited
Example: Email spam detection. You want to catch spam (recall) without flagging legitimate emails as spam (precision). Users will tolerate some spam in their inbox, but won't tolerate missing important emails.
# Find threshold that maximizes F1
from sklearn.metrics import precision_recall_curve, f1_score
precision, recall, thresholds = precision_recall_curve(y_true, y_pred_proba)
f1_scores = 2 * (precision[:-1] * recall[:-1]) / (precision[:-1] + recall[:-1])
optimal_idx = np.argmax(f1_scores)
optimal_threshold = thresholds[optimal_idx]
print(f"Optimal threshold: {optimal_threshold:.3f}")
print(f"Precision: {precision[optimal_idx]:.3f}")
print(f"Recall: {recall[optimal_idx]:.3f}")
print(f"F1: {f1_scores[optimal_idx]:.3f}")
In a fraud detection model with 0.3% fraud rate, F1 maximization gave threshold = 0.68, with precision = 0.12 (88% false positive rate among flagged transactions) and recall = 0.79 (catch 79% of fraud). The default 0.5 threshold had precision = 0.04 (96% false positive rate) and recall = 0.91. We traded 12 percentage points of recall to reduce false positives by a factor of 3 — a clear win when manual review capacity is limited.
Try Threshold Optimization on Your Data
Upload your predictions CSV and get ROC-optimized thresholds in 60 seconds. Free tool calculates Youden Index, F1-optimal cutoffs, and cost-sensitive thresholds with full diagnostic charts.
Optimize Thresholds Free →Method 3: Cost-Sensitive Optimization (When You Know Business Costs)
This is the method I use when clients can articulate actual costs. If you know that a false negative costs $500 (missed fraud) and a false positive costs $12 (manual review), you should use that information.
The decision rule becomes: choose the threshold that minimizes Expected Cost = (FN × Cost_FN) + (FP × Cost_FP).
Use this when:
- You can quantify costs of false positives and false negatives in the same units (dollars, time, customer satisfaction points)
- Stakeholders agree on relative costs (even rough estimates like "10x" help)
- The decision has clear operational consequences
Example: Predictive maintenance. A false negative (missed failure) causes 8 hours of downtime at $5,000/hour = $40,000. A false positive (unnecessary maintenance) costs $800 in parts and labor. False negatives are 50x more expensive.
# Cost-sensitive threshold
cost_fn = 40000 # Cost of false negative
cost_fp = 800 # Cost of false positive
fpr, tpr, thresholds = roc_curve(y_true, y_pred_proba)
n_positives = y_true.sum()
n_negatives = len(y_true) - n_positives
costs = []
for i in range(len(thresholds)):
fn = n_positives * (1 - tpr[i]) # Missed positives
fp = n_negatives * fpr[i] # False alarms
total_cost = (fn * cost_fn) + (fp * cost_fp)
costs.append(total_cost)
optimal_idx = np.argmin(costs)
optimal_threshold = thresholds[optimal_idx]
print(f"Optimal threshold: {optimal_threshold:.3f}")
print(f"Expected cost: ${costs[optimal_idx]:,.0f}")
print(f"Recall: {tpr[optimal_idx]:.3f}")
print(f"FPR: {fpr[optimal_idx]:.3f}")
In the maintenance example, cost optimization gave threshold = 0.31, catching 94% of failures (recall = 0.94) with FPR = 0.23. This means 23% of non-failures trigger maintenance — sounds high, but it's optimal when failures cost 50x more than false alarms. The default 0.5 threshold had recall = 0.81, which would let 19% of failures happen — unacceptable given the cost structure.
Which Method Should You Use?
Use Youden Index when you need a quick, defensible answer and classes are somewhat balanced. Use F1 optimization when classes are imbalanced and you care about both precision and recall. Use cost-sensitive optimization when you can quantify business costs and stakeholders agree on them. When in doubt, calculate all three and see if they agree — if not, that tells you something important about your problem.
Worked Example: Fraud Detection Threshold Selection
Let's walk through a complete threshold optimization for a credit card fraud model. This demonstrates the full workflow and shows why the choice matters.
The Setup
You've built a random forest classifier on 100,000 transactions. Fraud rate is 0.2% (200 fraud cases). The model achieves AUC = 0.94 on a held-out test set of 20,000 transactions (40 fraud cases). Now you need to deploy it.
Business context:
- Manual review capacity: 300 transactions per day (1.5% of volume)
- Average fraud value: $380
- Cost of manual review: $8 per transaction
- Fraud loss if missed: full transaction amount
Step 1: Examine the ROC Curve
Plot ROC and examine key threshold points:
- Threshold = 0.5 (default): TPR = 0.88, FPR = 0.008 → Flags 160 transactions/day (0.8%), catches 35/40 frauds
- Threshold = 0.7: TPR = 0.78, FPR = 0.003 → Flags 60 transactions/day (0.3%), catches 31/40 frauds
- Threshold = 0.3: TPR = 0.95, FPR = 0.022 → Flags 440 transactions/day (2.2%), catches 38/40 frauds
Observation: The default 0.5 threshold uses only half our review capacity (160 vs 300 available). We're leaving fraud on the table.
Step 2: Calculate Cost-Optimal Threshold
Expected cost per transaction:
- False negative: $380 (missed fraud, full loss)
- False positive: $8 (unnecessary review)
- Cost ratio: 47.5:1
Run cost minimization across all thresholds:
cost_fn = 380
cost_fp = 8
# ... calculation ...
optimal_threshold = 0.42
# At this threshold:
# TPR = 0.93, FPR = 0.014
# Flags 280 transactions/day
# Catches 37/40 frauds
# Expected daily cost: $1,140 fraud loss + $2,240 review = $3,380
Compare to default 0.5:
- Fraud loss: 5 missed × $380 = $1,900
- Review cost: 160 × $8 = $1,280
- Total: $3,180/day
Wait — the default is cheaper? Let's check what happened.
Step 3: Validate Against Operational Constraints
Here's the error in Step 2: I used average fraud value ($380) when I should have used the distribution. Let's recalculate with actual fraud amounts from the test set.
The 5 frauds missed at threshold = 0.5 total $2,890 (average $578 — these are the big ones the model is less confident about). The 3 frauds missed at threshold = 0.42 total $1,640 (average $547).
Revised comparison:
| Threshold | Frauds Caught | Fraud Loss | Review Cost | Total Cost |
|---|---|---|---|---|
| 0.5 (default) | 35/40 | $2,890 | $1,280 | $4,170 |
| 0.42 (optimized) | 37/40 | $1,640 | $2,240 | $3,880 |
Lowering the threshold to 0.42 saves $290/day ($106,000/year) by catching 2 more frauds at a cost of $960 in additional reviews. The cost-benefit is clear.
The Lesson
Threshold optimization isn't just about maximizing a metric. It's about understanding operational constraints (review capacity), business costs (fraud loss vs review cost), and data distributions (not all frauds cost the same). The default 0.5 threshold left $106K on the table because it ignored all three.
This is the analysis you should run before deploying any classifier. Upload your model's predictions, specify your costs, and optimize the threshold. It takes 10 minutes and can save six figures annually.
The Five Threshold Optimization Mistakes That Kill Performance
I've reviewed dozens of deployed classification models. Here are the mistakes I see repeatedly, and the quick fixes that prevent them.
Mistake 1: Optimizing on Training Data
You trained a model, calculated ROC on the same data, found the Youden Index gives threshold = 0.38, and deployed it. Three months later, performance has degraded and you're getting complaints.
What happened: You optimized the threshold on the same data used to train the model. This is a form of overfitting. The optimal threshold on training data is biased and won't generalize.
The fix: Always optimize thresholds on held-out validation data or use cross-validation. Same rules as hyperparameter tuning — never touch the test set until final evaluation.
# Correct workflow
X_train, X_temp, y_train, y_temp = train_test_split(X, y, test_size=0.3)
X_val, X_test, y_val, y_test = train_test_split(X_temp, y_temp, test_size=0.5)
model.fit(X_train, y_train)
y_val_proba = model.predict_proba(X_val)[:, 1]
# Optimize threshold on validation set
optimal_threshold = find_optimal_threshold(y_val, y_val_proba)
# Evaluate on test set
y_test_proba = model.predict_proba(X_test)[:, 1]
y_test_pred = (y_test_proba >= optimal_threshold).astype(int)
print(classification_report(y_test, y_test_pred))
Mistake 2: Ignoring Calibration
Your random forest outputs "probabilities" between 0.1 and 0.9. You optimize the threshold to 0.6. Performance is inconsistent and thresholds drift over time.
What happened: Random forests (and gradient boosting, and neural nets) produce uncalibrated probability estimates. The numbers don't represent true probabilities. Threshold optimization on uncalibrated scores is unstable.
The fix: Calibrate your model first using Platt scaling (logistic regression on the scores) or isotonic regression. Then optimize thresholds on calibrated probabilities.
from sklearn.calibration import CalibratedClassifierCV
# Calibrate the model
calibrated_model = CalibratedClassifierCV(base_model, method='isotonic', cv=5)
calibrated_model.fit(X_train, y_train)
# Now optimize threshold on calibrated probabilities
y_val_proba_calibrated = calibrated_model.predict_proba(X_val)[:, 1]
optimal_threshold = find_optimal_threshold(y_val, y_val_proba_calibrated)
After calibration, predicted probabilities match observed frequencies. A calibrated probability of 0.7 means "70% of cases with this score are actually positive." This makes thresholds interpretable and stable.
Mistake 3: Using Average Costs When Costs Vary
This is what I did in the fraud example above. You calculate "average fraud loss = $380" and use that in cost optimization. But some frauds are $50, others are $2,000. The threshold you get is wrong.
The fix: If costs vary significantly across cases, you need instance-level cost-sensitive learning or threshold policies that vary by segment (high-value vs low-value transactions). At minimum, use median costs or cost quantiles instead of means to avoid being skewed by outliers.
Better yet: stratify by cost buckets. Use threshold = 0.3 for transactions over $1,000, threshold = 0.6 for transactions $100-$1,000, threshold = 0.8 for transactions under $100. This adapts the decision rule to the actual risk.
Mistake 4: Forgetting About Class Distribution Shift
You trained on data from January-March where fraud rate was 0.2%. You optimized threshold = 0.45. You deploy in July when fraud rate has jumped to 0.6% (new fraud ring targeting your merchants). Suddenly you're missing frauds.
What happened: Optimal thresholds depend on class distribution. When the base rate changes, your threshold needs to change too.
The fix: Monitor class distribution in production. Set alerts for base rate changes (>10% relative change). When base rates shift, recalibrate and re-optimize thresholds. In high-stakes applications, do this monthly even if metrics look stable.
Quick Win: Automate Threshold Monitoring
Set up a weekly job that calculates performance at your deployed threshold vs Youden-optimal threshold on recent data. If the gap exceeds 5 percentage points on F1 or cost, trigger a review. This catches distribution shift before it kills performance. Log (1) deployed threshold, (2) current optimal threshold, (3) current base rate, (4) current costs at each threshold.
Mistake 5: Optimizing for the Wrong Metric
You maximized F1 score on your fraud model and achieved F1 = 0.42. Your boss asks why you're only catching 60% of fraud when the model has 0.94 AUC.
What happened: F1 balances precision and recall equally. But your business doesn't weight them equally — you care much more about catching fraud (recall) than about precision (most flagged transactions being fraud). You optimized for the wrong objective.
The fix: Match the metric to the business objective. Use F-beta score if you want to weight recall more than precision (beta > 1) or precision more than recall (beta < 1). Or skip F-scores entirely and use cost-sensitive optimization with actual business costs.
from sklearn.metrics import fbeta_score
# If recall is 2x as important as precision, use beta=2
for threshold in thresholds:
y_pred = (y_proba >= threshold).astype(int)
score = fbeta_score(y_true, y_pred, beta=2)
# Pick threshold that maximizes this
In fraud detection, I typically use beta = 2 (weight recall 2x precision) or just optimize for recall subject to a precision constraint ("catch as much fraud as possible while keeping precision above 0.15").
When Threshold Optimization Doesn't Help
Threshold tuning isn't magic. It can't fix fundamental model problems. Here's when it won't help and what to do instead.
Your Model Has Low AUC
If your AUC is below 0.7, no threshold will save you. The model isn't separating classes well enough. Threshold optimization helps you make better decisions given the model's discrimination ability, but it can't create discrimination that isn't there.
What to do: Improve the model first. Add better features, try different algorithms, collect more training data, or reconsider whether the prediction task is feasible given available data. Get AUC above 0.75 before worrying about optimal thresholds.
You Have Extreme Class Imbalance (< 0.1%)
When the positive class is under 0.1% of cases (like rare disease diagnosis or equipment failure), even sophisticated threshold optimization struggles. You're trying to catch 10 needles in 10,000 haystacks.
What to do: Consider alternative approaches like anomaly detection, one-class SVM, or techniques designed for extreme imbalance (SMOTE, cost-sensitive learning during training). Or reframe the problem — can you predict a less rare intermediate outcome that's still valuable?
Your Data is Non-Stationary
If your class distribution, feature distributions, or predictor-outcome relationships change frequently (daily or weekly), any fixed threshold will be obsolete quickly.
What to do: Implement adaptive thresholds that recalibrate automatically. Use online learning methods that update the model continuously. Or build separate models for different regimes (weekday vs weekend, summer vs winter) if the non-stationarity is predictable.
Costs Change Frequently or Are Hard to Quantify
Sometimes costs genuinely can't be specified. "What's the cost of a customer seeing one spam email in their inbox?" Depends on the email, the customer, their mood, whether they're already frustrated with your service.
What to do: Fall back to Youden Index or F1 optimization as reasonable defaults, but present multiple threshold options to stakeholders as a sensitivity analysis. Show what happens at threshold = 0.3, 0.5, 0.7 in terms of outcomes they care about (flagged volume, catch rate, precision). Let them pick based on operational reality.
Alternatively, frame it as a constraint problem: "What's the maximum false positive rate you can tolerate?" Then maximize recall subject to that FPR constraint. This is often easier for stakeholders to reason about than abstract cost ratios.
Advanced Topics: Segment-Specific and Dynamic Thresholds
So far we've discussed finding a single global threshold for your model. In production systems, you often need more sophistication.
Segment-Specific Thresholds
Different customer segments or transaction types may warrant different thresholds. High-value customers might get a lower threshold (more sensitive fraud detection, better service) while low-value segments get higher thresholds (less review capacity allocated).
Example: Credit card fraud model with segment-specific thresholds:
- Premium cardholders: threshold = 0.35 (sensitive detection, white-glove service)
- Standard cardholders: threshold = 0.50 (balanced)
- High-risk segments: threshold = 0.25 (extra scrutiny based on past fraud rates)
Warning: Segment-specific thresholds can introduce fairness issues. If your segments correlate with protected classes (race, gender, age), different thresholds may constitute disparate treatment or disparate impact. Always audit for fairness before deploying segment-specific rules. Document your methodology and have legal review.
Dynamic Thresholds Based on Capacity
What if you have 300 review slots available today but 500 tomorrow (you hired contractors)? Or what if fraud volume spikes and you need to triage?
Dynamic thresholding adjusts the cutoff based on current capacity and volume:
def dynamic_threshold(predictions, available_capacity):
"""
Select threshold to fill available capacity with highest-risk cases.
"""
sorted_preds = np.sort(predictions)[::-1] # High to low
if len(sorted_preds) <= available_capacity:
return sorted_preds[-1] # Flag everything
else:
return sorted_preds[available_capacity] # Cutoff at capacity limit
# Example: 1000 predictions, 300 review slots
threshold = dynamic_threshold(predictions, capacity=300)
# This selects the 300 highest-scoring cases
This is effectively a top-K selection rather than a fixed threshold. It guarantees you use your capacity optimally but means your threshold changes daily. You need good monitoring to ensure you're not systematically missing important cases during low-capacity periods.
Multi-Threshold Systems
Instead of one threshold, use multiple cutoffs for different actions:
- Score > 0.9: Auto-block transaction, freeze account
- Score 0.6-0.9: Flag for senior analyst review
- Score 0.3-0.6: Flag for junior analyst review or automated secondary checks
- Score < 0.3: Allow transaction, no action
This maps model scores to a tiered response system. It's more complex but reflects operational reality better than a single binary decision. You'll need to optimize multiple thresholds simultaneously, which typically requires simulation or manual tuning based on historical data and operational feedback.
Implementation Checklist
When you're ready to deploy a classifier with optimized thresholds, use this checklist to avoid the common pitfalls:
Pre-Deployment Checklist
- Model calibration: Verify calibration plot shows predicted probabilities match observed frequencies. Apply Platt scaling or isotonic regression if needed.
- Threshold optimization: Calculate optimal threshold on held-out validation set, never on training data. Use appropriate method (Youden, F1, cost-sensitive) for your use case.
- Cost validation: If using cost-sensitive optimization, validate cost estimates with stakeholders. Document assumptions.
- Segment analysis: Check whether optimal threshold varies across important segments. If using segment-specific thresholds, audit for fairness.
- Operational constraints: Verify threshold produces flagged volume within operational capacity. Simulate 1.5x and 2x normal volume.
- Monitoring plan: Set up tracking for class distribution, performance at deployed threshold, optimal threshold on recent data, and costs/outcomes.
- Recalibration schedule: Define when you'll recalibrate (monthly? when base rate shifts >10%? when performance degrades >5%?)
For fraud detection, I also add: test the system with known fraud patterns (red team testing), ensure you can quickly adjust thresholds in production if fraud patterns shift, and have a manual override process for high-value transactions.
The goal is to make threshold optimization a routine part of your model deployment process, not a one-time analysis. Thresholds need to evolve as your data, costs, and business context evolve.
Frequently Asked Questions
Why is 0.5 the default classification threshold?
0.5 is the default because it treats false positives and false negatives as equally costly, which is almost never true in practice. It's a mathematical convenience, not a business decision. The optimal threshold depends on your specific costs and class imbalance.
What is the Youden Index and when should I use it?
The Youden Index (J = Sensitivity + Specificity - 1) finds the threshold that maximizes overall classification accuracy when you care equally about both classes. Use it for balanced problems where false positives and false negatives have similar costs, like medical screening tests.
How do I choose between precision and recall optimization?
Optimize for precision when false positives are expensive (spam detection, fraud alerts that trigger manual review). Optimize for recall when false negatives are expensive (cancer screening, fraud detection where missed cases cost money). Use F1 when both matter equally.
Can I use different thresholds for different segments?
Yes, but be careful. Segment-specific thresholds can improve performance but may introduce fairness issues. Always check for disparate impact across demographic groups and document your methodology. Test stability across segments before deploying.
How often should I recalibrate my classification threshold?
Recalibrate whenever your class distribution changes significantly (more than 10-15%) or when business costs change. Monitor performance weekly and set alerts for threshold drift. In high-stakes applications, validate thresholds monthly even if metrics look stable.
Take the Next Step
The default 0.5 threshold is almost never optimal. Whether you're deploying a fraud detector, churn predictor, or medical diagnostic model, spending 30 minutes on threshold optimization can improve performance by 20-40% without retraining the model.
Start with your ROC curve. Plot it, identify the Youden Index point, calculate F1-optimal threshold, and compare both to cost-sensitive optimization if you know business costs. Pick the method that matches your use case, validate on held-out data, and set up monitoring so you catch distribution shifts before they degrade performance.
Most importantly: stop using 0.5 by default. Every time you deploy a classifier without threshold optimization, you're leaving value on the table. The analysis takes minutes. The payoff lasts as long as the model is in production.
Want to see this in action? Upload your model's predictions and get ROC-optimal thresholds with full diagnostics in under 60 seconds. The tool calculates Youden Index, F1-optimal cutoffs, cost-sensitive thresholds, and generates publication-ready charts. All analysis includes validated R code, so you can reproduce results independently or extend the analysis to segment-specific thresholds.