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.

87 lines (76 loc) 3.24 kB
class Dbscan { constructor(table, eps = 0.4, minPts = 3) { if (table.columnsN < 2) throw new Error('2 or more columns required') this.table = table; this.eps = eps; this.minPts = minPts; this.columnsArray = Object.entries(table.columns); this.n = this.columnsArray.length; this.labels = new Array(this.n).fill(0); // 0: не обработан, -1: шум, 1+: кластер this.clusters = []; this.distances = null; this.computeDistances(); this.run(); this.buildClusters(); } computeDistances() { // Вычисление матрицы расстояний this.distances = new Array(this.n).fill(null).map(() => new Array(this.n).fill(null)); for (let i = 0; i < this.n; i++) { for (let j = i + 1; j < this.n; j++) { const { correlationSample } = this.table.compare(this.columnsArray[i][0], this.columnsArray[j][0]); const dist = 1 - correlationSample; this.distances[i][j] = dist; this.distances[j][i] = dist; } this.distances[i][i] = 0; // расстояние до себя = 0 } } findNeighbors(pointIdx) { // Поиск соседей для точки const neighbors = []; for (let i = 0; i < this.n; i++) { if (this.distances[pointIdx][i] <= 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) { seeds.push(...currentNeighbors.filter(idx => this.labels[idx] === 0)); } } } run() { // Основной алгоритм DBSCAN let clusterId = 0; for (let i = 0; i < this.n; 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); } } buildClusters() { // Формирование кластеров как таблиц for (let id = 1; id <= Math.max(...this.labels); id++) { const clusterTable = new this.table.Table(); this.columnsArray.forEach(([name, column], idx) => { if (this.labels[idx] === id) { clusterTable.addColumn(name, column.clone(true)); } }); if (clusterTable.columnsN > 0) this.clusters.push(clusterTable); } } } module.exports = Dbscan