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.
233 lines (194 loc) • 8.95 kB
JavaScript
const { describe, it } = require('node:test');
const assert = require('node:assert');
const RatioColumn = require('../../lib/ratio-column/index');
describe('RatioColumn', () => {
it('should create a ratio column with valid numeric values', () => {
const column = new RatioColumn([10, 20, 30, 40, 50]);
assert.deepStrictEqual(column.values, [10, 20, 30, 40, 50]);
});
it('should compute correct sum', () => {
const column = new RatioColumn([10, 20, 30, 40, 50]);
assert.strictEqual(column.sum, 150);
});
it('should compute correct mean', () => {
const column = new RatioColumn([10, 20, 30, 40, 50]);
assert.strictEqual(column.mean, 30);
});
it('should return correct min and max values', () => {
const column = new RatioColumn([10, 20, 30, 40, 50]);
assert.strictEqual(column.min, 10);
assert.strictEqual(column.max, 50);
});
it('should compute correct range', () => {
const column = new RatioColumn([10, 20, 30, 40, 50]);
assert.strictEqual(column.range, 40);
});
it('should compute variance and standard deviation (sample)', () => {
const column = new RatioColumn([10, 20, 30, 40, 50]);
assert.strictEqual(column.varianceSample.toFixed(2), '250.00');
assert.strictEqual(column.stdDevSample.toFixed(2), '15.81');
});
it('should compute variance and standard deviation (population)', () => {
const column = new RatioColumn([10, 20, 30, 40, 50]);
assert.strictEqual(column.variancePopulation.toFixed(2), '200.00');
assert.strictEqual(column.stdDevPopulation.toFixed(2), '14.14');
});
it('should compute correct z-scores', () => {
const column = new RatioColumn([10, 20, 30, 40, 50]);
assert.deepStrictEqual(column.zScores.map(v => v.toFixed(2)), [
'-1.41', '-0.71', '0.00', '0.71', '1.41'
]);
});
it('should return correct quartiles', () => {
const column = new RatioColumn([10, 20, 30, 40, 50]);
assert.strictEqual(column.q1, 20);
assert.strictEqual(column.q3, 40);
assert.strictEqual(column.iqr, 20);
});
it('should return correct median', () => {
const column = new RatioColumn([10, 20, 30, 40, 50]);
assert.strictEqual(column.median, 30);
});
it('should compute mode correctly', () => {
const column1 = new RatioColumn([1, 2, 2, 3, 4]);
assert.deepStrictEqual(column1.mode, [2]);
const column2 = new RatioColumn([1, 2, 3, 4, 5]);
assert.deepStrictEqual(column2.mode, []);
});
it('should compute geometric mean', () => {
const column = new RatioColumn([1, 3, 9, 27, 81]);
assert.strictEqual(column.geometricMean.toFixed(2), '9.00');
});
it('should compute harmonic mean', () => {
const column = new RatioColumn([1, 2, 3, 4, 5]);
assert.strictEqual(column.harmonicMean.toFixed(2), '2.19');
});
it('should compute skewness and kurtosis (population and sample)', () => {
const column = new RatioColumn([10, 20, 30, 40, 50, 100]);
assert.strictEqual(column.skewnessPopulation.toFixed(2), '1.05');
assert.strictEqual(column.kurtosisPopulation.toFixed(2), '-0.02');
assert.strictEqual(column.skewnessSample.toFixed(2), '1.44');
assert.strictEqual(column.kurtosisSample.toFixed(2), '2.44');
});
it('should compute coefficient of variation (CV)', () => {
const column = new RatioColumn([10, 20, 30, 40, 50]);
assert.strictEqual(column.cv.toFixed(2), '0.47');
});
it('should detect outliers using z-score', () => {
const column = new RatioColumn([10, 20, 30, 40, 50, 100]);
const outliers = column.outliersZScore(2);
assert.deepStrictEqual(outliers.values, [100]);
assert.deepStrictEqual(outliers.indexes, [5]);
assert.deepStrictEqual(outliers.zScores.map(v => v.toFixed(2)), ['2.00']);
});
it('should detect outliers using IQR method', () => {
const column = new RatioColumn([10, 20, 30, 40, 50, 100]);
assert.deepStrictEqual(column.outliersIQR, [100]);
});
it('should compute confidence interval 95%', () => {
const column = new RatioColumn([10, 20, 30, 40, 50]);
const ci = column.confidenceInterval95;
assert.ok(ci.low < ci.high);
assert.ok(ci.width > 0);
});
it('should compute weighted mean correctly', () => {
const column = new RatioColumn([10, 20, 30, 40, 50]);
const weights = [1, 2, 3, 4, 5];
assert.strictEqual(column.weightedMean(weights).toFixed(2), '36.67');
});
it('should compute noise stability', () => {
const column = new RatioColumn([10, 20, 30, 40, 50, 100]);
assert.ok(column.noiseStability > 0);
});
it('should compute spectral power density metrics', () => {
const column = new RatioColumn([10, 20, 30, 40, 50, 100]);
assert.strictEqual(column.spectralPowerDensityArray.length, column.n);
assert.ok(column.spectralPowerDensityMetric > 0);
});
it('should correctly normalize values using Min-Max scaling', () => {
const column = new RatioColumn([10, 20, 30, 40, 50, 100]);
const expectedNormalized = [
(10 - 10) / (100 - 10),
(20 - 10) / (100 - 10),
(30 - 10) / (100 - 10),
(40 - 10) / (100 - 10),
(50 - 10) / (100 - 10),
(100 - 10) / (100 - 10)
];
assert.deepStrictEqual(column.normalizedValues.map(v => v.toFixed(2)), expectedNormalized.map(v => v.toFixed(2)));
});
it('should return an array of zeros if all values are the same', () => {
const column = new RatioColumn([5, 5, 5, 5, 5]);
assert.deepStrictEqual(column.normalizedValues, [0, 0, 0, 0, 0]);
});
it('should return 0 for standard deviation if all values are identical', () => {
const column = new RatioColumn([5, 5, 5, 5, 5]);
assert.strictEqual(column.stdDevPopulation, 0);
});
it('should return NaN for harmonic mean if any value is non-positive', () => {
const column = new RatioColumn([5, -10, 15]);
assert(Number.isNaN(column.harmonicMean));
});
it('should compute flatness correctly', () => {
const column = new RatioColumn([1, 3, 9, 27, 81]);
assert.strictEqual(column.flatness.toFixed(2), '0.37'); // geometricMean / mean ≈ 9 / 24.2
});
it('should compute spectral power density metric', () => {
const column = new RatioColumn([10, 20, 30]);
const spdMetric = column.spectralPowerDensityMetric;
assert.ok(spdMetric > 0 && spdMetric < 1); // Ожидаем значение между 0 и 1
});
it('should compute noise stability with single value', () => {
const column = new RatioColumn([5]);
assert.deepStrictEqual(column.noiseStability, [0]);
});
it('should return zeros for normalized values with zero range', () => {
const column = new RatioColumn([5, 5, 5]);
assert.deepStrictEqual(column.normalizedValues, [0, 0, 0]);
});
it('should detect no outliers with IQR', () => {
const column = new RatioColumn([1, 2, 3, 4, 5]);
assert.deepStrictEqual(column.outliersIQR, []);
});
});
describe('xValues', () => {
it('xValues returns [1, 2, ..., n]', () => {
const col = new RatioColumn([5, 10, 15, 20]);
assert.deepStrictEqual(col.xValues, [1, 2, 3, 4]);
});
it('xValues is cached (same reference)', () => {
const col = new RatioColumn([1, 2, 3]);
const first = col.xValues;
const second = col.xValues;
assert.strictEqual(first, second);
});
});
describe('regressionSlope', () => {
it('regressionSlope() matches perfect slope with default xValues', () => {
const col = new RatioColumn([10, 20, 30, 40]); // perfect slope 10
const slope = col.regressionSlope(); // X: [1,2,3,4]
assert.ok(Math.abs(slope - 10) < 1e-6);
});
it('regressionSlope() works with customX', () => {
const col = new RatioColumn([3, 6, 9]);
const slope = col.regressionSlope([1, 2, 3]);
assert.ok(Math.abs(slope - 3) < 1e-6);
});
it('regressionSlope() returns 0 for constant X', () => {
const col = new RatioColumn([10, 20, 30]);
const slope = col.regressionSlope([5, 5, 5]);
assert.strictEqual(slope, 0);
});
it('regressionSlope() throws if customX length mismatch', () => {
const col = new RatioColumn([1, 2, 3]);
assert.throws(() => {
col.regressionSlope([1, 2]); // wrong length
}, /same length/i);
});
it('regressionSlope() throws if customX is not an array', () => {
const col = new RatioColumn([1, 2, 3]);
assert.throws(() => {
col.regressionSlope("123");
}, /array/i);
});
})