Given locations of houses in a sparsely populated county,
Find locations to put mail drop mail drop boxes to serve the community
Given a large body of texts (e.g., books),
Find collections of similar books (e.g., similar style, topic, etc)
Given an image (e.g., digital photo),
Find a palette of representative colors similar to those in the image.
Question. What to these problems have in common?
Given:
Find:
Assume:
Representing items:
Examples: houses, texts, colors
Representing distances:
Output parameter: $k$ = # of desired parts/clusters/elements
Clusters $C_1, C_2, \ldots, C_k$ that partition the elements in the collection
Points $m_1, m_2, \ldots, m_k$ that a representative of the clusters
From representative points to clusters:
Question. How to choose good representative points?
Question. Given clusters, how could we choose appropriate representative points?
Idea. Choose the centroid a.k.a. mean of each cluster
Input.
Output.
Optimality.
Unfortunately. Finding $m_1, m_2, \ldots, m_k$ is computationally hard
start by selecting $m_1, m_2, \ldots, m_k$ randomly
compute associated clusters $C_1, C_2, \ldots, C_k$
then update means according to the clusters
then update the clusters
…
k-means-clustering.zip
KMeans
represents an instance of $k$-means clustering problem
KMeansVisualizer
KMeans
instance#k-means {
position: fixed;
z-index: 0;
top: 0px;
left: 0px;
width: 100svw;
height: 100svh;
background-color: rgb(220, 220, 255);
}
#root {
width: 700px;
height: 100svh;
margin: 0px auto;
display: flex;
flex-direction: column;
justify-content: space-between;
overflow: clip;
}
#root {
overflow: clip;
}
.head, .foot {
border-radius: 10px;
position: relative;
}
.head {
top: -10px;
padding: 30px 20px 20px 20px;
}
.foot {
top: 10px;
padding: 20px 20px 30px 20px;
}
for (let i = 0; i < this.k; i++) {
let group = document.createElementNS(SVG_NS, "g");
group.setAttributeNS(null, "fill",
`hsl(${60 + 360 * i / this.k},
90%, ${50 - 20 * i / this.k}%)`);
svg.appendChild(group);
this.clusterGroups.push(group);
}
Group element for each cluster, set fill
attribute
this.clusterGroups = []; // SVG groups for each cluster
for (let i = 0; i < this.k; i++) {
let group = document.createElementNS(SVG_NS, "g");
group.setAttributeNS(null, "fill", ...);
svg.appendChild(group);
this.clusterGroups.push(group);}
Adding elements
this.updateGroups = function () {
for (let i = 0; i < this.pointElts.length; i++) {
let pt = this.pointElts[i];
let group = this.kmeans.clusters[i];
this.clusterGroups[group].prepend(pt);}}
CSS
.hidden {
visibility: hidden;
}
JS
const btnHideBoxes = document.querySelector("#btn-hide-boxes");
btnHideBoxes.addEventListener("click", () => {
for (let box of document.querySelectorAll(".bounding-box")) {
box.classList.toggle("hidden");
}
});
let elt = document.createElementNS(SVG_NS, "polygon");
elt.setAttributeNS(null, "points", "10,0 0,10 -10,0 0,-10");
elt.classList.add('mean-point');
let animate = document.createElementNS(SVG_NS, "animateTransform");
animate.setAttributeNS(null, "attributeName", "transform");
animate.setAttributeNS(null, "attributeType", "XML");
animate.setAttributeNS(null, "type", "translate");
animate.setAttributeNS(null, "from", "0 0");
animate.setAttributeNS(null, "to", "0 0");
animate.setAttributeNS(null, "dur", "1s");
animate.setAttributeNS(null, "repeatCount", "1");
animate.setAttributeNS(null, "fill", "freeze");
animate.setAttributeNS(null, "begin", "indefinite");
elt.appendChild(animate);
this.updateMeans = function () {
for (let i = 0; i < this.k; i++) {
let elt = this.meanElts[i];
let x = this.kmeans.xMeans[i];
let y = this.kmeans.yMeans[i];
let animate = elt.firstChild;
let from = animate.getAttribute("to");
from = (from === "0 0") ? `${x} ${y}` : from;
animate.setAttributeNS(null, "from", from);
animate.setAttributeNS(null, "to", `${x} ${y}`);
animate.beginElement();}}
CSS:
.mean-point {
transition-property: transform;
transition-duration: 1s;
}
Unfortunately this applies the transition to style = "transform: ...;"
and not the transform
svg attribute.
Something a bit different