@thi.ng/k-means 1.1.1 → 2.0.1

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**: 2025-06-14T20:56:27Z
3
+ - **Last updated**: 2025-07-10T14:20:23Z
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.
@@ -11,6 +11,20 @@ See [Conventional Commits](https://conventionalcommits.org/) for commit guidelin
11
11
  **Note:** Unlisted _patch_ versions only involve non-code or otherwise excluded changes
12
12
  and/or version bumps of transitive dependencies.
13
13
 
14
+ # [2.0.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/k-means@2.0.0) (2025-06-24)
15
+
16
+ #### 🛑 Breaking changes
17
+
18
+ - update KMeansInit & kmeansPlusPlus() ([58d7eb1](https://github.com/thi-ng/umbrella/commit/58d7eb1))
19
+ - BREAKING CHANGE: update `KMeansInit` to return vectors not IDs
20
+ - update `KMeansInit` return type
21
+ - update `KMeansOpts.initial`
22
+ - rename `initKmeanspp()` => `kmeansPlusPlus()`
23
+
24
+ #### 🚀 Features
25
+
26
+ - add meanCut/medianCut() ([867889d](https://github.com/thi-ng/umbrella/commit/867889d))
27
+
14
28
  ## [1.1.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/k-means@1.1.0) (2025-06-14)
15
29
 
16
30
  #### 🚀 Features
package/README.md CHANGED
@@ -25,7 +25,16 @@
25
25
 
26
26
  ## About
27
27
 
28
- Configurable k-means & k-medians (with k-means++ initialization) for n-D vectors.
28
+ k-means & k-medians with customizable distance functions and centroid initializations for n-D vectors.
29
+
30
+ In addition to the main
31
+ [`kmeans()`](https://docs.thi.ng/umbrella/k-means/functions/kmeans.html)
32
+ implementation, the following k-means centroid initialization functions are
33
+ provided (can also be used in isolation to extract cluster centroids):
34
+
35
+ - [`kmeansPlusPlus()`](https://docs.thi.ng/umbrella/k-means/functions/kmeansPlusPlus.html)
36
+ - [`meanCut()`](https://docs.thi.ng/umbrella/k-means/functions/meanCut.html)
37
+ - [`medianCut()`](https://docs.thi.ng/umbrella/k-means/functions/medianCut.html)
29
38
 
30
39
  ## Status
31
40
 
@@ -59,7 +68,7 @@ For Node.js REPL:
59
68
  const kmeans = await import("@thi.ng/k-means");
60
69
  ```
61
70
 
62
- Package sizes (brotli'd, pre-treeshake): ESM: 898 bytes
71
+ Package sizes (brotli'd, pre-treeshake): ESM: 1.10 KB
63
72
 
64
73
  ## Dependencies
65
74
 
package/api.d.ts CHANGED
@@ -8,7 +8,7 @@ export interface KMeansOpts {
8
8
  * array given to {@link kmeans}) or a function producing such. If omitted,
9
9
  * {@link initKmeanspp} is used as default.
10
10
  */
11
- initial: number[] | KMeansInit<ReadonlyVec>;
11
+ initial: ReadonlyVec[] | KMeansInit;
12
12
  /**
13
13
  * Distance function/metric to use for finding nearest centroid.
14
14
  */
@@ -42,7 +42,7 @@ export interface KMeansOpts {
42
42
  /**
43
43
  * k-means initialization function, e.g. {@link initKmeanspp}.
44
44
  */
45
- export type KMeansInit<T extends ReadonlyVec> = (k: number, samples: T[], dist?: IDistance<ReadonlyVec>, rnd?: IRandom) => number[];
45
+ export type KMeansInit = (k: number, samples: ReadonlyVec[], dist?: IDistance<ReadonlyVec>, rnd?: IRandom) => ReadonlyVec[];
46
46
  export type CentroidStrategy = Fn<number, {
47
47
  update: Fn<ReadonlyVec, void>;
48
48
  finish: Fn0<Maybe<Vec>>;
package/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from "./api.js";
2
2
  export * from "./kmeans.js";
3
+ export * from "./mean-cut.js";
3
4
  //# sourceMappingURL=index.d.ts.map
package/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from "./api.js";
2
2
  export * from "./kmeans.js";
3
+ export * from "./mean-cut.js";
package/kmeans.d.ts CHANGED
@@ -41,7 +41,7 @@ export declare const kmeans: <T extends ReadonlyVec>(k: number, samples: T[], op
41
41
  * @param rnd -
42
42
  * @param exponent -
43
43
  */
44
- export declare const initKmeanspp: <T extends ReadonlyVec>(k: number, samples: T[], dist?: IDistance<ReadonlyVec>, rnd?: IRandom, exponent?: number) => number[];
44
+ export declare const kmeansPlusPlus: <T extends ReadonlyVec>(k: number, samples: T[], dist?: IDistance<ReadonlyVec>, rnd?: IRandom, exponent?: number) => T[];
45
45
  /**
46
46
  * Default centroid strategy forming new centroids by averaging the position of
47
47
  * participating samples.
package/kmeans.js CHANGED
@@ -18,10 +18,9 @@ const kmeans = (k, samples, opts = {}) => {
18
18
  rnd
19
19
  } = opts;
20
20
  const num = samples.length;
21
- const centroidIDs = Array.isArray(initial) ? initial : initial ? initial(k, samples, dist, rnd) : initKmeanspp(k, samples, dist, rnd, exponent);
22
- assert(centroidIDs.length > 0, `missing initial centroids`);
23
- k = centroidIDs.length;
24
- const centroids = centroidIDs.map((i) => samples[i]);
21
+ const centroids = Array.isArray(initial) ? initial : initial ? initial(k, samples, dist, rnd) : kmeansPlusPlus(k, samples, dist, rnd, exponent);
22
+ assert(centroids.length > 0, `missing initial centroids`);
23
+ k = centroids.length;
25
24
  const clusters = new Uint32Array(num).fill(k);
26
25
  let update = true;
27
26
  while (update && maxIter-- > 0) {
@@ -38,7 +37,7 @@ const kmeans = (k, samples, opts = {}) => {
38
37
  }
39
38
  return __buildClusters(centroids, clusters);
40
39
  };
41
- const initKmeanspp = (k, samples, dist = DIST_SQ, rnd = SYSTEM, exponent = 2) => {
40
+ const kmeansPlusPlus = (k, samples, dist = DIST_SQ, rnd = SYSTEM, exponent = 2) => {
42
41
  const num = samples.length;
43
42
  assert(num > 0, `missing samples`);
44
43
  k = Math.min(k, num);
@@ -61,7 +60,7 @@ const initKmeanspp = (k, samples, dist = DIST_SQ, rnd = SYSTEM, exponent = 2) =>
61
60
  centroidIDs.push(id);
62
61
  centroids.push(samples[id]);
63
62
  }
64
- return centroidIDs;
63
+ return centroids;
65
64
  };
66
65
  const __assign = (samples, centroids, assignments, dist) => {
67
66
  let update = false;
@@ -124,8 +123,8 @@ const meansLatLon = () => {
124
123
  };
125
124
  };
126
125
  export {
127
- initKmeanspp,
128
126
  kmeans,
127
+ kmeansPlusPlus,
129
128
  means,
130
129
  meansLatLon,
131
130
  medians
package/mean-cut.d.ts ADDED
@@ -0,0 +1,29 @@
1
+ import type { Fn } from "@thi.ng/api";
2
+ import type { ReadonlyVec } from "@thi.ng/vectors";
3
+ /**
4
+ * Mean-cut clustering, also usable as {@link kmeans} /
5
+ * {@link KMeansOpts.initial} centroid initialization. Returns up to `k`
6
+ * centroids for given `samples`.
7
+ *
8
+ * @remarks
9
+ * Only recommended for low-dimensional data.
10
+ *
11
+ * @param k
12
+ * @param samples
13
+ */
14
+ export declare const meanCut: <T extends ReadonlyVec>(k: number, samples: T[]) => import("@thi.ng/vectors").Vec<number>[];
15
+ /**
16
+ * Median-cut clustering, also usable as {@link kmeans} /
17
+ * {@link KMeansOpts.initial} centroid initialization. Returns up to `k`
18
+ * centroids for given `samples`.
19
+ *
20
+ * @remarks
21
+ * Only recommended for low-dimensional data.
22
+ *
23
+ * @param k
24
+ * @param samples
25
+ */
26
+ export declare const medianCut: <T extends ReadonlyVec>(k: number, samples: T[]) => import("@thi.ng/vectors").Vec<number>[];
27
+ /** @internal */
28
+ export declare const computeCutWith: (cut: Fn<ReadonlyVec, number>, samples: ReadonlyVec[], dim: number, depth: number) => ReadonlyVec[][];
29
+ //# sourceMappingURL=mean-cut.d.ts.map
package/mean-cut.js ADDED
@@ -0,0 +1,44 @@
1
+ import { mean, vmean } from "@thi.ng/vectors/mean";
2
+ import { vmedian } from "@thi.ng/vectors/median";
3
+ const meanCut = (k, samples) => computeCutWith(vmean, samples, samples[0].length, k).map(
4
+ (x) => mean([], x)
5
+ );
6
+ const medianCut = (k, samples) => computeCutWith(vmedian, samples, samples[0].length, k).map(
7
+ (x) => mean([], x)
8
+ );
9
+ const computeCutWith = (cut, samples, dim, depth) => {
10
+ if (!samples.length) return [];
11
+ if (depth <= 1) return [samples];
12
+ const channels = new Array(dim);
13
+ let maxRange = 0, maxRangeID = 0, i = 0, j, n = samples.length, min, max, value, range, channel;
14
+ for (; i < dim; i++) {
15
+ channel = channels[i] = new Array(n);
16
+ min = Infinity;
17
+ max = -Infinity;
18
+ for (j = 0; j < n; j++) {
19
+ value = channel[j] = samples[j][i];
20
+ if (value < min) min = value;
21
+ if (value > max) max = value;
22
+ }
23
+ range = max - min;
24
+ if (range > maxRange) {
25
+ maxRange = range;
26
+ maxRangeID = i;
27
+ }
28
+ }
29
+ channel = channels[maxRangeID];
30
+ const split = cut(channel);
31
+ const lo = [];
32
+ const hi = [];
33
+ for (j = 0; j < n; j++) {
34
+ (channel[j] <= split ? lo : hi).push(samples[j]);
35
+ }
36
+ return computeCutWith(cut, lo, dim, depth >> 1).concat(
37
+ computeCutWith(cut, hi, dim, depth + 1 >> 1)
38
+ );
39
+ };
40
+ export {
41
+ computeCutWith,
42
+ meanCut,
43
+ medianCut
44
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@thi.ng/k-means",
3
- "version": "1.1.1",
4
- "description": "Configurable k-means & k-medians (with k-means++ initialization) for n-D vectors",
3
+ "version": "2.0.1",
4
+ "description": "k-means & k-medians with customizable distance functions and centroid initializations for n-D vectors",
5
5
  "type": "module",
6
6
  "module": "./index.js",
7
7
  "typings": "./index.d.ts",
@@ -39,24 +39,30 @@
39
39
  "tool:tangle": "../../node_modules/.bin/tangle src/**/*.ts"
40
40
  },
41
41
  "dependencies": {
42
- "@thi.ng/api": "^8.11.29",
43
- "@thi.ng/distance": "^3.0.1",
44
- "@thi.ng/errors": "^2.5.35",
45
- "@thi.ng/random": "^4.1.20",
46
- "@thi.ng/vectors": "^8.3.1"
42
+ "@thi.ng/api": "^8.11.30",
43
+ "@thi.ng/distance": "^3.0.3",
44
+ "@thi.ng/errors": "^2.5.36",
45
+ "@thi.ng/random": "^4.1.21",
46
+ "@thi.ng/vectors": "^8.3.2"
47
47
  },
48
48
  "devDependencies": {
49
- "esbuild": "^0.25.5",
50
- "typedoc": "^0.28.5",
49
+ "esbuild": "^0.25.6",
50
+ "typedoc": "^0.28.7",
51
51
  "typescript": "^5.8.3"
52
52
  },
53
53
  "keywords": [
54
+ "centroid",
54
55
  "cluster",
55
56
  "distance",
57
+ "eucledian",
58
+ "haversine",
56
59
  "k-means",
57
60
  "k-means++",
58
61
  "k-medians",
62
+ "mean",
63
+ "median",
59
64
  "nd",
65
+ "partition",
60
66
  "self-organizing",
61
67
  "typescript"
62
68
  ],
@@ -79,6 +85,9 @@
79
85
  },
80
86
  "./kmeans": {
81
87
  "default": "./kmeans.js"
88
+ },
89
+ "./mean-cut": {
90
+ "default": "./mean-cut.js"
82
91
  }
83
92
  },
84
93
  "thi.ng": {
@@ -87,5 +96,5 @@
87
96
  "year": 2021,
88
97
  "screenshot": "examples/kmeans-viz.jpg"
89
98
  },
90
- "gitHead": "b076434a497b291ad33e81b1a15f6a71e2c82cc2\n"
99
+ "gitHead": "56d8f088389b22192a06e9a395b5eecebf47697a\n"
91
100
  }