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
JavaScript
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);
});
});