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 (102 loc) 4.78 kB
import { describe, it } from 'node:test'; import assert from 'node:assert'; import Dbscan from '../../../lib/analyze/dbscan/dbscan.js'; describe('Dbscan', () => { it('should throw an error if the table has less than 2 columns', () => { assert.throws(() => new Dbscan({ a: [1, 2, 3] }, { eps: 0.4, minPts: 3, metric: 'mad' })); }); it('should create multiple clusters if columns are weakly correlated', () => { const table = { A: [1, 2, 3, 4, 5], B: [10, 20, 30, 40, 50], C: [100, 200, 300, 400, 500], D: [1000, 2000, 3000, 4000, 5000], } const eps = 0.5; // Уменьшаем `eps`, чтобы точки не объединялись в 1 кластер const minPts = 1; // Уменьшаем `minPts`, чтобы кластеры могли формироваться const dbscan = new Dbscan(table, { eps, minPts }); // console.log('Distance matrix:', JSON.stringify(dbscan.distances, null, 2)); // console.log('Labels:', dbscan.labels); // console.log('Clusters count:', dbscan.clusters.length); const uniqueClusters = new Set(dbscan.labels.filter(label => label > 0)); assert.ok(uniqueClusters.size >= 2, `Ожидалось >=2 кластера, а получили ${uniqueClusters.size}`); assert.ok(dbscan.clusters.length >= 2, `Ожидалось >=2 кластера, а получили ${dbscan.clusters.length}`); }); it('should handle self-clustering correctly', () => { const table = { A: [1, 2, 3, 4, 5], B: [1, 2, 3, 4, 5], C: [1, 2, 3, 4, 5], } const dbscan = new Dbscan(table, { eps: 0.4, minPts: 2 }); // Все три колонки должны попасть в один кластер assert.strictEqual(dbscan.labels[0], dbscan.labels[1]); assert.strictEqual(dbscan.labels[1], dbscan.labels[2]); assert.strictEqual(dbscan.clusters.length, 1); }); it('should correctly form a cluster with exact minPts', () => { const table = { A: [1, 2, 3], B: [2, 3, 4], C: [3, 4, 5], } const dbscan = new Dbscan(table, { eps: 0.3, minPts: 3 }); assert.strictEqual(dbscan.clusters.length, 1); }); it('should correctly cluster highly correlated columns', () => { const table = { A: [1, 2, 3, 4, 5], B: [2, 3, 4, 5, 6], // Высокая корреляция с A C: [10, 20, 30, 40, 50], // Далеко от A и B } const dbscan = new Dbscan(table, { eps: 0.3, minPts: 2 }); // Проверяем, что A и B попали в один кластер assert.strictEqual(dbscan.labels[0], dbscan.labels[1]); assert.strictEqual(dbscan.labels[2], -1); // C должен быть шумом assert.strictEqual(dbscan.clusters.length, 1); }); it('should mark all points as noise if no points meet minPts', () => { const table = { A: [1, 2, 3, 4, 5], B: [2, 3, 4, 5, 6], C: [10, 20, 30, 40, 50], } const dbscan = new Dbscan(table, { eps: 0.1, minPts: 3 }); // Все точки должны быть шумом (-1) assert.ok(dbscan.labels.every(label => label === -1)); assert.strictEqual(dbscan.clusters.length, 0); }); it('should return empty clusters if all points are noise', () => { const table = { A: [1, 2, 3], B: [4, 5, 6], } const dbscan = new Dbscan(table, { eps: 0.05, minPts: 2 }); assert.strictEqual(dbscan.clusters.length, 0); assert.ok(dbscan.labels.every(label => label === -1)); }); }); describe('Dbscan — metric option', () => { it('MAD metric splits AB|CD for reasonable eps', () => { const table = { A: [1, 2, 3, 4, 5], B: [2, 3, 4, 5, 6], C: [10, 20, 30, 40, 50], D: [11, 21, 31, 41, 51], }; const db = new Dbscan(table, { eps: 0.3, minPts: 2, metric: 'mad' }); const uniq = new Set(db.labels.filter(x => x > 0)); assert.ok(uniq.size >= 2); }); it('Pearson metric glues shape-equal columns (needs smaller eps)', () => { const table = { A: [1, 2, 3, 4, 5], B: [2, 3, 4, 5, 6], C: [10, 20, 30, 40, 50], }; // В Pearson dist=1-r ∈ [0,2]; A~B~C ≈ 0, поэтому при очень маленьком eps всё слепится const db = new Dbscan(table, { eps: 0.05, minPts: 2, metric: 'pearson' }); const uniq = new Set(db.labels.filter(x => x > 0)); assert.strictEqual(uniq.size, 1); }); });