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

129 lines (105 loc) 5.56 kB
import { describe, it } from 'node:test'; import assert from 'node:assert'; import Regression from '../../../lib/analyze/regression/index.js'; function in01(x) { return x >= 0 && x <= 1; } describe('LogisticRegression (wrapper with steps)', () => { it('autodetects predictors when xNames omitted', () => { const table = { X1: [0, 1, 2, 3, 4], X2: [1, 1, 1, 0, 0], Y: [0, 0, 0, 1, 1], }; const model = new Regression(table, { yName: 'Y', xNames: undefined, type: 'logistic' }); // без xNames const step0 = model.steps[0]; // X1 и X2 должны быть выбраны (исключая Y) assert.deepStrictEqual(step0.xNames.sort(), ['X1', 'X2'].sort()); assert.strictEqual(step0.yName, 'Y'); assert.strictEqual(step0.step, 0); }); it('learns a simple separable boundary (accuracy >= 0.8)', () => { const table = { X: [-2, -1, 0, 1, 2], Y: [0, 0, 0, 1, 1], }; const model = new Regression(table, { yName: 'Y', xNames: ['X'], type: 'logistic' }); const step0 = model.steps[0]; assert.ok(step0.accuracy >= 0.8, `Expected accuracy >= 0.8, got ${step0.accuracy}`); assert.strictEqual(step0.coefficients.length, 2); // [Intercept, X] assert.ok(step0.coefficients.every(Number.isFinite)); }); it('results returns array with proper structure for each step', () => { const table = { X: [0, 1, 2, 3, 4], Y: [0, 0, 0, 1, 1], }; const reg = new Regression(table, { yName: 'Y', xNames: ['X'], type: 'logistic' }).next([]); // создадим 2 шага (второй без новых предикторов) const results = reg.results; assert.ok(Array.isArray(results)); assert.strictEqual(results.length, 2); for (const r of results) { assert.ok(Array.isArray(r.Variable)); assert.ok(Array.isArray(r.Coefficient)); assert.strictEqual(r.Variable.length, r.Coefficient.length); assert.ok(r.n > 0); assert.ok(r.step >= 0); assert.ok(r.Accuracy >= 0 && r.Accuracy <= 1); } }); it('throws if next() receives unknown predictor name (non-interaction)', () => { const table = { X: [0, 1, 2, 3, 4], Y: [0, 0, 0, 1, 1], }; const reg = new Regression(table, { yName: 'Y', xNames: ['X'], type: 'logistic' }); assert.throws(() => reg.next(['Q']).steps[1], /undefined|values|Cannot read/i); }); it('next() adds moderator and interaction on cloned table', () => { const table = { X: [0, 1, 2, 3, 4], Z: [0, 1, 0, 1, 0], Y: [0, 0, 0, 1, 1] }; const reg = new Regression(table, { yName: 'Y', xNames: ['X'], type: 'logistic' }); const beforeCols = Object.keys(table); reg.next(['Z', 'X*Z']); // добавляем модератор и интеракцию const step1 = reg.steps[1]; // в модели появился новый признак и интеракция assert.ok(step1.xNames.includes('Z')); assert.ok(step1.xNames.includes('X*Z')); // интеракция добавилась в КЛОН step1.table, исходная таблица не изменилась assert.ok(!Object.keys(table).includes('X*Z'), 'Original table should not have X*Z'); assert.ok(Object.keys(step1.columns).includes('X*Z'), 'Cloned table should have X*Z'); // и точность посчиталась корректно (в [0,1]) assert.ok(step1.accuracy >= 0 && step1.accuracy <= 1); assert.deepStrictEqual(Object.keys(table), beforeCols); // исходник не мутирован }); }); describe('Regression.LogisticRegression (core)', () => { it('predictProba returns values in [0,1] and predict returns 0/1', () => { const table = { X: [-2, -1, 0, 1, 2], Y: [0, 0, 0, 1, 1], }; const core = new Regression.LogisticRegression(table, 'Y', ['X'], 'logistic').calculate(); const proba = core.predictProba(core.X); assert.strictEqual(proba.length, core.n); assert.ok(proba.every(p => in01(p))); const yhat = core.predict(core.X, 0.5); assert.strictEqual(yhat.length, core.n); assert.ok(yhat.every(v => v === 0 || v === 1)); // согласованность с рассчитанной точностью const correct = yhat.reduce((acc, v, i) => acc + (v === core.y[i] ? 1 : 0), 0) / core.n; assert.ok(Math.abs(correct - core.accuracy) < 1e-12); }); it('works with two predictors and converges to sensible accuracy', () => { const table = { X1: [0, 1, 2, 3, 4, 5], X2: [1, 0, 1, 0, 1, 0], Y: [0, 0, 0, 1, 1, 1], }; const core = new Regression.LogisticRegression(table, 'Y', ['X1', 'X2'], 0).calculate(); assert.ok(core.accuracy >= 0.66, `Expected accuracy >= 0.66, got ${core.accuracy}`); assert.strictEqual(core.coefficients.length, 3); // [Intercept, X1, X2] }); it('throws if used with empty xNames (defensive check recommended)', () => { const table = { Y: [0, 1, 0, 1], X: [1, 2, 3, 4] }; // сейчас без твоего дополнительного guard-а это упадёт TypeError-ом — тест ожидает throw assert.throws(() => new Regression.LogisticRegression(table, 'Y', [], 0).calculate()); }); });