Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/kmeans-cluster-sizes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chroma-js": patch
---

Fix `chroma.limits(data, 'k', n)` (k-means) dropping clusters. The cluster tally was incremented inside the nearest-centroid loop, so each point was counted up to `n` times against intermediate best-so-far indices, corrupting the centroid means. It is now counted once per point after the argmin, so well-separated data yields the expected breaks (`chroma.limits([0,1,2,50,51,52,100,101,102], 'k', 3)` returns `[0, 2, 52, 102]`).
6 changes: 4 additions & 2 deletions src/utils/analyze.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,11 @@ export function limits(data, mode = 'equal', num = 7) {
mindist = dist;
best = j;
}
clusterSizes[best]++;
assignments[i] = best;
}
// tally the winning cluster once per point, after the argmin
// over j; incrementing inside the loop corrupted clusterSizes
clusterSizes[best]++;
assignments[i] = best;
}

// update centroids step
Expand Down
6 changes: 6 additions & 0 deletions test/limits.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,10 @@ describe('Some tests for chroma.limits()', () => {
it('logarithmic domain - non-positive values', () => {
expect(() => limits([-1, 10000], 'log', 4)).toThrow('Logarithmic scales are only possible for values > 0');
});

it('k-means classifies all requested clusters', () => {
// three well-separated clusters must yield three class breaks; a
// corrupted cluster-size tally previously dropped the middle cluster
expect(limits([0, 1, 2, 50, 51, 52, 100, 101, 102], 'k', 3)).toEqual([0, 2, 52, 102]);
});
});