UNPKG

als-statistics

Version:

A powerful and lightweight JavaScript library for descriptive statistics, regression, clustering, outlier detection, and noise analysis using a flexible table/column architecture.

164 lines (141 loc) 7.37 kB
const Column = require('../column/index') const { MovingAverage, Noice, confidenceInterval, mode, outliersZScore, weightedMean } = require('./instruments/index'); const Comparative = require('../table/instruments/comparative') class RatioColumn extends Column { constructor(values, columnFilter) { super(values, columnFilter, Number) } get sum() { return this.cached('sum', () => this.values.reduce((acc, val) => acc + val, 0)) } get mean() { return this.cached('mean', () => this.sum / this.n) } get sorted() { return this.cached('sorted', () => this.values.slice().sort((a, b) => a - b)) } get min() { return this.cached('min', () => this.sorted[0]) } get max() { return this.cached('max', () => this.sorted[this.n - 1]) } get range() { return this.cached('range', () => this.max - this.min) } get variance() { return this.variancePopulation } get stdDev() { return this.stdDevPopulation } get skewness() { return this.skewnessPopulation } get kurtosis() { return this.kurtosisPopulation } get cv() { return this.cached('cv', () => this.mean === 0 ? 0 : this.stdDevPopulation / this.mean) } // Coefficient of Variation - stdDevPopulation / mean get iqr() { return this.cached('iqr', () => this.q3 - this.q1) } outliersZScore(threshold = 3, twoFactors = true) { return outliersZScore(this, threshold, twoFactors) } weightedMean(weights) { return weightedMean(weights, this.values) } noice() { return new Noice(this) } ma(windowSize) { return new MovingAverage(this, windowSize).result } get variancePopulation() { return this.cached('variancePopulation', () => { return this.values.reduce((acc, val) => acc + Math.pow(val - this.mean, 2), 0) / this.n; }); } get stdDevPopulation() { return this.cached('stdDevPopulation', () => Math.sqrt(this.variancePopulation)) } get zScores() { return this.cached('zScores', () => { const { mean, stdDevPopulation, values } = this; if (stdDevPopulation === 0) return values.map(v => 0); return values.map(v => (v - mean) / stdDevPopulation); }); } get skewnessPopulation() { return this.cached('skewnessPopulation', () => { const { values, mean, stdDevPopulation, n } = this; if (stdDevPopulation === 0) return 0; // If stdDev = 0 return values.reduce((acc, v) => acc + ((v - mean) / stdDevPopulation) ** 3, 0) / n; }); } get kurtosisPopulation() { return this.cached('kurtosisPopulation', () => { const sumZ4 = this.zScores.reduce((acc, z) => acc + z ** 4, 0); return (sumZ4 / this.n) - 3; }); } get varianceSample() { // Sample (selected): Bessel's correction. s^2 = (1/(n-1)) * sum( (x - mean)^2 ) return this.cached('varianceSample', () => { if (this.n < 2) return 0; return this.values.reduce((acc, val) => acc + (val - this.mean) ** 2, 0) / (this.n - 1); }); } get stdDevSample() { return this.cached('stdDevSample', () => Math.sqrt(this.varianceSample)) } get relativeDispersion() { return this.median === 0 ? 0 : this.stdDev / this.median } get skewnessSample() { return this.cached('skewnessSample', () => { if (this.n < 3) return 0; const { mean, values, n, stdDevSample } = this; const numerator = values.reduce((acc, val) => acc + ((val - mean) / stdDevSample) ** 3, 0); const factor = n / ((n - 1) * (n - 2)); return factor * numerator; }); } get kurtosisSample() { return this.cached('kurtosisSample', () => { if (this.n < 4) return 0; const { mean, values, n, stdDevSample } = this; 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; }); } get mode() { return this.cached('mode', () => mode(this.values)) } get normalizedValues() { // Min-Max scaling return this.cached('normalizedValues', () => { const { min, range, values } = this; if (range === 0) return values.map(() => 0); return values.map(v => (v - min) / range); }); } get geometricMean() { // GM = (П(i=1..n) x_i)^(1/n). Only for x_i > 0. return this.cached('geometricMean', () => { if (!this.values.every(v => v > 0)) return NaN; const logSum = this.values.reduce((acc, v) => acc + Math.log(v), 0); return Math.exp(logSum / this.n); }); } get harmonicMean() { // HM = n / Σ(1/x_i) . Only for x_i > 0 return this.cached('harmonicMean', () => { if (!this.values.every(v => v > 0)) return NaN; const denom = this.values.reduce((acc, v) => acc + 1 / v, 0); return this.n / denom; }); } get flatness() { return this.cached('flatness', () => this.geometricMean / this.mean) } get sumOfSquares() { return this.cached('sumOfSquares', () => this.values.reduce((acc, v) => acc + v ** 2, 0)) } // Сумма квадратов (энергетическая норма). Σ(x_i^2) get confidenceInterval95() { return this.cached('confidenceInterval95', () => confidenceInterval(this)) } get outliersIQR() { const { q1, q3, iqr, values } = this; const lowerBound = q1 - 1.5 * iqr, upperBound = q3 + 1.5 * iqr; return values.filter(v => v < lowerBound || v > upperBound); } get noiseStability() { return this.cached('noiseStability', () => { const { values, mean, n } = this; if (n < 2) return Array(n).fill(0); const variance = values.reduce((acc, v) => acc + (v - mean) ** 2, 0) / n; const stdDev = Math.sqrt(variance); return stdDev; }); } get spectralPowerDensityArray() { return this.cached('spectralPowerDensityArray', () => { const { values, sumOfSquares } = this; if (sumOfSquares === 0) return values.map(() => 0); return values.map(v => (v ** 2) / sumOfSquares); }); } get spectralPowerDensityMetric() { return this.cached('spectralPowerDensityMetric', () => { const spdArray = this.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 → голос }); } get xValues() { return this.cached('xValues', () => Array.from({ length: this.n }, (_, i) => i + 1)) } regressionSlope(customX = null) { const x = customX || this.xValues; if (!Array.isArray(x) || x.length !== this.n) throw new Error("x must be an array of same length as values"); const xCol = new RatioColumn(x); const cov = new Comparative(xCol, this).covarianceSample; const varX = xCol.varianceSample; return varX === 0 ? 0 : cov / varX; } } module.exports = RatioColumn