Skip to content

Instantly share code, notes, and snippets.

@susielu
Last active October 10, 2023 14:24
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save susielu/c9170f67e06af8adfbe29ba7617c2cfa to your computer and use it in GitHub Desktop.
K-Means Centroid Deviation

K-Means Centroid Deviation

Added all of the Farmers' Markets data into the kMeans Library by @emilbayes, thank you!

Exploring the idea of using the areas around the centroids to exaggerate that cluster when it deviates from the rest of the clusters. This originates from the question "Which features in each cluster differentiate it from the rest?"

d3.json('https://gist.githubusercontent.com/susielu/3d194b8660ec6ab214a3/raw/farmers-markets.json', (error, data) => {
const variables = [
{ "key": "vegetables", "label": "Vegetables 96%", "percent": .96},
{ "key": "bakedgoods", "label": "Baked Goods 88%", "percent": .88},
{ "key": "honey", "label": "Honey 81%", "percent": .81},
{ "key": "jams", "label": "Jams 80%", "percent": .80},
{ "key": "fruits", "label": "Fruits 80%", "percent": .80},
{ "key": "herbs", "label": "Herbs 79%", "percent": .79},
{ "key": "eggs", "label": "Eggs 74%", "percent": .74},
{ "key": "flower", "label": "Flowers 69%", "percent": .69},
{ "key": "soap", "label": "Soap 67%", "percent": .67 },
{ "key": "plants", "label": "Plants 66%", "percent": .66},
{ "key": "crafts", "label": "Crafts 61%", "percent": .61},
{ "key": "prepared", "label": "Prepared Food 61%", "percent": .61},
{ "key": "meat", "label": "Meat 55%", "percent": .55},
{ "key": "cheese", "label": "Cheese 50%", "percent": .50},
{ "key": "poultry", "label": "Poultry 45%", "percent": .45},
{ "key": "coffee", "label": "Coffee 33%", "percent": .33},
{ "key": "maple", "label": "Maple 32%", "percent": .32},
{ "key": "nuts", "label": "Nuts 29%", "percent": .29},
{ "key": "trees", "label": "Trees 29%", "percent": .29},
{ "key": "seafood", "label": "Seafood 24%", "percent": .24},
{ "key": "juices", "label": "Juices 22%", "percent": .22},
{ "key": "mushrooms", "label": "Mushrooms 22%", "percent": .22},
{ "key": "petfood", "label": "Pet Food 18%", "percent": .18},
{ "key": "wine", "label": "Wine 17%", "percent": .17},
{ "key": "beans", "label": "Beans 14%", "percent": .14},
{ "key": "grains", "label": "Grains 14%", "percent": .14},
{ "key": "wildharvest", "label": "Wild Harvest 13%", "percent": .13},
{ "key": "nursery", "label": "Nursery 6%", "percent": .06},
{ "key": "tofu", "label": "Tofu 4%", "percent": .04}
]
const features = []
const clusters = 5
data.forEach(d => {
const f = [];
variables.forEach(k => {
if (d[k.key] === "Y") {
f.push(1)
} else {
f.push(0)
}
})
features.push(f)
})
const km = new kMeans({
K: clusters
})
const cluster = () => {
km.cluster(features);
while (km.step()) {
km.findClosestCentroids();
km.moveCentroids();
let hasConverged;
try{
hasConverged = km.hasConverged()
if(hasConverged) break;
}
catch (e) {
console.log('error', e)
}
}
console.log(km.centroids, km.clusters);
variables.forEach((v, i) => {
v.clusterValues = []
km.centroids.forEach((c, j) => {
v.clusterValues.push({
value: c[i],
cluster: j
})
})
v.clusterValues.forEach(c => {
c.minDiff = Math.min(...v.clusterValues.map( m => c.value !== m.value ? Math.abs(m.value - c.value) : 1 ))
})
})
}
cluster()
const svg = d3.select('svg')
const w = 29
const wPadding = 65
const h = 130
const yOffset = 220
const y = d3.scaleLinear().range([0 + yOffset, h + yOffset]).domain([1, 0])
const colors = d3.scaleOrdinal()
.domain(d3.range(0, clusters, 1))
.range(['rgb(30, 0, 255)', '#0960ff', '#00ffc4','#2bff1d', '#a4e800', 'rgb(0, 92, 255)', 'rgb(255, 0, 92)', 'rgb(255, 92, 0)', 'orange'])
const getOffset = (d, i, j) => {
const minDiff = variables[j].clusterValues[i].minDiff
return y(d + minDiff) - y(d)
}
const drawArea = (d, i) => {
const line = d3.area()
.x((d, i) => i*w + wPadding )
.y1((d, j) => y(d) + getOffset(d, i, j))
.y0((d, j) => y(d) - getOffset(d, i, j))
.curve(d3.curveCardinal)
return line(d)
}
const drawLine = d3.line()
.x((d, i) => i*w + wPadding )
.y(d => y(d))
.curve(d3.curveCardinal)
variables.forEach((v, i) => {
const variable = svg.append('g')
.attr('class', 'variable')
const xOffset = i*w + wPadding
variable.append('line')
.attr('x1', xOffset)
.attr('x2', xOffset)
.attr('y1', y(0))
.attr('y2', y(1))
variable.append('text')
.attr('x', xOffset)
.attr('y', h + yOffset + 50)
.attr('transform', `rotate(45, ${xOffset - 15}, ${h + yOffset + 50})`)
.text(v.label)
})
svg.append('g')
.attr('class', 'connectors')
const colorLegend = d3.legendColor()
.orient('horizontal')
.shapeWidth(30)
.scale(colors)
svg.append('g')
.attr('class', 'legend')
.attr('transform', 'translate(300, 120)')
.call(colorLegend)
const legendScale = d3.scaleLinear()
.domain([0, d3.max(km.clusters, d => d.length)])
.range([0, 60])
const t = d3.transition()
.ease(d3.easeLinear)
const recluster = () => {
cluster()
const connectorsArea = svg.select('g.connectors').selectAll('path.area')
.data(km.centroids)
const connectorsLine = svg.select('g.connectors').selectAll('path.line')
.data(km.centroids)
const hoverPath = () => {
}
connectorsArea.enter()
.append('path')
.attr('class','area')
.on('hover', hoverPath)
.merge(connectorsArea)
.attr('stroke', (d,i) => colors(i))
.attr('fill', (d,i) => colors(i))
.transition()
.attr('d', (d, i) => drawArea(d, i))
connectorsArea.exit()
.remove()
connectorsLine.enter()
.append('path')
.attr('class', 'line')
.on('hover', hoverPath)
.merge(connectorsLine)
.attr('stroke', (d,i) => d3.color(colors(i)).darker())
.transition()
.attr('d', (d, i) => drawLine(d, i))
connectorsLine.exit()
.remove()
svg.selectAll('g.variable')
.each( () => {
legendScale
.domain([0, d3.max(km.clusters, d => d.length)])
svg.selectAll('.cell .swatch')
.data(km.clusters)
.transition(t)
.attr('height', d => legendScale(d.length))
.attr('y', d => -legendScale(d.length) + 15)
svg.select('.legendCells')
.selectAll('text.count')
.data(km.clusters)
.enter()
.append('text')
.attr('class', 'count')
svg.selectAll('text.count')
.text(d => d.length)
.transition(t)
.attr('x', (d, i) => i*32 + 15)
.attr('y', d => -legendScale(d.length) + 10)
})
}
recluster()
// interactions
const buttons = svg.append('g')
.attr('class', 'buttons')
.attr('transform', 'translate(30, 130)')
buttons.append('rect')
.attr('width', 100)
.attr('height', 30)
.on('click', recluster)
buttons.append('text')
.text('Recluster')
.attr('x', 50)
.attr('y', 20)
})
"use strict";
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
d3.json('https://gist.githubusercontent.com/susielu/3d194b8660ec6ab214a3/raw/farmers-markets.json', function (error, data) {
var variables = [{ "key": "vegetables", "label": "Vegetables 96%", "percent": .96 }, { "key": "bakedgoods", "label": "Baked Goods 88%", "percent": .88 }, { "key": "honey", "label": "Honey 81%", "percent": .81 }, { "key": "jams", "label": "Jams 80%", "percent": .80 }, { "key": "fruits", "label": "Fruits 80%", "percent": .80 }, { "key": "herbs", "label": "Herbs 79%", "percent": .79 }, { "key": "eggs", "label": "Eggs 74%", "percent": .74 }, { "key": "flower", "label": "Flowers 69%", "percent": .69 }, { "key": "soap", "label": "Soap 67%", "percent": .67 }, { "key": "plants", "label": "Plants 66%", "percent": .66 }, { "key": "crafts", "label": "Crafts 61%", "percent": .61 }, { "key": "prepared", "label": "Prepared Food 61%", "percent": .61 }, { "key": "meat", "label": "Meat 55%", "percent": .55 }, { "key": "cheese", "label": "Cheese 50%", "percent": .50 }, { "key": "poultry", "label": "Poultry 45%", "percent": .45 }, { "key": "coffee", "label": "Coffee 33%", "percent": .33 }, { "key": "maple", "label": "Maple 32%", "percent": .32 }, { "key": "nuts", "label": "Nuts 29%", "percent": .29 }, { "key": "trees", "label": "Trees 29%", "percent": .29 }, { "key": "seafood", "label": "Seafood 24%", "percent": .24 }, { "key": "juices", "label": "Juices 22%", "percent": .22 }, { "key": "mushrooms", "label": "Mushrooms 22%", "percent": .22 }, { "key": "petfood", "label": "Pet Food 18%", "percent": .18 }, { "key": "wine", "label": "Wine 17%", "percent": .17 }, { "key": "beans", "label": "Beans 14%", "percent": .14 }, { "key": "grains", "label": "Grains 14%", "percent": .14 }, { "key": "wildharvest", "label": "Wild Harvest 13%", "percent": .13 }, { "key": "nursery", "label": "Nursery 6%", "percent": .06 }, { "key": "tofu", "label": "Tofu 4%", "percent": .04 }];
var features = [];
var clusters = 5;
data.forEach(function (d) {
var f = [];
variables.forEach(function (k) {
if (d[k.key] === "Y") {
f.push(1);
} else {
f.push(0);
}
});
features.push(f);
});
var km = new kMeans({
K: clusters
});
var cluster = function cluster() {
km.cluster(features);
while (km.step()) {
km.findClosestCentroids();
km.moveCentroids();
var hasConverged = void 0;
try {
hasConverged = km.hasConverged();
if (hasConverged) break;
} catch (e) {
console.log('error', e);
}
}
console.log(km.centroids, km.clusters);
variables.forEach(function (v, i) {
v.clusterValues = [];
km.centroids.forEach(function (c, j) {
v.clusterValues.push({
value: c[i],
cluster: j
});
});
v.clusterValues.forEach(function (c) {
c.minDiff = Math.min.apply(Math, _toConsumableArray(v.clusterValues.map(function (m) {
return c.value !== m.value ? Math.abs(m.value - c.value) : 1;
})));
});
});
};
cluster();
var svg = d3.select('svg');
var w = 29;
var wPadding = 65;
var h = 130;
var yOffset = 220;
var y = d3.scaleLinear().range([0 + yOffset, h + yOffset]).domain([1, 0]);
var colors = d3.scaleOrdinal().domain(d3.range(0, clusters, 1)).range(['rgb(30, 0, 255)', '#0960ff', '#00ffc4', '#2bff1d', '#a4e800', 'rgb(0, 92, 255)', 'rgb(255, 0, 92)', 'rgb(255, 92, 0)', 'orange']);
var getOffset = function getOffset(d, i, j) {
var minDiff = variables[j].clusterValues[i].minDiff;
return y(d + minDiff) - y(d);
};
var drawArea = function drawArea(d, i) {
var line = d3.area().x(function (d, i) {
return i * w + wPadding;
}).y1(function (d, j) {
return y(d) + getOffset(d, i, j);
}).y0(function (d, j) {
return y(d) - getOffset(d, i, j);
}).curve(d3.curveCardinal);
return line(d);
};
var drawLine = d3.line().x(function (d, i) {
return i * w + wPadding;
}).y(function (d) {
return y(d);
}).curve(d3.curveCardinal);
variables.forEach(function (v, i) {
var variable = svg.append('g').attr('class', 'variable');
var xOffset = i * w + wPadding;
variable.append('line').attr('x1', xOffset).attr('x2', xOffset).attr('y1', y(0)).attr('y2', y(1));
variable.append('text').attr('x', xOffset).attr('y', h + yOffset + 50).attr('transform', "rotate(45, " + (xOffset - 15) + ", " + (h + yOffset + 50) + ")").text(v.label);
});
svg.append('g').attr('class', 'connectors');
var colorLegend = d3.legendColor().orient('horizontal').shapeWidth(30).scale(colors);
svg.append('g').attr('class', 'legend').attr('transform', 'translate(300, 120)').call(colorLegend);
var legendScale = d3.scaleLinear().domain([0, d3.max(km.clusters, function (d) {
return d.length;
})]).range([0, 60]);
var t = d3.transition().ease(d3.easeLinear);
var recluster = function recluster() {
cluster();
var connectorsArea = svg.select('g.connectors').selectAll('path.area').data(km.centroids);
var connectorsLine = svg.select('g.connectors').selectAll('path.line').data(km.centroids);
var hoverPath = function hoverPath() {};
connectorsArea.enter().append('path').attr('class', 'area').on('hover', hoverPath).merge(connectorsArea).attr('stroke', function (d, i) {
return colors(i);
}).attr('fill', function (d, i) {
return colors(i);
}).transition().attr('d', function (d, i) {
return drawArea(d, i);
});
connectorsArea.exit().remove();
connectorsLine.enter().append('path').attr('class', 'line').on('hover', hoverPath).merge(connectorsLine).attr('stroke', function (d, i) {
return d3.color(colors(i)).darker();
}).transition().attr('d', function (d, i) {
return drawLine(d, i);
});
connectorsLine.exit().remove();
svg.selectAll('g.variable').each(function () {
legendScale.domain([0, d3.max(km.clusters, function (d) {
return d.length;
})]);
svg.selectAll('.cell .swatch').data(km.clusters).transition(t).attr('height', function (d) {
return legendScale(d.length);
}).attr('y', function (d) {
return -legendScale(d.length) + 15;
});
svg.select('.legendCells').selectAll('text.count').data(km.clusters).enter().append('text').attr('class', 'count');
svg.selectAll('text.count').text(function (d) {
return d.length;
}).transition(t).attr('x', function (d, i) {
return i * 32 + 15;
}).attr('y', function (d) {
return -legendScale(d.length) + 10;
});
});
};
recluster();
// interactions
var buttons = svg.append('g').attr('class', 'buttons').attr('transform', 'translate(30, 130)');
buttons.append('rect').attr('width', 100).attr('height', 30).on('click', recluster);
buttons.append('text').text('Recluster').attr('x', 50).attr('y', 20);
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link href='https://fonts.googleapis.com/css?family=Lato:300,900' rel='stylesheet' type='text/css'>
<style>
body{
background-color: whitesmoke;
}
svg {
background-color: white;
font-family: 'Lato';
}
line {
stroke: black;
stroke-width: .5px;
opacity: .3
}
text {
font-size: 11px;
font-weight: 900;
}
.graph-legend text {
font-weight: normal;
}
text.title {
font-size: 24px;
}
text.mini-title {
font-size: 20px;
font-weight: normal;
}
text.subtitle {
font-size: 16px;
font-weight: normal;
}
.legendCells text.count {
text-anchor: middle;
font-size: 10px;
}
.legendCells text:not(.count) {
font-weight: normal;
}
rect {
opacity: .5;
}
.buttons rect {
fill: blue;
cursor: pointer;
}
.buttons:hover {
cursor: pointer;
}
.buttons text {
text-anchor: middle;
fill: white;
font-weight: 300;
font-size: 16px;
pointer-events: none;
cursor: pointer;
}
path.area {
opacity: .3;
}
path.line {
fill: none;
}
line.centroid {
stroke: black;
stroke-width: 3px;
}
path.line, line.centroid {
stroke-width: 2px;
stroke-dasharray: 1, 3;
}
</style>
</head>
<body>
<svg width="960" height="500">
<text class="title" x=30 y=40>K-Means Clustering</text>
<text class="mini-title" x=30 y=70>Farmers' Markets</text>
<text class="mini-title" x=30 y=97>By Goods</text>
<text class="subtitle" x=297 y=40>Farmers' Markets per Cluster</text>
<text class="subtitle" x=30 y=225>Yes</text>
<text class="subtitle" x=30 y=350>No</text>
<g transform="translate(550, 40)" class="graph-legend">
<text>Close to another cluster's score for this good</text>
<line x1=0 x2=210 y1=13 y2=13 />
<line x1=160 x2=160 y1=13 y2=40 />
<circle cx=160 cy=40 r=2 />
<g transform="translate(120,120)">
<line x1=0 x2=197 y1=-15 y2=-15 />
<line x1=110 x2=110 y1=-15 y2=-60 />
<circle cx=110 cy=-60 r=2 />
<text >Far from other cluster scores for this good</text>
</g>
<g transform="translate(255,55)">
<line x1=-5 x2=-15 y1=-5 y2=-5 />
<text >Cluster centroid</text>
</g>
<line class="centroid" x1=60 x2=235 y1=50 y2=50 />
<path transform="translate(-160, -130)" class="area" stroke="#aaa" fill="#aaa" d="M220,177.34313376907926C220,177.34313376907926,236.66666666666666,169.9255145054399,245,167.0046437685483C253.33333333333334,164.0837730316567,261.6666666666667,161.23221844223664,270,159.81790934772965C278.3333333333333,158.40360025322266,286.6666666666667,155.15774549733788,295,158.51878920150634C303.3333333333333,161.8798329056748,311.6666666666667,180.38701321932143,320,179.98417157274045C328.3333333333333,179.58132992615947,336.6666666666667,160.20826282804072,345,156.10173932202045C353.3333333333333,151.99521581600018,361.6666666666667,157.82667409186635,370,155.34503053661888C378.3333333333333,152.8633869813714,395,141.2118779905356,395,141.2118779905356L395,218.7881220094644C395,218.7881220094644,378.3333333333333,207.1366130186286,370,204.65496946338112C361.6666666666667,202.17332590813365,353.3333333333333,208.00478418399982,345,203.89826067797955C336.6666666666667,199.79173717195928,328.3333333333333,180.41867007384053,320,180.01582842725955C311.6666666666667,179.61298678067857,303.3333333333333,198.1201670943252,295,201.48121079849366C286.6666666666667,204.84225450266212,278.3333333333333,201.59639974677734,270,200.18209065227035C261.6666666666667,198.76778155776336,253.33333333333334,195.9162269683433,245,192.9953562314517C236.66666666666666,190.0744854945601,220,182.65686623092074,220,182.65686623092074Z"></path>
</g>
<g class="connectors" />
</svg>
<script src="https://d3js.org/d3.v4.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-legend/2.9.0/d3-legend.min.js"></script>
<script src="kMeans.js"></script>
<script src="clustering.js"></script>
</body>
</html>
// Generated by CoffeeScript 1.6.2
var exports, kMeans;
kMeans = (function() {
function kMeans(options) {
var _ref, _ref1, _ref2, _ref3, _ref4;
if (options == null) {
options = {};
}
this.K = (_ref = options.K) != null ? _ref : 5;
this.maxIterations = (_ref1 = options.maxIterations) != null ? _ref1 : 100;
this.enableConvergenceTest = (_ref2 = options.enableConvergenceTest) != null ? _ref2 : true;
this.tolerance = (_ref3 = options.tolerance) != null ? _ref3 : 1e-9;
this.initialize = (_ref4 = options.initialize) != null ? _ref4 : kMeans.initializeForgy;
}
kMeans.prototype.cluster = function(X) {
var _ref;
this.X = X;
this.prevCentroids = [];
this.clusters = [];
this.currentIteration = 0;
_ref = [this.X.length, this.X[0].length], this.m = _ref[0], this.n = _ref[1];
if ((this.m == null) || (this.n == null) || this.m < this.K || this.n < 1) {
throw "You must pass more data";
}
return this.centroids = this.initialize(this.X, this.K, this.m, this.n);
};
kMeans.prototype.step = function() {
return this.currentIteration++ < this.maxIterations;
};
kMeans.prototype.autoCluster = function(X) {
var _results;
this.cluster(X);
_results = [];
while (this.step()) {
this.findClosestCentroids();
this.moveCentroids();
if (this.hasConverged()) {
break;
} else {
_results.push(void 0);
}
}
return _results;
};
kMeans.initializeForgy = function(X, K, m, n) {
var k, _i, _results;
_results = [];
for (k = _i = 0; 0 <= K ? _i < K : _i > K; k = 0 <= K ? ++_i : --_i) {
_results.push(X[Math.floor(Math.random() * m)]);
}
return _results;
};
kMeans.initializeInRange = function(X, K, m, n) {
var d, i, k, max, min, x, _i, _j, _k, _l, _len, _len1, _m, _results;
for (i = _i = 0; 0 <= n ? _i < n : _i > n; i = 0 <= n ? ++_i : --_i) {
min = Infinity;
}
for (i = _j = 0; 0 <= n ? _j < n : _j > n; i = 0 <= n ? ++_j : --_j) {
max = -Infinity;
}
for (_k = 0, _len = X.length; _k < _len; _k++) {
x = X[_k];
for (i = _l = 0, _len1 = x.length; _l < _len1; i = ++_l) {
d = x[i];
min[i] = Math.min(min[i], d);
max[i] = Math.max(max[i], d);
}
}
_results = [];
for (k = _m = 0; 0 <= K ? _m < K : _m > K; k = 0 <= K ? ++_m : --_m) {
_results.push((function() {
var _n, _results1;
_results1 = [];
for (d = _n = 0; 0 <= n ? _n < n : _n > n; d = 0 <= n ? ++_n : --_n) {
_results1.push(Math.random() * (max[d] - min[d]) + min[d]);
}
return _results1;
})());
}
return _results;
};
kMeans.prototype.findClosestCentroids = function() {
var c, cMin, i, j, k, min, r, x, xMin, _i, _j, _k, _len, _len1, _ref, _ref1, _ref2, _results;
if (this.enableConvergenceTest) {
this.prevCentroids = (function() {
var _i, _len, _ref, _results;
_ref = this.centroids;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
r = _ref[_i];
_results.push(r.slice(0));
}
return _results;
}).call(this);
}
this.clusters = (function() {
var _i, _ref, _results;
_results = [];
for (i = _i = 0, _ref = this.K; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) {
_results.push([]);
}
return _results;
}).call(this);
_ref = this.X;
_results = [];
for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) {
x = _ref[i];
cMin = 0;
xMin = Infinity;
_ref1 = this.centroids;
for (j = _j = 0, _len1 = _ref1.length; _j < _len1; j = ++_j) {
c = _ref1[j];
min = 0;
for (k = _k = 0, _ref2 = x.length; 0 <= _ref2 ? _k < _ref2 : _k > _ref2; k = 0 <= _ref2 ? ++_k : --_k) {
min += (x[k] - c[k]) * (x[k] - c[k]);
}
if (min < xMin) {
cMin = j;
xMin = min;
}
}
_results.push(this.clusters[cMin].push(i));
}
return _results;
};
kMeans.prototype.moveCentroids = function() {
var cl, d, i, j, sum, _i, _len, _ref, _results;
_ref = this.clusters;
_results = [];
for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) {
cl = _ref[i];
if (cl.length < 1) {
continue;
}
_results.push((function() {
var _j, _k, _len1, _ref1, _results1;
_results1 = [];
for (j = _j = 0, _ref1 = this.n; 0 <= _ref1 ? _j < _ref1 : _j > _ref1; j = 0 <= _ref1 ? ++_j : --_j) {
sum = 0;
for (_k = 0, _len1 = cl.length; _k < _len1; _k++) {
d = cl[_k];
sum += this.X[d][j];
}
_results1.push(this.centroids[i][j] = sum / cl.length);
}
return _results1;
}).call(this));
}
return _results;
};
kMeans.prototype.hasConverged = function() {
var absDelta, i, j, _i, _j, _ref, _ref1;
if (!this.enableConvergenceTest) {
return false;
}
for (i = _i = 0, _ref = this.n; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) {
for (j = _j = 0, _ref1 = this.m; 0 <= _ref1 ? _j < _ref1 : _j > _ref1; j = 0 <= _ref1 ? ++_j : --_j) {
absDelta = Math.abs(this.prevCentroids[i][j] - this.centroids[i][j]);
if (this.tolerance > absDelta) {
return true;
}
}
}
return false;
};
return kMeans;
})();
if (((typeof module !== "undefined" && module !== null ? module.exports : void 0) != null) || (typeof exports !== "undefined" && exports !== null)) {
module.exports = exports = kMeans;
} else {
window.kMeans = kMeans;
}
{
"name": "farmers-market-clustering",
"version": "1.0.0",
"description": "Playing around with the idea of a bump chart comparison with two overlapping areas. Based on [Farmers Markets data](https://catalog.data.gov/dataset/farmers-markets-geographic-data) from [data.gov](https://catalog.data.gov/).",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+ssh://git@gist.github.com/1b10cd2de64b027a91e960c43c9bc23f.git"
},
"author": "",
"license": "ISC",
"bugs": {
"url": "https://gist.github.com/1b10cd2de64b027a91e960c43c9bc23f"
},
"homepage": "https://gist.github.com/1b10cd2de64b027a91e960c43c9bc23f",
"devDependencies": {
"kmeans-js": "^0.1.2"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment