ROC Curve and ROC-AUC
Install and import#
npm install fintech-algorithmsimport { rocCurveAndRocAuc } from "fintech-algorithms/model-validation-and-backtesting/classification-and-score-validation/roc-curve-and-roc-auc";Signature#
rocCurveAndRocAuc(inputs)Walks a weighted record ledger from the highest score down, grouping records that share a score into one threshold step, and emits the ROC point at every step plus the trapezoidal area under them.
Parameters#
| Name | Type | Notes |
|---|---|---|
inputs | { records: Array<{ id: string; label: 0 | 1; score: number; weight?: number; score_available_at?: string; label_available_at?: string }>; evaluation_cutoff?: string } | The scored population. Every record needs a unique nonempty id, a label that is exactly the number 0 or 1, and a finite score; weight defaults to 1 and must be positive. If evaluation_cutoff is supplied, any score_available_at or label_available_at on a record is compared against it as a string and must not sort after it.records: nonempty · label: 0 or 1 · weight: positive, default 1 |
Returns#
{ points: Array<{ threshold: number | null; true_positive: number; false_positive: number; true_negative: number; false_negative: number; tpr: number; fpr: number }>; roc_auc: number; positive_weight: number; negative_weight: number; tie_group_count: number; state: string }
points opens with the classify-nothing point (threshold: null) and then carries one point per distinct score, each with weighted confusion counts and the tpr and fpr they imply. roc_auc is the trapezoidal area over those points, tie_group_count counts the distinct scores, and state is ranking-evaluated.
Errors#
- When
recordsis absent, not an array, or empty — throws RangeError - When a record
idis missing, empty, or repeats an earlier one — throws RangeError - When a
labelis anything other than the number 0 or 1 — throws RangeError - When a
weightis zero or negative — throws RangeError - When a record
scoreorweightis not a finite number — throws TypeError - When
score_available_atorlabel_available_atsorts afterevaluation_cutoff— throws RangeError - When one class carries no weight, so either
positive_weightornegative_weightis 0 — throws RangeError
Complexity: time O(n log n),
space O(n).
Worked example#
verified This is the worked example published in the article, replayed by the test suite on every run. The output cannot drift.
Input#
{
"records": [
{
"id": "R01",
"label": 1,
"score": 0.95,
"probability": 0.92,
"weight": 1,
"sector": "Banking",
"country": "Egypt",
"regime": "Expansion",
"score_available_at": "2025-01-01T00:00:00Z",
"label_available_at": "2026-01-01T00:00:00Z"
},
{
"id": "R02",
"label": 0,
"score": 0.9,
"probability": 0.88,
"weight": 1,
"sector": "Insurance",
"country": "Egypt",
"regime": "Expansion",
"score_available_at": "2025-01-01T00:00:00Z",
"label_available_at": "2026-01-01T00:00:00Z"
},
{
"id": "R03",
"label": 1,
"score": 0.9,
"probability": 0.84,
"weight": 1,
"sector": "Markets",
"country": "Saudi Arabia",
"regime": "Expansion",
"score_available_at": "2025-01-01T00:00:00Z",
"label_available_at": "2026-01-01T00:00:00Z"
}
],
"evaluation_cutoff": "2026-06-30T00:00:00Z"
}Call#
rocCurveAndRocAuc(inputs)Returns#
object with 6 fields: points, roc_auc, positive_weight, negative_weight, tie_group_count, state
{
"points": [
{
"threshold": null,
"true_positive": 0,
"false_positive": 0,
"true_negative": 14,
"false_negative": 10,
"tpr": 0,
"fpr": 0
},
{
"threshold": 0.95,
"true_positive": 1,
"false_positive": 0,
"true_negative": 14,
"false_negative": 9,
"tpr": 0.1,
"fpr": 0
},
{
"threshold": 0.9,
"true_positive": 2,
"false_positive": 1,
"true_negative": 13,
"false_negative": 8,
"tpr": 0.2,
"fpr": 0.07142857142857142
}
],
"roc_auc": 0.6607142857142857,
"positive_weight": 10,
"negative_weight": 14,
"tie_group_count": 22,
"state": "ranking-evaluated"
}Other exports#
This module also exports
precisionRecallCurveAndPrAuc, brierScore, logLoss, reliabilityDiagramAndExpectedCalibrationError, gainsLiftAndDecileCapture, costSensitiveThresholdOptimization, scoreStabilityAndMigrationMatrix, sliceBasedValidationBySectorCountryAndRegime, rareEventBacktestAndConfidenceBounds, calculate. Every module additionally exports run as an alias of its
primary function, and a meta object carrying its catalog id, domain, family,
shape and article URL.
Diagrams#
Calculation flow#
ROC Curve and ROC-AUC calculation flow
flowchart LR
S1["Validate identities labels weights direction and cutof"]
S2["Group records by descending score"]
S3["Start at zero predicted positives"]
S4["Add each tie groups positive and negative weight"]
S5["Publish confusion rates after each group"]
S1 --> S2
S2 --> S3
S3 --> S4
S4 --> S5
S5 --> D{"both classes must remain present and ties must remain grou"}
D --> O["roc_auc + diagnostics"]
O --> A["Audit: ROC points start at 00 end at 11 and never decrease in eit"]
How it works#
This page states the contract — how to call it correctly. The article explains the concept: why it works, and where it breaks.
References#
- Revised Guidance on Model Risk Management — Board of Governors of the Federal Reserve System, OCC, and FDIC
- The Meaning and Use of the Area under a Receiver Operating Characteristic Curve — James A. Hanley and Barbara J. McNeil
- Metrics and scoring: quantifying the quality of predictions — scikit-learn maintainers
- Evidence boundary