UNPKG

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.

129 lines (97 loc) 5.58 kB
const { describe, it } = require('node:test'); const assert = require('node:assert'); const Dbscan = require('../../lib/table/instruments/dbscan'); const { newTable, newColumn } = require('../../lib/index'); describe('Dbscan', () => { it('should throw an error if the table has less than 2 columns', () => { const table = newTable(); table.addColumn('A', newColumn([1, 2, 3])); assert.throws(() => new Dbscan(table, 0.4, 3, newTable), { message: '2 or more columns required' }); }); it('should correctly cluster highly correlated columns', () => { const table = newTable(); table.addColumn('A', newColumn([1, 2, 3, 4, 5])); table.addColumn('B', newColumn([2, 3, 4, 5, 6])); // Высокая корреляция с A table.addColumn('C', newColumn([10, 20, 30, 40, 50])); // Далеко от A и B table.compare = (col1, col2) => { if ((col1 === 'A' && col2 === 'B') || (col1 === 'B' && col2 === 'A')) { return { correlationSample: 0.9 }; // A и B почти одинаковы } return { correlationSample: 0.1 }; // С не коррелирован с A и B }; const dbscan = new Dbscan(table, 0.3, 2, newTable); // Проверяем, что 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 = newTable(); table.addColumn('A', newColumn([1, 2, 3, 4, 5])); table.addColumn('B', newColumn([2, 3, 4, 5, 6])); table.addColumn('C', newColumn([10, 20, 30, 40, 50])); table.compare = () => ({ correlationSample: 0.2 }); // Слабая корреляция const dbscan = new Dbscan(table, 0.1, 3, newTable); // Все точки должны быть шумом (-1) assert.ok(dbscan.labels.every(label => label === -1)); assert.strictEqual(dbscan.clusters.length, 0); }); it('should create multiple clusters if columns are weakly correlated', () => { const table = newTable(); table.addColumn('A', newColumn([1, 2, 3, 4, 5])); table.addColumn('B', newColumn([10, 20, 30, 40, 50])); table.addColumn('C', newColumn([100, 200, 300, 400, 500])); table.addColumn('D', newColumn([1000, 2000, 3000, 4000, 5000])); table.compare = (col1, col2) => { let correlation = col1 === col2 ? 1 : 0.2; console.log(`Compare ${col1} - ${col2} => correlationSample: ${correlation}`); return { correlationSample: correlation }; }; const eps = 0.5; // Уменьшаем `eps`, чтобы точки не объединялись в 1 кластер const minPts = 1; // Уменьшаем `minPts`, чтобы кластеры могли формироваться const dbscan = new Dbscan(table, eps, minPts, newTable); // 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 = newTable(); table.addColumn('A', newColumn([1, 2, 3, 4, 5])); table.addColumn('B', newColumn([1, 2, 3, 4, 5])); table.addColumn('C', newColumn([1, 2, 3, 4, 5])); table.compare = () => ({ correlationSample: 1 }); // Все колонки одинаковые const dbscan = new Dbscan(table, 0.4, 2, newTable); // Все три колонки должны попасть в один кластер assert.strictEqual(dbscan.labels[0], dbscan.labels[1]); assert.strictEqual(dbscan.labels[1], dbscan.labels[2]); assert.strictEqual(dbscan.clusters.length, 1); }); it('should return empty clusters if all points are noise', () => { const table = newTable(); table.addColumn('A', newColumn([1, 2, 3])); table.addColumn('B', newColumn([4, 5, 6])); table.compare = () => ({ correlationSample: 0.1 }); // Нет взаимосвязи const dbscan = new Dbscan(table, 0.05, 2, newTable); assert.strictEqual(dbscan.clusters.length, 0); assert.ok(dbscan.labels.every(label => label === -1)); }); it('should correctly form a cluster with exact minPts', () => { const table = newTable(); table.addColumn('A', newColumn([1, 2, 3])); table.addColumn('B', newColumn([2, 3, 4])); table.addColumn('C', newColumn([3, 4, 5])); table.compare = (col1, col2) => { if ((col1 === 'A' && col2 === 'B') || (col1 === 'B' && col2 === 'C')) { return { correlationSample: 0.8 }; } return { correlationSample: 0.2 }; }; const dbscan = new Dbscan(table, 0.3, 3, newTable); assert.strictEqual(dbscan.clusters.length, 1); }); });