confusion

js/ml/metrics/confusion.ts

Confusion matrix and the per-class rates derived from it.

Classes

class ConfusionMatrix {

Counts of predicted versus true labels, and the metrics derived from them.

A matrix owns a fixed label universe and a dense count grid indexed [trueLabel][predictedLabel]. Build one from paired arrays with ConfusionMatrix.from, or construct an empty one over a known label set and accumulate with observe/observeAll — the latter is what makes this the aggregation primitive behind StreamingConfusionMatrix, since merge combines matrices computed independently over different shards.

Every rate is derived, not stored, so a matrix is the cheapest way to report accuracy, precision, recall, F-scores, kappa, and MCC together rather than re-scanning the labels once per metric.

Undefined rates are reported as 0: a class with no predictions has zero precision, and a class with no support has zero recall.

import { ConfusionMatrix } from 'fino:ml/metrics';

const cm = ConfusionMatrix.from(
  ['cat', 'dog', 'cat', 'bird'],
  ['cat', 'dog', 'dog', 'bird'],
);
console.log(cm.accuracy()); // 0.75
console.log(cm.recall('cat')); // 0.5
console.log(cm.macroF1().toFixed(4)); // 0.7778

Constructors

constructor(labels: readonly Label[])

Create an empty matrix over a fixed label universe.

Duplicate labels are rejected. Observing a label outside this set throws, which is what keeps sharded accumulation honest about its schema.

Static Methods

static from( yTrue: ArrayLike<Label>, yPred: ArrayLike<Label>, options: { labels?: readonly Label[] } = {}, ): ConfusionMatrix

Tabulate paired true and predicted labels.

import { ConfusionMatrix } from 'fino:ml/metrics';

const cm = ConfusionMatrix.from([1, 0, 1, 1], [1, 0, 0, 1]);
console.log(cm.count(1, 0)); // 1

Getters

get labels(): readonly Label[]

The label universe, in report order.

get total(): number

Total number of observations tabulated.

Methods

observe(trueLabel: Label, predictedLabel: Label, weight = 1): void

Record one observation, optionally with a fractional weight.

observeAll(yTrue: ArrayLike<Label>, yPred: ArrayLike<Label>): void

Record a batch of paired observations.

merge(other: ConfusionMatrix): ConfusionMatrix

Combine with another matrix, unioning both label universes.

Neither input is modified. Shards that only saw some of the classes merge cleanly, which is why worker-parallel evaluation can tabulate locally and reduce at the end.

import { ConfusionMatrix } from 'fino:ml/metrics';

const shardA = ConfusionMatrix.from(['a', 'b'], ['a', 'a']);
const shardB = ConfusionMatrix.from(['c'], ['c']);
console.log(shardA.merge(shardB).labels); // ['a', 'b', 'c']
count(trueLabel: Label, predictedLabel: Label): number

Count of observations with the given true and predicted labels.

toArray(): number[][]

Dense count grid indexed [trueLabel][predictedLabel], in label order.

support(label: Label): number

Number of observations whose true label is label.

predictedCount(label: Label): number

Number of observations predicted as label.

truePositives(label: Label): number

Correct predictions of label.

falsePositives(label: Label): number

Observations wrongly predicted as label.

falseNegatives(label: Label): number

Observations of label predicted as something else.

trueNegatives(label: Label): number

Observations that are neither label nor predicted as label.

accuracy(): number

Fraction of observations predicted correctly.

precision(label: Label): number

TP / (TP + FP) for label — how often a positive prediction is right.

recall(label: Label): number

TP / (TP + FN) for label — how much of the class was recovered.

specificity(label: Label): number

TN / (TN + FP) for label — recall of the negative class.

f1(label: Label): number

Harmonic mean of precision and recall for label.

fBeta(label: Label, beta: number): number

Weighted harmonic mean of precision and recall for label.

beta sets how much more recall matters than precision: beta < 1 favors precision, beta > 1 favors recall.

macroPrecision(): number

Unweighted mean precision across classes.

macroRecall(): number

Unweighted mean recall across classes.

macroF1(): number

Unweighted mean F1 across classes.

weightedPrecision(): number

Support-weighted mean precision across classes.

weightedRecall(): number

Support-weighted mean recall across classes.

weightedF1(): number

Support-weighted mean F1 across classes.

microPrecision(): number

Precision over pooled counts. Equals accuracy for single-label problems.

microRecall(): number

Recall over pooled counts. Equals accuracy for single-label problems.

microF1(): number

F1 over pooled counts. Equals accuracy for single-label problems.

balancedAccuracy(): number

Mean recall across classes that have support.

Unlike accuracy, a majority-class predictor cannot score well here, so this is the honest headline number for imbalanced data.

matthewsCorrCoef(): number

Matthews correlation coefficient over all classes, in [-1, 1].

Reports 0 for a degenerate matrix where the coefficient is undefined.

cohenKappa(): number

Cohen's kappa: accuracy corrected for agreement expected by chance.

Reports 0 when chance agreement is total and the statistic is undefined.

report(): Array<{ label: Label; precision: number; recall: number; f1: number; support: number }>

Per-class precision, recall, F1, and support, in label order.

format(): string

Render the count grid as an aligned text table, true labels down the rows.

import { ConfusionMatrix } from 'fino:ml/metrics';

console.log(ConfusionMatrix.from(['a', 'b'], ['a', 'a']).format());
toJSON(): { labels: Label[]; counts: number[][] }

Plain structure suitable for JSON.stringify and cross-realm transfer.