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