Statistics Calculator — TypeScript source
Compute descriptive statistics - count, sum, mean, median, mode, min/max, range, variance, standard deviation, and quartiles (Q1/Q3/IQR) - from any list of numbers. Tolerates mixed separators and flags unparseable tokens. Choose sample (n−1) or population (n) variance. Everything runs 100% client-side.
This is the TypeScript implementation — the same logic the interactive tool runs, in a shareable, citable form.
// Pure logic for the Statistics Calculator. No React, no DOM - the unit-test
// surface. Fully deterministic: every function depends only on its inputs.
/** Outcome of splitting a free-form number list into valid + invalid tokens. */
export interface ParseResult {
/** Finite numbers, in the order they appeared. */
values: number[];
/** Tokens that could not be parsed as finite numbers, in order. */
invalid: string[];
}
/** Descriptive statistics over a sample of numbers. Numeric fields are `NaN`
* when `count === 0`; `mode` is `[]` when there is no mode. */
export interface Stats {
count: number;
sum: number;
mean: number;
median: number;
/** Most frequent value(s), ascending. `[]` when uniform / no mode. */
mode: number[];
min: number;
max: number;
range: number;
variance: number;
stddev: number;
q1: number;
q3: number;
iqr: number;
}
/**
* Parse a free-form number list into finite values and unparseable tokens.
* Separators are any run of whitespace and/or commas. Tokens like `Infinity`
* and `NaN` are not finite, so they land in `invalid`. Empty input yields no
* values and no invalid tokens.
*/
export function parseNumbers(input: string): ParseResult {
if (!input.trim()) return { values: [], invalid: [] };
const tokens = input.split(/[\s,]+/).filter((t) => t.length > 0);
const values: number[] = [];
const invalid: string[] = [];
for (const tok of tokens) {
const n = Number(tok);
if (Number.isFinite(n)) values.push(n);
else invalid.push(tok);
}
return { values, invalid };
}
/**
* Linear-interpolation quantile (R-7 / NumPy / Excel PERCENTILE convention).
* `sorted` must be ascending and non-empty; `p` in [0, 1].
*/
function quantile(sorted: number[], p: number): number {
const n = sorted.length;
const h = (n - 1) * p;
const lower = Math.floor(h);
const upper = Math.ceil(h);
if (lower === upper) return sorted[lower];
return sorted[lower] + (h - lower) * (sorted[upper] - sorted[lower]);
}
/**
* Most frequent value(s), ascending. Returns `[]` when there is no mode - i.e.
* when every value is distinct, or when all distinct values share the same
* frequency (a flat / uniform distribution with ≥2 distinct values). A single
* repeated value (e.g. `[5,5,5]`) does have a mode: `[5]`.
*/
function computeMode(values: number[]): number[] {
const freq = new Map<number, number>();
for (const v of values) freq.set(v, (freq.get(v) ?? 0) + 1);
// A single distinct value is always the mode (covers [7] and [5,5,5]).
if (freq.size === 1) return [...freq.keys()];
const max = Math.max(...freq.values());
const modes = [...freq.entries()].filter(([, c]) => c === max).map(([v]) => v);
// All distinct values share the max frequency → uniform → no mode.
if (modes.length === freq.size) return [];
return modes.sort((a, b) => a - b);
}
/** An all-`NaN` Stats block for the empty-input case (count 0). */
const EMPTY_STATS: Stats = {
count: 0,
sum: NaN,
mean: NaN,
median: NaN,
mode: [],
min: NaN,
max: NaN,
range: NaN,
variance: NaN,
stddev: NaN,
q1: NaN,
q3: NaN,
iqr: NaN,
};
/**
* Compute descriptive statistics over `values`. With `sample = true` (default)
* variance/stddev use the sample estimator (n−1); with `sample = false` they
* use the population estimator (n). Empty input returns count 0 with every
* numeric field `NaN` and `mode: []` - never throws.
*/
export function summarize(values: number[], sample = true): Stats {
const n = values.length;
if (n === 0) return { ...EMPTY_STATS };
const sorted = [...values].sort((a, b) => a - b);
const sum = values.reduce((a, b) => a + b, 0);
const mean = sum / n;
const min = sorted[0];
const max = sorted[n - 1];
const median = quantile(sorted, 0.5);
const q1 = quantile(sorted, 0.25);
const q3 = quantile(sorted, 0.75);
// Sum of squared deviations from the mean.
const ss = values.reduce((a, x) => a + (x - mean) ** 2, 0);
const variance = sample ? (n >= 2 ? ss / (n - 1) : NaN) : ss / n;
const stddev = Number.isFinite(variance) ? Math.sqrt(variance) : NaN;
return {
count: n,
sum,
mean,
median,
mode: computeMode(values),
min,
max,
range: max - min,
variance,
stddev,
q1,
q3,
iqr: q3 - q1,
};
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →