classification
js/ml/metrics/classification.ts
Classification metrics over discrete labels and over ranked scores.
Types
type Average = 'binary' | 'macro' | 'micro' | 'weighted' | 'none'
How per-class scores are combined into a single number.
binary reports the positive class alone. macro is the unweighted mean
over classes, treating rare classes as equal to common ones. weighted
takes the same per-class scores weighted by true-class support. micro
pools every class's counts before dividing, which for single-label problems
equals accuracy. none returns one score per class, in label order.
Interfaces
interface AverageOptions {
Options shared by the averaged classification metrics.
Properties
average?: Average
Averaging mode. Defaults to binary.
positiveLabel?: Label
Which label counts as positive under binary averaging. Inferred as 1
when the labels are {0, 1} and true when they are {false, true};
otherwise it must be given.
labels?: readonly Label[]
Label universe, in report order. Defaults to the sorted union of the observed true and predicted labels. Pass this to keep a class present in the report even when a batch never observes it.
interface ProbabilityOptions {
Options for probability-scored classification metrics.
Properties
positiveLabel?: Label
Which label the probabilities refer to. Inferred as 1 or true when
the labels allow it.
interface RocCurve {
A receiver-operating-characteristic curve.
Properties
thresholds: number[]
Score thresholds, descending. The first entry is above every score, which is the origin point where nothing is predicted positive.
falsePositiveRate: number[]
False-positive rate at each threshold.
truePositiveRate: number[]
True-positive rate at each threshold.
interface PrecisionRecallCurve {
A precision-recall curve.
Properties
thresholds: number[]
Score thresholds, ascending, one per curve point except the final
(recall 0, precision 1) endpoint.
precision: number[]
Precision at each threshold, ending at 1.
recall: number[]
Recall at each threshold, ending at 0.
Functions
function accuracy(yTrue: ArrayLike<Label>, yPred: ArrayLike<Label>): number
Fraction of predictions that match the true label.
import { accuracy } from 'fino:ml/metrics';
console.log(accuracy([1, 0, 1, 1], [1, 0, 0, 1])); // 0.75function precision(
yTrue: ArrayLike<Label>,
yPred: ArrayLike<Label>,
options?: AverageOptions & { average?: Exclude<Average, 'none'> },
): number
function precision(
yTrue: ArrayLike<Label>,
yPred: ArrayLike<Label>,
options?: AverageOptions & { average?: Exclude<Average, 'none'> },
): number
function precision(
yTrue: ArrayLike<Label>,
yPred: ArrayLike<Label>,
options: AverageOptions & { average: 'none' },
): number[]Precision: of everything predicted positive, how much really was.
import { precision } from 'fino:ml/metrics';
console.log(precision([1, 0, 1, 1], [1, 1, 0, 1])); // 0.6666666666666666
console.log(precision(['a', 'b', 'c'], ['a', 'b', 'b'], { average: 'macro' }));function recall(
yTrue: ArrayLike<Label>,
yPred: ArrayLike<Label>,
options?: AverageOptions & { average?: Exclude<Average, 'none'> },
): number
function recall(
yTrue: ArrayLike<Label>,
yPred: ArrayLike<Label>,
options?: AverageOptions & { average?: Exclude<Average, 'none'> },
): number
function recall(
yTrue: ArrayLike<Label>,
yPred: ArrayLike<Label>,
options: AverageOptions & { average: 'none' },
): number[]Recall: of everything that really was positive, how much was found.
import { recall } from 'fino:ml/metrics';
console.log(recall([1, 0, 1, 1], [1, 1, 0, 1])); // 0.6666666666666666function f1Score(
yTrue: ArrayLike<Label>,
yPred: ArrayLike<Label>,
options?: AverageOptions & { average?: Exclude<Average, 'none'> },
): number
function f1Score(
yTrue: ArrayLike<Label>,
yPred: ArrayLike<Label>,
options?: AverageOptions & { average?: Exclude<Average, 'none'> },
): number
function f1Score(
yTrue: ArrayLike<Label>,
yPred: ArrayLike<Label>,
options: AverageOptions & { average: 'none' },
): number[]Harmonic mean of precision and recall.
import { f1Score } from 'fino:ml/metrics';
console.log(f1Score([1, 0, 1, 1], [1, 1, 0, 1])); // 0.6666666666666666function fBetaScore(
yTrue: ArrayLike<Label>,
yPred: ArrayLike<Label>,
beta: number,
options?: AverageOptions & { average?: Exclude<Average, 'none'> },
): number
function fBetaScore(
yTrue: ArrayLike<Label>,
yPred: ArrayLike<Label>,
beta: number,
options?: AverageOptions & { average?: Exclude<Average, 'none'> },
): number
function fBetaScore(
yTrue: ArrayLike<Label>,
yPred: ArrayLike<Label>,
beta: number,
options: AverageOptions & { average: 'none' },
): number[]F-score with beta weighting recall against precision.
beta below 1 favors precision, above 1 favors recall. beta = 2 is
the usual choice when a miss costs more than a false alarm.
import { fBetaScore } from 'fino:ml/metrics';
console.log(fBetaScore([1, 0, 1, 1], [1, 1, 0, 1], 2));function balancedAccuracy(yTrue: ArrayLike<Label>, yPred: ArrayLike<Label>): number
Mean per-class recall, the imbalance-resistant counterpart to accuracy.
import { balancedAccuracy } from 'fino:ml/metrics';
console.log(balancedAccuracy([0, 0, 0, 1], [0, 0, 0, 0])); // 0.5function matthewsCorrCoef(yTrue: ArrayLike<Label>, yPred: ArrayLike<Label>): number
Matthews correlation coefficient, in [-1, 1].
1 is perfect agreement, 0 is chance, -1 is total disagreement. Unlike
F1 it accounts for true negatives, so it does not flatter a model that only
ever predicts the majority class.
import { matthewsCorrCoef } from 'fino:ml/metrics';
console.log(matthewsCorrCoef([1, 1, 0, 0], [1, 1, 0, 0])); // 1function cohenKappa(yTrue: ArrayLike<Label>, yPred: ArrayLike<Label>): number
Cohen's kappa: agreement corrected for what chance would produce.
import { cohenKappa } from 'fino:ml/metrics';
console.log(cohenKappa([1, 1, 0, 0], [1, 0, 0, 0]).toFixed(4)); // 0.5000function logLoss(
yTrue: ArrayLike<Label>,
probabilities: ArrayLike<number>,
options: ProbabilityOptions & { eps?: number } = {},
): number
Cross-entropy of predicted probabilities against true labels.
Lower is better and 0 is perfect. Probabilities are clamped away from the
exact endpoints by eps so that a confident mistake costs a large finite
amount rather than infinity.
import { logLoss } from 'fino:ml/metrics';
console.log(logLoss([1, 0, 1], [0.9, 0.1, 0.8]).toFixed(4)); // 0.1446function rocCurve(
yTrue: ArrayLike<Label>,
scores: ArrayLike<number>,
options: ProbabilityOptions = {},
): RocCurve
Sweep every threshold and report the false/true positive rate at each.
Points are emitted once per distinct score, so tied scores collapse into a single point and the curve stays independent of input order.
import { rocCurve } from 'fino:ml/metrics';
const curve = rocCurve([0, 0, 1, 1], [0.1, 0.4, 0.35, 0.8]);
console.log(curve.truePositiveRate); // [0, 0.5, 0.5, 1, 1]function rocAuc(
yTrue: ArrayLike<Label>,
scores: ArrayLike<number>,
options: ProbabilityOptions = {},
): number
Area under the ROC curve: the chance a random positive outranks a random negative.
0.5 is coin-flip performance and 1 is a perfect ranking. Computed from
tie-corrected ranks, so it is exact rather than trapezoid-approximated, and
unlike threshold metrics it does not depend on a decision cutoff.
Reports 0 when the labels are all positive or all negative, where the
statistic is undefined.
import { rocAuc } from 'fino:ml/metrics';
console.log(rocAuc([0, 0, 1, 1], [0.1, 0.4, 0.35, 0.8])); // 0.75function precisionRecallCurve(
yTrue: ArrayLike<Label>,
scores: ArrayLike<number>,
options: ProbabilityOptions = {},
): PrecisionRecallCurve
Precision and recall at every distinct threshold.
Prefer this over rocCurve when positives are rare: it ignores true
negatives, so a large easy negative class cannot inflate the picture.
import { precisionRecallCurve } from 'fino:ml/metrics';
const curve = precisionRecallCurve([0, 0, 1, 1], [0.1, 0.4, 0.35, 0.8]);
console.log(curve.recall); // [1, 1, 0.5, 0.5, 0]function averagePrecision(
yTrue: ArrayLike<Label>,
scores: ArrayLike<number>,
options: ProbabilityOptions = {},
): number
Average precision: the precision-recall curve summarized as one number.
Each threshold's precision is weighted by the recall it gains, which avoids the optimistic interpolation that trapezoid area under the same curve would introduce.
import { averagePrecision } from 'fino:ml/metrics';
console.log(averagePrecision([0, 0, 1, 1], [0.1, 0.4, 0.35, 0.8])); // 0.8333333333333333