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
61 lines (54 loc) • 2.16 kB
JavaScript
import computeDistances from './compute-dist.js';
import { buildClusters } from './build-clusters.js'
import { TestBase } from '../test-base/index.js'
export default class Dbscan extends TestBase {
constructor(samples, options = {}) {
super(samples, 'Dbscan',[], { min: 2 })
const { eps = 0.4, minPts = 3, metric = 'mad' } = options
this.metric = metric;
this.eps = eps;
this.minPts = minPts;
this.labels = new Array(this.k).fill(0); // 0: не обработан, -1: шум, 1+: кластер
this.clusters = [];
this.distances = computeDistances(this.samples,this.metric)
this.run();
buildClusters(this);
}
findNeighbors(pointIdx) {
const neighbors = [];
for (let i = 0; i < this.k; i++) {
const d = this.distances[pointIdx][i];
if (Number.isFinite(d) && d <= this.eps) neighbors.push(i);
}
return neighbors;
}
expandCluster(pointIdx, clusterId) {
this.labels[pointIdx] = clusterId;
const seeds = this.findNeighbors(pointIdx).filter(idx => idx !== pointIdx);
while (seeds.length > 0) {
const current = seeds.pop();
if (this.labels[current] === -1) this.labels[current] = clusterId; // шум -> кластер
if (this.labels[current] !== 0) continue; // уже помечен
this.labels[current] = clusterId;
const currentNeighbors = this.findNeighbors(current);
if (currentNeighbors.length >= this.minPts) {
for (const nb of currentNeighbors) {
if (this.labels[nb] === 0) seeds.push(nb);
}
}
}
}
run() { // Основной алгоритм DBSCAN
let clusterId = 0;
for (let i = 0; i < this.k; i++) {
if (this.labels[i] !== 0) continue; // уже обработан
const neighbors = this.findNeighbors(i);
if (neighbors.length < this.minPts) {
this.labels[i] = -1; // шум
continue;
}
clusterId++;
this.expandCluster(i, clusterId);
}
}
}