k-medoids
Version:
Implementation of the k-mediods clustering algorithm
123 lines (122 loc) • 5.03 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const euclidean_1 = require("../DistanceCalculators/euclidean");
const errorMessages = require("./errorMessages");
const cluster_1 = require("./cluster");
class Clusterer {
constructor(Elements, K, DistanceFn) {
this.Elements = Elements;
this.K = K;
this.DistanceFn = DistanceFn;
this.OptimisationCompleted = false;
this.iterate = () => {
this.IterationCount++;
if (!this.Clusters) {
this.allocateToClustersAroundCurrentMedoids();
}
const medoidsB4Iter = this.Medoids;
const clustersB4Iter = this.Clusters;
const costBeforeIteration = this.calculateCurrentCost();
let bestCostSoFar = costBeforeIteration;
for (let i = 0; i < this.Medoids.length; i++) {
const isNonMedoid = (e) => this.Medoids.filter((m) => this.DistanceFn(e, m) === 0).length === 0;
for (const nonMedoidElement of this.Elements.filter(isNonMedoid)) {
const proposedMedoids = this.Medoids.slice();
proposedMedoids[i] = nonMedoidElement;
const tmpClusterer = new Clusterer(this.Elements.slice(), this.K, this.DistanceFn);
tmpClusterer.Medoids = proposedMedoids;
tmpClusterer.allocateToClustersAroundCurrentMedoids();
const costForThisConfiguration = tmpClusterer.calculateCurrentCost();
if (costForThisConfiguration < bestCostSoFar) {
bestCostSoFar = costForThisConfiguration;
this.Clusters = tmpClusterer.Clusters;
this.Medoids = proposedMedoids;
}
}
}
const newCost = this.calculateCurrentCost();
if (newCost < costBeforeIteration) {
return true;
}
else {
this.Clusters = clustersB4Iter;
this.Medoids = medoidsB4Iter;
this.OptimisationCompleted = true;
return false;
}
};
this.getClusteredData = () => {
if (!this.OptimisationCompleted) {
this.runToCompletion();
}
return this.Clusters.map((c) => c.Elements.map((e) => e.Element));
};
this.runToCompletion = () => {
// tslint:disable-next-line:curly
while (this.iterate())
;
};
this.allocateToClustersAroundCurrentMedoids = () => {
this.Clusters = new Array();
this.Medoids.forEach((m) => {
this.Clusters.push(new cluster_1.Cluster({
Elements: [],
Medoid: m,
}));
});
this.Elements.forEach((element, idx) => {
const distances = this.findDistances(this.Clusters.map((c) => c.Medoid), element);
const idxOfMinDistance = distances
.reduce((iMin, x, i, arr) => x < arr[iMin] ? i : iMin, 0);
this.Clusters[idxOfMinDistance].Elements.push({
DistanceFromMedoid: distances[idxOfMinDistance],
Element: element,
});
});
};
this.calculateCurrentCost = () => {
return this.Clusters.map((c) => c.getCost()).reduce((a, b) => a + b, 0);
};
this.findDistances = (bases, e) => {
return bases.map((m) => this.DistanceFn(m, e));
};
this.selectInitialMedoids = () => {
if (this.K >= this.Elements.length) {
throw errorMessages.kGtElementArrLength;
}
const ret = [];
const isDistinctFromGroup = (element, group) => {
for (const grpElem of group) {
if (this.DistanceFn(grpElem, element) === 0) {
return false;
}
}
return true;
};
let i = 0;
while (i < this.Elements.length && ret.length < this.K) {
if (isDistinctFromGroup(this.Elements[i], ret)) {
ret.push(this.Elements[i]);
}
i++;
}
return ret;
};
this.DistanceFn = this.DistanceFn || euclidean_1.distance;
this.Medoids = this.selectInitialMedoids();
this.IterationCount = 0;
}
get OptimisationStarted() {
return this.IterationCount > 0;
}
}
Clusterer.clusterElements = (elements, k, distanceFn) => {
const instance = Clusterer.getInstance(elements, k, distanceFn);
const ret = new Array();
return ret;
};
Clusterer.getInstance = (elements, k, distanceFn) => {
const instance = new Clusterer(elements, k, distanceFn);
return instance;
};
exports.Clusterer = Clusterer;