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
JavaScript
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