UNPKG

als-statistics

Version:

Modular JS statistics toolkit for Node.js and the browser: descriptive stats, correlations (Pearson/Spearman/Kendall), t-tests & ANOVA (Student/Welch), reliability (Cronbach’s alpha), regression (linear/logistic), clustering (DBSCAN/HDBSCAN), and table/co

262 lines (229 loc) 11.2 kB
const nd = v => v === undefined import MovingAverage from './ma.js'; import confidenceInterval from './confidence.js'; import mode from './mode.js'; import outliersZScore from './z-outliers.js'; import weightedMean from './weighted-mean.js'; import slopeByIndex from './slope-by-index.js'; import { frequencies, recode } from './frequencies.js'; class Stats { static sorted({ values }) { return values.slice().sort((a, b) => a - b) } static sum({ values }) { return values.reduce((acc, val) => acc + val, 0) } static mean({ values, sum }) { return ((sum ?? Stats.sum({ values })) / values.length) } static mode({ values }) { return mode(values) } static slope({ values }) { return slopeByIndex({ values }) } static confidenceInterval({ mean, stdDevSample, values }) { if (nd(mean)) mean = Stats.mean({ values }) if (nd(stdDevSample)) stdDevSample = Stats.stdDevSample({ values }) return confidenceInterval({ n: values.length, mean, stdDevSample }) } static weightedMean({ values }, weights) { return weightedMean(weights, values) } static outliersZScore({ values, zScores }, threshold, twoFactors, below) { if (nd(threshold)) threshold = 3 if (nd(twoFactors)) twoFactors = true if (nd(zScores)) zScores = Stats.zScores({ values }) return outliersZScore({ zScores, values }, threshold, twoFactors, below) } static ma({ values }, windowSize) { return new MovingAverage({ values, n: values.length }, windowSize).result } static sumOfSquares({ values }) { return values.reduce((acc, v) => acc + v ** 2, 0) } // Сумма квадратов (энергетическая норма). Σ(x_i^2) static varianceSample({ values, mean }) { // Sample (selected): Bessel's correction. s^2 = (1/(n-1)) * sum( (x - mean)^2 ) const n = values.length if (nd(mean)) mean = Stats.mean({ values }) return n < 2 ? 0 : values.reduce((acc, val) => acc + (val - mean) ** 2, 0) / (n - 1); } static variance({ values, mean }) { if (nd(mean)) mean = Stats.mean({ values }) return values.reduce((acc, v) => acc + Math.pow(v - mean, 2), 0) / values.length; } static stdDev({ variance, values }) { if (nd(variance)) variance = Stats.variance({ values }) return Math.sqrt(variance) } static stdDevSample({ values, varianceSample }) { if (nd(varianceSample)) varianceSample = Stats.varianceSample({ values }) return Math.sqrt(varianceSample) } static cv({ mean, stdDev, values }) { if (nd(mean)) mean = Stats.mean({ values }) if (nd(stdDev)) stdDev = Stats.stdDev({ values }) return mean === 0 ? 0 : stdDev / mean } static zScores({ values, stdDev, mean },sample = false) { if (nd(mean)) mean = Stats.mean({ values }) if (nd(stdDev)) { stdDev = sample ? Stats.stdDevSample({ values }) : Stats.stdDev({ values }) } return stdDev === 0 ? values.map(v => 0) : values.map(v => (v - mean) / stdDev) } static skewness({ values, mean, stdDev }) { const n = values.length if (nd(mean)) mean = Stats.mean({ values }) if (nd(stdDev)) stdDev = Stats.stdDev({ values }) if (stdDev === 0) return 0; // If stdDev = 0 return values.reduce((acc, v) => acc + ((v - mean) / stdDev) ** 3, 0) / n; } static kurtosis({ zScores, values }) { if (nd(zScores)) zScores = Stats.zScores({ values }) return (zScores.reduce((acc, z) => acc + z ** 4, 0) / zScores.length) - 3; } static skewnessSample({ mean, values, stdDevSample }) { const n = values.length if (nd(mean)) mean = Stats.mean({ values }) if (nd(stdDevSample)) stdDevSample = Stats.stdDevSample({ values }) if (n < 3) return 0; const numerator = values.reduce((acc, val) => acc + ((val - mean) / stdDevSample) ** 3, 0); const factor = n / ((n - 1) * (n - 2)); return factor * numerator; } static kurtosisSample({ mean, values, stdDevSample }) { const n = values.length if (nd(mean)) mean = Stats.mean({ values }) if (nd(stdDevSample)) stdDevSample = Stats.stdDevSample({ values }) if (n < 4) return 0; const z4sum = values.reduce((acc, val) => acc + Math.pow((val - mean) / stdDevSample, 4), 0); const a = (n * (n + 1)) / ((n - 1) * (n - 2) * (n - 3)); const b = 3 * Math.pow(n - 1, 2) / ((n - 2) * (n - 3)); return a * z4sum - b; } static geometricMean({ values, min = 1e-12 }) { const n = values.length; if (!n) return NaN; let sumLog = 0; for (let v of values) { if (!Number.isFinite(v)) return NaN; if (v < min) v = min; sumLog += Math.log(v); } return Math.exp(sumLog / n); } static flatness({ geometricMean, mean, values }) { if (nd(geometricMean)) geometricMean = Stats.geometricMean({ values }) if (nd(mean)) mean = Stats.mean({ values }) return mean === 0 ? 0 : geometricMean / mean; } static harmonicMean({ values }) { // HM = n / Σ(1/x_i) . Only for x_i > 0 const n = values.length if (!values.every(v => v > 0)) return NaN; const denom = values.reduce((acc, v) => acc + 1 / v, 0); return n / denom; } static noiseStability({ values, mean }) { const n = values.length if (nd(mean)) mean = Stats.mean({ values }) if (n < 2) return 0; const variance = values.reduce((acc, v) => acc + (v - mean) ** 2, 0) / n; return Math.sqrt(variance); // stdDev } static spectralPowerDensityArray({ values, sumOfSquares }) { if (nd(sumOfSquares)) sumOfSquares = Stats.sumOfSquares({ values }) return sumOfSquares === 0 ? values.map(() => 0) : values.map(v => (v ** 2) / sumOfSquares); } static spectralPowerDensityMetric({ spectralPowerDensityArray, values }) { if (nd(spectralPowerDensityArray)) spectralPowerDensityArray = Stats.spectralPowerDensityArray({ values }) const spdArray = spectralPowerDensityArray; const geometricMean = Math.exp(spdArray.reduce((sum, x) => sum + Math.log(x + Number.EPSILON), 0) / spdArray.length); const arithmeticMean = spdArray.reduce((sum, x) => sum + x, 0) / spdArray.length; return geometricMean / arithmeticMean; // Близкое к 1 → равномерный шум, ближе к 0 → голос } static zScoresSorted({ zScores: zs, values }) { if (nd(zs)) zs = Stats.zScores({ values }) const objs = zs.map((z, i) => ({ z, i })).sort((a, b) => a.z - b.z); const zScores = [], indexes = []; objs.forEach(({ z, i }) => { zScores.push(z); indexes.push(i); }); return { zScores, indexes } } static median({ values, sorted }) { const s = sorted ? sorted : Stats.sorted({ values }); const m = s.length >> 1; return s.length % 2 ? s[m] : (s[m - 1] + s[m]) / 2; } static percentile({ values, sorted }, q) { if (q > 1) q = q / 100 const s = sorted ? sorted : Stats.sorted({ values }); const n = s.length; if (!n) return undefined; const pos = (n - 1) * q; const i = Math.floor(pos), f = pos - i; return f ? s[i] * (1 - f) + s[i + 1] * f : s[i]; } static q1(obj) { return Stats.percentile(obj, 25) } static q3(obj) { return Stats.percentile(obj, 75) } static p10(obj) { return Stats.percentile(obj, 10) } static p90(obj) { return Stats.percentile(obj, 90) } static frequencies({ values, labels }) { return frequencies({ values, labels }) } static recode({ values }, ranges) { return recode({ values }, ranges) } static relativeFrequencies({ frequencies, values, labels }) { // Relative frequencies (the proportion of each value relative to the total) const n = values.length, relFreq = {} if (nd(frequencies)) frequencies = Stats.frequencies({ values, labels }) for (const key in frequencies) { relFreq[key] = frequencies[key] / n } return relFreq; } static mad({ sorted, median, values }) { // Median Absolute Deviation if (values.length === 0) return 0; if (!sorted) sorted = Stats.sorted({ values }); if (nd(median)) median = Stats.median({ values, sorted }) const deviations = sorted.map(x => Math.abs(x - median)).sort((a, b) => a - b); const m = Math.floor(deviations.length / 2); return (deviations.length % 2 === 0) ? (deviations[m - 1] + deviations[m]) / 2 : deviations[m]; } static relativeDispersion({ values, median, stdDev }) { if (nd(median)) median = Stats.median({ values }) if (nd(stdDev)) stdDev = Stats.stdDev({ values }) return median === 0 ? 0 : stdDev / median } static iqr({ values, q1, q3, sorted }) { if (nd(q1)) q1 = Stats.q1({ values, sorted }) if (nd(q3)) q3 = Stats.q3({ values, sorted }) return q3 - q1 } static range({ min, max, sorted, values }) { if (nd(min) || nd(max)) { if (nd(sorted)) sorted = Stats.sorted({ values }) min = Stats.min({ sorted }) max = Stats.max({ sorted }) } return max - min } static min({ sorted, values }) { if (nd(sorted)) sorted = Stats.sorted({ values }) return sorted[0] } static max({ sorted, values }) { if (nd(sorted)) sorted = Stats.sorted({ values }) return sorted[sorted.length - 1] } static zScore({ stdDev, mean, values }, v) { if (nd(stdDev)) stdDev = Stats.stdDev({ values }) if (nd(mean)) mean = Stats.stdDev({ values }) return stdDev === 0 ? 0 : (v - mean) / stdDev } static normalizedValues({ values, range, min }) { if (nd(min)) min = Stats.min({ values }) if (nd(range)) range = Stats.range({ values, min }) return range === 0 ? values.map(() => 0) : values.map(v => (v - min) / range); } static outliersIQR({ q1, q3, iqr, values }) { if (nd(q1)) { const sorted = Stats.sorted({ values }) q1 = Stats.q1({ values, sorted }) q3 = Stats.q3({ values, sorted }) iqr = Stats.iqr({ values, sorted }) } const lowerBound = q1 - 1.5 * iqr, upperBound = q3 + 1.5 * iqr; return values.filter(v => v < lowerBound || v > upperBound); } static xValues = ({ values }) => Array.from({ length: values.length }, (_, i) => i + 1) static regressionSlope({ values, xValues, mean }, customX) { const x = customX || xValues || Stats.xValues; if (!Array.isArray(x) || x.length !== values.length) throw new Error("x must be an array of same length as values"); if (nd(mean)) mean = Stats.mean const n = values.length const mean2 = Stats.mean({ values: x }) const varianceSample = Stats.varianceSample({ values: x }); let sumCov = 0; for (let i = 0; i < n; i++) { sumCov += (values[i] - mean) * (x[i] - mean2) } const cov = n < 2 ? 0 : sumCov / (n - 1) return varianceSample === 0 ? 0 : cov / varianceSample; } } export default Stats