UNPKG

k-medoids

Version:

Implementation of the k-mediods clustering algorithm

123 lines (122 loc) 5.03 kB
"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;