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
JavaScript
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