@thi.ng/k-means 0.6.48 → 0.6.50

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-12-09T19:12:03Z
3
+ - **Last updated**: 2023-12-11T10:07:09Z
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.
package/README.md CHANGED
@@ -49,7 +49,7 @@ For Node.js REPL:
49
49
  const kMeans = await import("@thi.ng/k-means");
50
50
  ```
51
51
 
52
- Package sizes (brotli'd, pre-treeshake): ESM: 878 bytes
52
+ Package sizes (brotli'd, pre-treeshake): ESM: 868 bytes
53
53
 
54
54
  ## Dependencies
55
55
 
package/api.js CHANGED
@@ -1 +0,0 @@
1
- export {};
package/kmeans.js CHANGED
@@ -7,186 +7,129 @@ import { add } from "@thi.ng/vectors/add";
7
7
  import { median } from "@thi.ng/vectors/median";
8
8
  import { mulN } from "@thi.ng/vectors/muln";
9
9
  import { zeroes } from "@thi.ng/vectors/setn";
10
- /**
11
- * Takes an array of n-dimensional `samples` and attempts to assign them to up
12
- * to `k` clusters (might produce less), using the behavior defined by
13
- * (optionally) given `opts`.
14
- *
15
- * @remarks
16
- * https://en.wikipedia.org/wiki/K-medians_clustering
17
- *
18
- * @param k -
19
- * @param samples -
20
- * @param opts -
21
- */
22
- export const kmeans = (k, samples, opts) => {
23
- let { dist, initial, maxIter, rnd, strategy } = {
24
- dist: DIST_SQ,
25
- maxIter: 32,
26
- strategy: means,
27
- ...opts,
28
- };
29
- const num = samples.length;
30
- const dim = samples[0].length;
31
- const centroidIDs = initial || initKmeanspp(k, samples, dist, rnd);
32
- assert(centroidIDs.length > 0, `missing initial centroids`);
33
- k = centroidIDs.length;
34
- const centroids = centroidIDs.map((i) => samples[i]);
35
- const clusters = new Uint32Array(num).fill(k);
36
- let update = true;
37
- while (update && maxIter-- > 0) {
38
- update = assign(samples, centroids, clusters, dist);
39
- if (!update)
40
- break;
41
- for (let i = 0; i < k; i++) {
42
- const impl = strategy(dim);
43
- for (let j = 0; j < num; j++) {
44
- i === clusters[j] && impl.update(samples[j]);
45
- }
46
- const centroid = impl.finish();
47
- if (centroid)
48
- centroids[i] = centroid;
49
- }
10
+ const kmeans = (k, samples, opts) => {
11
+ let { dist, initial, maxIter, rnd, strategy } = {
12
+ dist: DIST_SQ,
13
+ maxIter: 32,
14
+ strategy: means,
15
+ ...opts
16
+ };
17
+ const num = samples.length;
18
+ const dim = samples[0].length;
19
+ const centroidIDs = initial || initKmeanspp(k, samples, dist, rnd);
20
+ assert(centroidIDs.length > 0, `missing initial centroids`);
21
+ k = centroidIDs.length;
22
+ const centroids = centroidIDs.map((i) => samples[i]);
23
+ const clusters = new Uint32Array(num).fill(k);
24
+ let update = true;
25
+ while (update && maxIter-- > 0) {
26
+ update = assign(samples, centroids, clusters, dist);
27
+ if (!update)
28
+ break;
29
+ for (let i = 0; i < k; i++) {
30
+ const impl = strategy(dim);
31
+ for (let j = 0; j < num; j++) {
32
+ i === clusters[j] && impl.update(samples[j]);
33
+ }
34
+ const centroid = impl.finish();
35
+ if (centroid)
36
+ centroids[i] = centroid;
50
37
  }
51
- return buildClusters(centroids, clusters);
38
+ }
39
+ return buildClusters(centroids, clusters);
52
40
  };
53
- /**
54
- * k-means++ initialization / selection of initial cluster centroids. Default
55
- * centroid initialization method for {@link kmeans}.
56
- *
57
- * @remarks
58
- * Might return fewer than `k` centroid IDs if the requested number cannot be
59
- * fulfilled (e.g. due to lower number of samples and/or distance metric).
60
- * Throws an error if `samples` are empty.
61
- *
62
- * @remarks
63
- * References:
64
- * - https://en.wikipedia.org/wiki/K-means%2B%2B
65
- * - http://ilpubs.stanford.edu:8090/778/1/2006-13.pdf
66
- * - http://vldb.org/pvldb/vol5/p622_bahmanbahmani_vldb2012.pdf (TODO)
67
- *
68
- * @param k -
69
- * @param samples -
70
- * @param dist -
71
- * @param rnd -
72
- */
73
- export const initKmeanspp = (k, samples, dist = DIST_SQ, rnd = SYSTEM) => {
74
- const num = samples.length;
75
- assert(num > 0, `missing samples`);
76
- k = Math.min(k, num);
77
- const centroidIDs = [rnd.int() % num];
78
- const centroids = [samples[centroidIDs[0]]];
79
- const indices = new Array(num).fill(0).map((_, i) => i);
80
- const metric = dist.metric;
81
- while (centroidIDs.length < k) {
82
- let psum = 0;
83
- const probs = samples.map((p) => {
84
- const d = dist.from(metric(p, centroids[argmin(p, centroids, dist)])) **
85
- 2;
86
- psum += d;
87
- return d;
88
- });
89
- if (!psum)
90
- break;
91
- let id;
92
- do {
93
- id = weightedRandom(indices, probs, rnd)();
94
- } while (centroidIDs.includes(id));
95
- centroidIDs.push(id);
96
- centroids.push(samples[id]);
97
- }
98
- return centroidIDs;
41
+ const initKmeanspp = (k, samples, dist = DIST_SQ, rnd = SYSTEM) => {
42
+ const num = samples.length;
43
+ assert(num > 0, `missing samples`);
44
+ k = Math.min(k, num);
45
+ const centroidIDs = [rnd.int() % num];
46
+ const centroids = [samples[centroidIDs[0]]];
47
+ const indices = new Array(num).fill(0).map((_, i) => i);
48
+ const metric = dist.metric;
49
+ while (centroidIDs.length < k) {
50
+ let psum = 0;
51
+ const probs = samples.map((p) => {
52
+ const d = dist.from(metric(p, centroids[argmin(p, centroids, dist)])) ** 2;
53
+ psum += d;
54
+ return d;
55
+ });
56
+ if (!psum)
57
+ break;
58
+ let id;
59
+ do {
60
+ id = weightedRandom(indices, probs, rnd)();
61
+ } while (centroidIDs.includes(id));
62
+ centroidIDs.push(id);
63
+ centroids.push(samples[id]);
64
+ }
65
+ return centroidIDs;
99
66
  };
100
67
  const assign = (samples, centroids, assignments, dist) => {
101
- let update = false;
102
- for (let i = samples.length; i-- > 0;) {
103
- const id = argmin(samples[i], centroids, dist);
104
- if (id !== assignments[i]) {
105
- assignments[i] = id;
106
- update = true;
107
- }
68
+ let update = false;
69
+ for (let i = samples.length; i-- > 0; ) {
70
+ const id = argmin(samples[i], centroids, dist);
71
+ if (id !== assignments[i]) {
72
+ assignments[i] = id;
73
+ update = true;
108
74
  }
109
- return update;
75
+ }
76
+ return update;
110
77
  };
111
78
  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);
121
- }
122
- return clusters.filter((x) => !!x);
79
+ const clusters = [];
80
+ for (let i = 0, n = assignments.length; i < n; i++) {
81
+ const id = assignments[i];
82
+ (clusters[id] || (clusters[id] = {
83
+ id,
84
+ centroid: centroids[id],
85
+ items: []
86
+ })).items.push(i);
87
+ }
88
+ return clusters.filter((x) => !!x);
123
89
  };
124
- /**
125
- * Default centroid strategy forming new centroids by averaging the position of
126
- * participating samples.
127
- *
128
- * @param dim -
129
- */
130
- export const means = (dim) => {
131
- const acc = zeroes(dim);
132
- let n = 0;
133
- return {
134
- update: (p) => {
135
- add(acc, acc, p);
136
- n++;
137
- },
138
- finish: () => (n ? mulN(acc, acc, 1 / n) : undefined),
139
- };
90
+ const means = (dim) => {
91
+ const acc = zeroes(dim);
92
+ let n = 0;
93
+ return {
94
+ update: (p) => {
95
+ add(acc, acc, p);
96
+ n++;
97
+ },
98
+ finish: () => n ? mulN(acc, acc, 1 / n) : void 0
99
+ };
140
100
  };
141
- /**
142
- * Centroid strategy forming new centroids via componentwise medians.
143
- *
144
- * @remarks
145
- * https://en.wikipedia.org/wiki/K-medians_clustering
146
- */
147
- export const medians = () => {
148
- const acc = [];
149
- return {
150
- update: (p) => acc.push(p),
151
- finish: () => (acc.length ? median([], acc) : undefined),
152
- };
101
+ const medians = () => {
102
+ const acc = [];
103
+ return {
104
+ update: (p) => acc.push(p),
105
+ finish: () => acc.length ? median([], acc) : void 0
106
+ };
107
+ };
108
+ const meansLatLon = () => {
109
+ let lat = 0;
110
+ let lon = 0;
111
+ let n = 0;
112
+ return {
113
+ update: ([$lat, $lon]) => {
114
+ lat += $lat < 0 ? $lat + 360 : $lat;
115
+ lon += $lon;
116
+ n++;
117
+ },
118
+ finish: () => {
119
+ if (!n)
120
+ return;
121
+ lat /= n;
122
+ if (lat > 180)
123
+ lat -= 360;
124
+ lon /= n;
125
+ return [lat, lon];
126
+ }
127
+ };
153
128
  };
154
- /**
155
- * Means centroid strategy for decimal degree lat/lon positions (e.g. WGS84).
156
- * Unlike the default {@link means} strategy, this one treats latitude values
157
- * correctly in terms of the ±180 deg boundary and ensures samples on either
158
- * side of the Pacific are forming correct centroids.
159
- *
160
- * @remarks
161
- * When using this strategy, you should also use the
162
- * [`HAVERSINE_LATLON`](https://docs.thi.ng/umbrella/distance/variables/HAVERSINE_LATLON.html)
163
- * distance metric for {@link KMeansOpts.distance}.
164
- *
165
- * @example
166
- * ```ts
167
- * kmeans(3, [...], { strategy: meansLatLon, dist: HAVERSINE_LATLON })
168
- * ```
169
- *
170
- * https://en.wikipedia.org/wiki/World_Geodetic_System
171
- */
172
- export const meansLatLon = () => {
173
- let lat = 0;
174
- let lon = 0;
175
- let n = 0;
176
- return {
177
- update: ([$lat, $lon]) => {
178
- lat += $lat < 0 ? $lat + 360 : $lat;
179
- lon += $lon;
180
- n++;
181
- },
182
- finish: () => {
183
- if (!n)
184
- return;
185
- lat /= n;
186
- if (lat > 180)
187
- lat -= 360;
188
- lon /= n;
189
- return [lat, lon];
190
- },
191
- };
129
+ export {
130
+ initKmeanspp,
131
+ kmeans,
132
+ means,
133
+ meansLatLon,
134
+ medians
192
135
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thi.ng/k-means",
3
- "version": "0.6.48",
3
+ "version": "0.6.50",
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",
@@ -24,7 +24,9 @@
24
24
  "author": "Karsten Schmidt (https://thi.ng)",
25
25
  "license": "Apache-2.0",
26
26
  "scripts": {
27
- "build": "yarn clean && tsc --declaration",
27
+ "build": "yarn build:esbuild && yarn build:decl",
28
+ "build:decl": "tsc --declaration --emitDeclarationOnly",
29
+ "build:esbuild": "esbuild --format=esm --platform=neutral --target=es2022 --tsconfig=tsconfig.json --outdir=. src/**/*.ts",
28
30
  "clean": "rimraf --glob '*.js' '*.d.ts' '*.map' doc",
29
31
  "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts",
30
32
  "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose",
@@ -33,14 +35,15 @@
33
35
  "test": "bun test"
34
36
  },
35
37
  "dependencies": {
36
- "@thi.ng/api": "^8.9.11",
37
- "@thi.ng/distance": "^2.4.33",
38
- "@thi.ng/errors": "^2.4.5",
39
- "@thi.ng/random": "^3.6.17",
40
- "@thi.ng/vectors": "^7.8.8"
38
+ "@thi.ng/api": "^8.9.12",
39
+ "@thi.ng/distance": "^2.4.35",
40
+ "@thi.ng/errors": "^2.4.6",
41
+ "@thi.ng/random": "^3.6.18",
42
+ "@thi.ng/vectors": "^7.8.10"
41
43
  },
42
44
  "devDependencies": {
43
45
  "@microsoft/api-extractor": "^7.38.3",
46
+ "esbuild": "^0.19.8",
44
47
  "rimraf": "^5.0.5",
45
48
  "tools": "^0.0.1",
46
49
  "typedoc": "^0.25.4",
@@ -80,5 +83,5 @@
80
83
  "status": "beta",
81
84
  "year": 2021
82
85
  },
83
- "gitHead": "25f2ac8ff795a432a930119661b364d4d93b59a0\n"
86
+ "gitHead": "22e36fa838e5431d40165384918b395603bbd92f\n"
84
87
  }