fintech-algorithms
Using a coding agent? Give it the skill: npx skills add IslamBaraka90/Fintech-Algorithms-Library What it does →

ROC Curve and ROC-AUC

Install and import#

bash
npm install fintech-algorithms
ts
import { 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#

NameTypeNotes
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 records is absent, not an array, or empty — throws RangeError
  • When a record id is missing, empty, or repeats an earlier one — throws RangeError
  • When a label is anything other than the number 0 or 1 — throws RangeError
  • When a weight is zero or negative — throws RangeError
  • When a record score or weight is not a finite number — throws TypeError
  • When score_available_at or label_available_at sorts after evaluation_cutoff — throws RangeError
  • When one class carries no weight, so either positive_weight or negative_weight is 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#

inputs
{
  "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#

ROC Curve and ROC-AUC — article hero
ROC Curve and ROC-AUC — decision boundaries
ROC Curve and ROC-AUC — method selection
ROC Curve and ROC-AUC — system map

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.

Read the article →

References#

The rest of the Classification and Score Validation family#