@thi.ng/k-means 0.5.39 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Change Log
2
2
 
3
- - **Last updated**: 2023-01-10T15:20:19Z
3
+ - **Last updated**: 2023-02-10T13:55:29Z
4
4
  - **Generator**: [thi.ng/monopub](https://thi.ng/monopub)
5
5
 
6
6
  All notable changes to this project will be documented in this file.
@@ -9,6 +9,15 @@ See [Conventional Commits](https://conventionalcommits.org/) for commit guidelin
9
9
  **Note:** Unlisted _patch_ versions only involve non-code or otherwise excluded changes
10
10
  and/or version bumps of transitive dependencies.
11
11
 
12
+ ## [0.6.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/k-means@0.6.0) (2023-02-10)
13
+
14
+ #### 🚀 Features
15
+
16
+ - filter result clusters, minor optimizations ([78169e6](https://github.com/thi-ng/umbrella/commit/78169e6))
17
+ - use u32 array for internal cluster assignments
18
+ - remove attempt to start new cluster if one became unused
19
+ - update buildClusters to filter out empty
20
+
12
21
  ## [0.5.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/k-means@0.5.0) (2021-11-17)
13
22
 
14
23
  #### 🚀 Features
package/README.md CHANGED
@@ -48,7 +48,7 @@ For Node.js REPL:
48
48
  const kMeans = await import("@thi.ng/k-means");
49
49
  ```
50
50
 
51
- Package sizes (brotli'd, pre-treeshake): ESM: 906 bytes
51
+ Package sizes (brotli'd, pre-treeshake): ESM: 878 bytes
52
52
 
53
53
  ## Dependencies
54
54
 
@@ -82,7 +82,7 @@ import { kmeans, meansLatLon } from "@thi.ng/k-means";
82
82
  import { HAVERSINE_LATLON } from "@thi.ng/distance";
83
83
 
84
84
  // data from: https://simplemaps.com/data/world-cities
85
- const items = [
85
+ const cities = [
86
86
  { id: "berlin", latlon: [52.5167, 13.3833] },
87
87
  { id: "boston", latlon: [42.3188, -71.0846] },
88
88
  { id: "detroit", latlon: [42.3834, -83.1024] },
@@ -99,7 +99,7 @@ const items = [
99
99
  // cluster based on lat/lon
100
100
  const clusters = kmeans(
101
101
  3,
102
- items.map((x) => x.latlon),
102
+ cities.map((x) => x.latlon),
103
103
  {
104
104
  // custom centroid calc for geo locations
105
105
  // https://docs.thi.ng/umbrella/k-means/modules.html#meansLatLon
@@ -111,7 +111,7 @@ const clusters = kmeans(
111
111
 
112
112
  // print each cluster
113
113
  for (let c of clusters) {
114
- console.log(c.items.map((i) => items[i].id));
114
+ console.log(c.items.map((i) => cities[i].id));
115
115
  }
116
116
 
117
117
  // [ 'boston', 'detroit', 'new york', 'philadelphia' ]
package/kmeans.d.ts CHANGED
@@ -3,7 +3,8 @@ import type { ReadonlyVec } from "@thi.ng/vectors";
3
3
  import type { CentroidStrategy, Cluster, KMeansOpts } from "./api.js";
4
4
  /**
5
5
  * Takes an array of n-dimensional `samples` and attempts to assign them to up
6
- * to `k` clusters, using the behavior defined by (optionally) given `opts`.
6
+ * to `k` clusters (might produce less), using the behavior defined by
7
+ * (optionally) given `opts`.
7
8
  *
8
9
  * @remarks
9
10
  * https://en.wikipedia.org/wiki/K-medians_clustering
package/kmeans.js CHANGED
@@ -2,7 +2,6 @@ import { argmin } from "@thi.ng/distance/argmin";
2
2
  import { DIST_SQ } from "@thi.ng/distance/squared";
3
3
  import { assert } from "@thi.ng/errors/assert";
4
4
  import { SYSTEM } from "@thi.ng/random/system";
5
- import { uniqueIndices } from "@thi.ng/random/unique-indices";
6
5
  import { weightedRandom } from "@thi.ng/random/weighted-random";
7
6
  import { add } from "@thi.ng/vectors/add";
8
7
  import { median } from "@thi.ng/vectors/median";
@@ -10,7 +9,8 @@ import { mulN } from "@thi.ng/vectors/muln";
10
9
  import { zeroes } from "@thi.ng/vectors/setn";
11
10
  /**
12
11
  * Takes an array of n-dimensional `samples` and attempts to assign them to up
13
- * to `k` clusters, using the behavior defined by (optionally) given `opts`.
12
+ * to `k` clusters (might produce less), using the behavior defined by
13
+ * (optionally) given `opts`.
14
14
  *
15
15
  * @remarks
16
16
  * https://en.wikipedia.org/wiki/K-medians_clustering
@@ -32,27 +32,20 @@ export const kmeans = (k, samples, opts) => {
32
32
  assert(centroidIDs.length > 0, `missing initial centroids`);
33
33
  k = centroidIDs.length;
34
34
  const centroids = centroidIDs.map((i) => samples[i]);
35
- const clusters = [];
35
+ const clusters = new Uint32Array(num).fill(k);
36
36
  let update = true;
37
- outer: while (update && maxIter-- > 0) {
37
+ while (update && maxIter-- > 0) {
38
38
  update = assign(samples, centroids, clusters, dist);
39
+ if (!update)
40
+ break;
39
41
  for (let i = 0; i < k; i++) {
40
42
  const impl = strategy(dim);
41
43
  for (let j = 0; j < num; j++) {
42
44
  i === clusters[j] && impl.update(samples[j]);
43
45
  }
44
46
  const centroid = impl.finish();
45
- if (centroid) {
47
+ if (centroid)
46
48
  centroids[i] = centroid;
47
- }
48
- else {
49
- const numC = centroidIDs.length;
50
- uniqueIndices(1, num, centroidIDs, undefined, rnd);
51
- if (centroidIDs.length === numC)
52
- break outer;
53
- centroids[i] = samples[centroidIDs[numC]];
54
- update = true;
55
- }
56
49
  }
57
50
  }
58
51
  return buildClusters(centroids, clusters);
@@ -104,25 +97,29 @@ export const initKmeanspp = (k, samples, dist = DIST_SQ, rnd = SYSTEM) => {
104
97
  }
105
98
  return centroidIDs;
106
99
  };
107
- const assign = (samples, centroids, clusters, dist) => {
100
+ const assign = (samples, centroids, assignments, dist) => {
108
101
  let update = false;
109
- for (let i = 0, n = samples.length; i < n; i++) {
102
+ for (let i = samples.length; i-- > 0;) {
110
103
  const id = argmin(samples[i], centroids, dist);
111
- if (id !== clusters[i]) {
112
- clusters[i] = id;
104
+ if (id !== assignments[i]) {
105
+ assignments[i] = id;
113
106
  update = true;
114
107
  }
115
108
  }
116
109
  return update;
117
110
  };
118
- const buildClusters = (centroids, clusters) => {
119
- const indices = [];
120
- for (let i = 0, n = clusters.length; i < n; i++) {
121
- const c = clusters[i];
122
- (indices[c] ||
123
- (indices[c] = { id: c, centroid: centroids[c], items: [] })).items.push(i);
111
+ const buildClusters = (centroids, assignments) => {
112
+ const clusters = [];
113
+ for (let i = 0, n = assignments.length; i < n; i++) {
114
+ const id = assignments[i];
115
+ (clusters[id] ||
116
+ (clusters[id] = {
117
+ id,
118
+ centroid: centroids[id],
119
+ items: [],
120
+ })).items.push(i);
124
121
  }
125
- return indices;
122
+ return clusters.filter((x) => !!x);
126
123
  };
127
124
  /**
128
125
  * Default centroid strategy forming new centroids by averaging the position of
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thi.ng/k-means",
3
- "version": "0.5.39",
3
+ "version": "0.6.0",
4
4
  "description": "Configurable k-means & k-medians (with k-means++ initialization) for n-D vectors",
5
5
  "type": "module",
6
6
  "module": "./index.js",
@@ -34,19 +34,19 @@
34
34
  "test": "testament test"
35
35
  },
36
36
  "dependencies": {
37
- "@thi.ng/api": "^8.6.3",
38
- "@thi.ng/distance": "^2.1.39",
39
- "@thi.ng/errors": "^2.2.8",
40
- "@thi.ng/random": "^3.3.21",
41
- "@thi.ng/vectors": "^7.5.31"
37
+ "@thi.ng/api": "^8.7.1",
38
+ "@thi.ng/distance": "^2.2.1",
39
+ "@thi.ng/errors": "^2.2.10",
40
+ "@thi.ng/random": "^3.3.23",
41
+ "@thi.ng/vectors": "^7.6.1"
42
42
  },
43
43
  "devDependencies": {
44
- "@microsoft/api-extractor": "^7.33.7",
45
- "@thi.ng/testament": "^0.3.9",
46
- "rimraf": "^3.0.2",
44
+ "@microsoft/api-extractor": "^7.34.2",
45
+ "@thi.ng/testament": "^0.3.10",
46
+ "rimraf": "^4.1.2",
47
47
  "tools": "^0.0.1",
48
- "typedoc": "^0.23.22",
49
- "typescript": "^4.9.4"
48
+ "typedoc": "^0.23.24",
49
+ "typescript": "^4.9.5"
50
50
  },
51
51
  "keywords": [
52
52
  "cluster",
@@ -82,5 +82,5 @@
82
82
  "status": "beta",
83
83
  "year": 2021
84
84
  },
85
- "gitHead": "3f0b3e2a7c82aefc7e46fb4338369836b5e1b8cf\n"
85
+ "gitHead": "2b5a99a8af71670780875637299be9118b01084d\n"
86
86
  }