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

126 lines (100 loc) 5.27 kB
import { describe, it } from 'node:test'; import assert from 'node:assert'; import Regression from '../../../lib/analyze/regression/index.js'; function almostEqual(actual, expected, epsilon = 1e-6) { assert.ok(Math.abs(actual - expected) < epsilon, `Expected ${actual}${expected}`); } describe('LinearRegression', () => { it('Simple Linear Regression (Y = 2X)', () => { const table = { X: [1, 2, 3, 4, 5], Y: [2, 4, 6, 8, 10] }; const model = new Regression(table, { yName: 'Y', xNames: ['X'] }).steps[0]; almostEqual(model.coefficients[0], 0); // intercept almostEqual(model.coefficients[1], 2); // slope almostEqual(model.r2, 1); }); it('With Mediator M (X -> M -> Y)', () => { const table = { X: [1, 2, 3, 4, 5, 6], M: [2, 4, 5, 7, 7, 9], Y: [3, 5, 7, 9, 10, 13] }; const model = new Regression(table, { yName: 'Y', xNames: ['X'] }).next(['M']).steps[1]; assert.ok(model.r2 > 0.8, `Expected R² > 0.8, got ${model.r2}`); assert.ok(model.result.Variable.includes('M'), 'Missing M as mediator'); }); it('Regression with low R² (noisy data)', () => { const table = { X: [1, 2, 3, 4, 5], Y: [1, 1.1, 1.2, 0.9, 1] }; const model = new Regression(table, { yName: 'Y', xNames: ['X'] }).steps[0]; assert.ok(model.r2 < 0.5, `Expected low R², got ${model.r2}`); }); it('Returns correct result structure', () => { const table = { X: [1, 2, 3], Y: [2, 4, 6] }; const result = new Regression(table, { yName: 'Y', xNames: ['X'] }).steps[0].result; assert.ok(Array.isArray(result.Variable), 'Variable should be array'); assert.strictEqual(result.Variable.length, 2); assert.strictEqual(result.Coefficient.length, 2); assert.strictEqual(result.StdError.length, 2); assert.strictEqual(result.pValue.length, 2); }); it('predict() returns the same as yHat', () => { const table = { X: [1, 2, 3, 4, 5], Y: [2, 4, 6, 8, 10] }; const model = new Regression(table, { yName: 'Y', xNames: ['X'] }).steps[0]; const predicted = model.predict(model.X); assert.deepStrictEqual(predicted, model.yHat); }); it('Throws on constant X (singular matrix)', () => { const table = { X: [1, 1, 1, 1, 1], Y: [2, 3, 4, 5, 6] }; assert.throws(() => new Regression(table, { yName: 'Y', xNames: ['X'] }).steps[0], /singular|constant/i); }); it('Detects impact of outliers on R²', () => { const table = { X: [1, 2, 3, 4, 100], Y: [2, 4, 6, 8, 15] }; const model = new Regression(table, { yName: 'Y', xNames: ['X'] }).steps[0]; assert.ok(model.r2 < 0.95, `Expected R² < 0.95 due to outlier, got ${model.r2}`); }); it('Throws if predictor does not exist in table', () => { const table = { X: [1, 2, 3], Y: [2, 4, 6] }; assert.throws(() => new Regression(table, { yName: 'Y', xNames: ['Z'] }).steps[0], /undefined|not found/i); }); it('Works with large dataset (1000+ rows)', () => { const X = Array.from({ length: 1000 }, (_, i) => i + 1); const Y = X.map(x => 5 * x + 3 + (Math.random() - 0.5)); const table = { X, Y }; const model = new Regression(table, { yName: 'Y', xNames: ['X'] }).steps[0]; almostEqual(model.coefficients[0], 3, 1); almostEqual(model.coefficients[1], 5, 0.1); assert.ok(model.r2 > 0.99); }); it('Produces near-zero R² when no linear relationship', () => { const table = { X: [1, 2, 3, 4, 5], Y: [5, 3, 6, 2, 4] }; const model = new Regression(table, { yName: 'Y', xNames: ['X'] }).steps[0]; assert.ok(model.r2 < 0.1); }); it('Automatically includes moderator and interaction term', () => { const table = { X: [1, 2, 3, 4, 5], Z: [0, 1, 0, 1, 0], Y: [2, 3, 6, 7, 10] }; const result = new Regression(table, { yName: 'Y', xNames: ['X'] }).next(['Z', 'X*Z']).steps[1].result; assert.ok(result.Variable.includes('Z')); assert.ok(result.Variable.includes('X*Z')); }); it('Autodetect xNames if not provided', () => { const table = { X1: [1, 2, 3, 4, 5], X2: [2, 1, 4, 3, 5], Y: [3, 4, 6, 7, 10] }; const model = new Regression(table, { yName: 'Y' }).steps[0]; assert.deepStrictEqual(model.xNames.sort(), ['X1', 'X2'].sort()); }); it('With Moderator Z (interaction X*Z)', () => { const table = { X: [1, 2, 3, 4, 5], Z: [1, 2, 1, 2, 1], Y: [3, 6, 5, 10, 7] }; const model = new Regression(table, { yName: 'Y', xNames: ['X'] }).next(['Z', 'X*Z']).steps[1]; const interactionName = 'X*Z'; assert.ok(model.result.Variable.includes(interactionName), 'Interaction term missing'); }); }); describe('Linear Regression — edges', () => { it('fits simple line y = 2x + 1', () => { const x = [1, 2, 3, 4]; const y = [3, 5, 7, 9]; const m = new Regression({ x, y }, { yName: 'y', xNames: ['x'] }); const [b0, b1] = m.results[0].Coefficient; assert.ok(Number.isFinite(b0) && Number.isFinite(b1)); assert.ok(Math.abs(b0 - 1) < 1e-9); assert.ok(Math.abs(b1 - 2) < 1e-9); }); });