w-cluster 1.0.19 → 1.0.21

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.
Files changed (73) hide show
  1. package/.github/workflows/ci-test.yml +3 -3
  2. package/README.md +4 -6
  3. package/babel.config.js +4 -3
  4. package/dist/w-cluster.umd.js +2 -2
  5. package/dist/w-cluster.umd.js.map +1 -1
  6. package/dist/w-cluster.wk.umd.js +1 -1
  7. package/docs/DenseMatrix.html +202 -0
  8. package/docs/DistancePair.html +202 -0
  9. package/docs/LowerTriangle.html +202 -0
  10. package/docs/Rec.html +202 -0
  11. package/docs/Reco.html +202 -0
  12. package/docs/WCluster.mjs.html +8 -4
  13. package/docs/examples/ex-PCA.html +1 -1
  14. package/docs/examples/ex-cluster-webworker.html +1 -1
  15. package/docs/examples/ex-cluster.html +1 -1
  16. package/docs/global.html +6799 -164
  17. package/docs/index.html +2 -2
  18. package/docs/k-medoids_alternating.mjs.html +144 -0
  19. package/docs/k-medoids_arrayadapter.mjs.html +107 -0
  20. package/docs/k-medoids_dynmsc.mjs.html +255 -0
  21. package/docs/k-medoids_fastermsc.mjs.html +411 -0
  22. package/docs/k-medoids_fasterpam.mjs.html +305 -0
  23. package/docs/k-medoids_fastmsc.mjs.html +166 -0
  24. package/docs/k-medoids_fastpam1.mjs.html +132 -0
  25. package/docs/k-medoids_initialization.mjs.html +107 -0
  26. package/docs/k-medoids_pam.mjs.html +275 -0
  27. package/docs/k-medoids_pammedsil.mjs.html +307 -0
  28. package/docs/k-medoids_pamsil.mjs.html +225 -0
  29. package/docs/k-medoids_par_fasterpam.mjs.html +134 -0
  30. package/docs/k-medoids_par_silhouette.mjs.html +126 -0
  31. package/docs/k-medoids_silhouette.mjs.html +193 -0
  32. package/docs/k-medoids_util.mjs.html +135 -0
  33. package/g-PCA-nodeworker.mjs +1 -1
  34. package/g-PCA.mjs +1 -1
  35. package/g-cluster-kMeans-large.mjs +83 -0
  36. package/g-cluster-kMeans-nodeworker.mjs +181 -0
  37. package/g-cluster-kMeans.mjs +178 -0
  38. package/g-cluster-kMedoids-large-suggest.mjs +95 -0
  39. package/g-cluster-kMedoids-large.mjs +104 -0
  40. package/{g-cluster-nodeworker.mjs → g-cluster-kMedoids-nodeworker.mjs} +1 -2
  41. package/{g-cluster.mjs → g-cluster-kMedoids-simple.mjs} +1 -1
  42. package/package.json +7 -7
  43. package/script.txt +18 -0
  44. package/src/WCluster.mjs +6 -2
  45. package/src/WClusterCore.mjs +30 -21
  46. package/src/WClusterMat.mjs +98 -24
  47. package/src/WPCAMat.mjs +2 -2
  48. package/src/jaccardBitset.mjs +63 -0
  49. package/src/k-medoids/README.md +124 -0
  50. package/src/k-medoids/alternating.mjs +72 -0
  51. package/src/k-medoids/arrayadapter.mjs +35 -0
  52. package/src/k-medoids/dynmsc.mjs +183 -0
  53. package/src/k-medoids/fastermsc.mjs +339 -0
  54. package/src/k-medoids/fasterpam.mjs +233 -0
  55. package/src/k-medoids/fastmsc.mjs +94 -0
  56. package/src/k-medoids/fastpam1.mjs +60 -0
  57. package/src/k-medoids/index.mjs +37 -0
  58. package/src/k-medoids/initialization.mjs +35 -0
  59. package/src/k-medoids/package.json +8 -0
  60. package/src/k-medoids/pam.mjs +203 -0
  61. package/src/k-medoids/pammedsil.mjs +235 -0
  62. package/src/k-medoids/pamsil.mjs +153 -0
  63. package/src/k-medoids/par_fasterpam.mjs +62 -0
  64. package/src/k-medoids/par_silhouette.mjs +54 -0
  65. package/src/k-medoids/silhouette.mjs +121 -0
  66. package/src/k-medoids/test.mjs +357 -0
  67. package/src/k-medoids/util.mjs +63 -0
  68. package/test/jaccardBitset.test.mjs +141 -0
  69. package/test/kMeans.test.mjs +278 -0
  70. package/test/kMedoids-large-suggest.test.mjs +140 -0
  71. package/test/kMedoids-large.test.mjs +142 -0
  72. package/test/{cluster.test.mjs → kMedoids-simple.test.mjs} +5 -5
  73. package/toolg/gDocsExams.mjs +1 -1
@@ -0,0 +1,278 @@
1
+ import assert from 'assert'
2
+ import WCluster from '../src/WCluster.mjs'
3
+
4
+
5
+ describe(`k-means`, function() {
6
+
7
+ let mode = 'k-means'
8
+ let seed = 0 //給定seed使k-means隨機初始化固定, 分群結果可重現以利穩定測試
9
+
10
+ let mat = [
11
+ [40, 50, 60],
12
+ [50, 70, 60],
13
+ [80, 70, 90],
14
+ [50, 60, 80]
15
+ ]
16
+ let clmat = {
17
+ 'keys': null,
18
+ 'ginds': [
19
+ [0, 1, 3],
20
+ [2]
21
+ ],
22
+ 'gmat': [
23
+ [
24
+ [-1.704002697669786, 0.43087564048799354],
25
+ [-0.2509699164590232, -1.1519035967558147],
26
+ [-0.03633718671228592, 0.48858292291798827]
27
+ ],
28
+ [
29
+ [1.9913098008410954, 0.23244503334983269]
30
+ ]
31
+ ],
32
+ 'gltdt': [
33
+ [
34
+ [40, 50, 60],
35
+ [50, 70, 60],
36
+ [50, 60, 80]
37
+ ],
38
+ [
39
+ [80, 70, 90]
40
+ ]
41
+ ]
42
+ }
43
+
44
+ it(`should return '${JSON.stringify(clmat)}' when input ${JSON.stringify(mat)}, { mode, kNumber: 2, nCompNIPALS: 2, seed }`, async function() {
45
+ let r = await WCluster.cluster(mat, { mode, kNumber: 2, nCompNIPALS: 2, seed })
46
+ assert.strict.deepStrictEqual(r, clmat)
47
+ })
48
+
49
+ let mat2 = [
50
+ [1040, 50, 60],
51
+ [1050, 70, 60],
52
+ [1080, 70, 90],
53
+ [1050, 60, 80]
54
+ ]
55
+ let clmat2 = {
56
+ 'keys': null,
57
+ 'ginds': [
58
+ [0, 1, 3],
59
+ [2]
60
+ ],
61
+ 'gmat': [
62
+ [
63
+ [-1.704002697669786, 0.43087564048799354],
64
+ [-0.2509699164590232, -1.1519035967558147],
65
+ [-0.03633718671228592, 0.48858292291798827]
66
+ ],
67
+ [
68
+ [1.9913098008410954, 0.23244503334983269]
69
+ ]
70
+ ],
71
+ 'gltdt': [
72
+ [
73
+ [1040, 50, 60],
74
+ [1050, 70, 60],
75
+ [1050, 60, 80]
76
+ ],
77
+ [
78
+ [1080, 70, 90]
79
+ ]
80
+ ]
81
+ }
82
+
83
+ it(`should return '${JSON.stringify(clmat2)}' when input ${JSON.stringify(mat2)}, { mode, kNumber: 2, nCompNIPALS: 2, seed }`, async function() {
84
+ let r = await WCluster.cluster(mat2, { mode, kNumber: 2, nCompNIPALS: 2, seed })
85
+ assert.strict.deepStrictEqual(r, clmat2)
86
+ })
87
+
88
+ let mat3 = [
89
+ [11040, 50, 60],
90
+ [13050, 70, 60],
91
+ [15080, 70, 90],
92
+ [17050, 60, 80]
93
+ ]
94
+ let clmat3 = {
95
+ 'keys': null,
96
+ 'ginds': [
97
+ [0],
98
+ [1, 2, 3]
99
+ ],
100
+ 'gmat': [
101
+ [
102
+ [-1.8599655569892897, 0.4908764271508211]
103
+ ],
104
+ [
105
+ [-0.3950941652793108, -1.0977355294143445],
106
+ [1.3430199944041517, -0.17213692267477554],
107
+ [0.912039727864449, 0.778996024938299]
108
+ ]
109
+ ],
110
+ 'gltdt': [
111
+ [
112
+ [11040, 50, 60]
113
+ ],
114
+ [
115
+ [13050, 70, 60],
116
+ [15080, 70, 90],
117
+ [17050, 60, 80]
118
+ ]
119
+ ]
120
+ }
121
+
122
+ it(`should return '${JSON.stringify(clmat3)}' when input ${JSON.stringify(mat3)}, { mode, kNumber: 2, nCompNIPALS: 2, seed }`, async function() {
123
+ let r = await WCluster.cluster(mat3, { mode, kNumber: 2, nCompNIPALS: 2, seed })
124
+ assert.strict.deepStrictEqual(r, clmat3)
125
+ })
126
+
127
+ let ltdt = [
128
+ { name: 'Cameron', a: 40, b: 50, c: 60 },
129
+ { name: 'Buckley', a: 50, b: 70, c: 60 },
130
+ { name: 'Paul', a: 80, b: 70, c: 90 },
131
+ { name: 'Fawcett', a: 50, b: 60, c: 80 },
132
+ ]
133
+ let clltdt = {
134
+ 'keys': ['a', 'b', 'c'],
135
+ 'ginds': [
136
+ [0, 1, 3],
137
+ [2]
138
+ ],
139
+ 'gmat': [
140
+ [
141
+ [-1.704002697669786, 0.43087564048799354],
142
+ [-0.2509699164590232, -1.1519035967558147],
143
+ [-0.03633718671228592, 0.48858292291798827]
144
+ ],
145
+ [
146
+ [1.9913098008410954, 0.23244503334983269]
147
+ ]
148
+ ],
149
+ 'gltdt': [
150
+ [
151
+ { 'name': 'Cameron', 'a': 40, 'b': 50, 'c': 60 },
152
+ { 'name': 'Buckley', 'a': 50, 'b': 70, 'c': 60 },
153
+ { 'name': 'Fawcett', 'a': 50, 'b': 60, 'c': 80 }
154
+ ],
155
+ [
156
+ { 'name': 'Paul', 'a': 80, 'b': 70, 'c': 90 }
157
+ ]
158
+ ]
159
+ }
160
+
161
+ it(`should return '${JSON.stringify(clltdt)}' when input ${JSON.stringify(ltdt)}, { mode, kNumber: 2, nCompNIPALS: 2, seed }`, async function() {
162
+ let r = await WCluster.cluster(ltdt, { mode, kNumber: 2, nCompNIPALS: 2, seed })
163
+ assert.strict.deepStrictEqual(r, clltdt)
164
+ })
165
+
166
+ it(`should be deterministic (same result on repeated runs) when given seed`, async function() {
167
+ let r1 = await WCluster.cluster(mat, { mode, kNumber: 2, nCompNIPALS: 2, seed })
168
+ let r2 = await WCluster.cluster(mat, { mode, kNumber: 2, nCompNIPALS: 2, seed })
169
+ assert.strict.deepStrictEqual(r1, r2)
170
+ })
171
+
172
+ it(`should not throw and cover all indices when no seed (random init)`, async function() {
173
+ let r = await WCluster.cluster(mat, { mode, kNumber: 2, nCompNIPALS: 2 })
174
+ let flat = r.ginds.flat().sort((a, b) => a - b)
175
+ assert.strict.deepStrictEqual(flat, [0, 1, 2, 3])
176
+ })
177
+
178
+ it(`should return 'Error: data is not eff. array' when input 123`, async function() {
179
+ let r = ''
180
+ await WCluster.cluster(123, { mode, seed })
181
+ .catch((err) => {
182
+ r = err.toString()
183
+ })
184
+ let rr = 'Error: data is not eff. array'
185
+ assert.strict.deepStrictEqual(r, rr)
186
+ })
187
+
188
+ it(`should return 'Error: data is not eff. array' when input 123.456`, async function() {
189
+ let r = ''
190
+ await WCluster.cluster(123.456, { mode, seed })
191
+ .catch((err) => {
192
+ r = err.toString()
193
+ })
194
+ let rr = 'Error: data is not eff. array'
195
+ assert.strict.deepStrictEqual(r, rr)
196
+ })
197
+
198
+ it(`should return 'Error: data is not eff. array' when input '123'`, async function() {
199
+ let r = ''
200
+ await WCluster.cluster('123', { mode, seed })
201
+ .catch((err) => {
202
+ r = err.toString()
203
+ })
204
+ let rr = 'Error: data is not eff. array'
205
+ assert.strict.deepStrictEqual(r, rr)
206
+ })
207
+
208
+ it(`should return 'Error: data is not eff. array' when input '123.456'`, async function() {
209
+ let r = ''
210
+ await WCluster.cluster('123.456', { mode, seed })
211
+ .catch((err) => {
212
+ r = err.toString()
213
+ })
214
+ let rr = 'Error: data is not eff. array'
215
+ assert.strict.deepStrictEqual(r, rr)
216
+ })
217
+
218
+ it(`should return 'Error: data is not eff. array' when input true`, async function() {
219
+ let r = ''
220
+ await WCluster.cluster(true, { mode, seed })
221
+ .catch((err) => {
222
+ r = err.toString()
223
+ })
224
+ let rr = 'Error: data is not eff. array'
225
+ assert.strict.deepStrictEqual(r, rr)
226
+ })
227
+
228
+ it(`should return 'Error: data is not eff. array' when input ''`, async function() {
229
+ let r = ''
230
+ await WCluster.cluster('', { mode, seed })
231
+ .catch((err) => {
232
+ r = err.toString()
233
+ })
234
+ let rr = 'Error: data is not eff. array'
235
+ assert.strict.deepStrictEqual(r, rr)
236
+ })
237
+
238
+ it(`should return 'Error: data is not eff. array' when input []`, async function() {
239
+ let r = ''
240
+ await WCluster.cluster([], { mode, seed })
241
+ .catch((err) => {
242
+ r = err.toString()
243
+ })
244
+ let rr = 'Error: data is not eff. array'
245
+ assert.strict.deepStrictEqual(r, rr)
246
+ })
247
+
248
+ it(`should return 'Error: data is not eff. array' when input {}`, async function() {
249
+ let r = ''
250
+ await WCluster.cluster({}, { mode, seed })
251
+ .catch((err) => {
252
+ r = err.toString()
253
+ })
254
+ let rr = 'Error: data is not eff. array'
255
+ assert.strict.deepStrictEqual(r, rr)
256
+ })
257
+
258
+ it(`should return 'Error: data is not eff. array' when input null`, async function() {
259
+ let r = ''
260
+ await WCluster.cluster(null, { mode, seed })
261
+ .catch((err) => {
262
+ r = err.toString()
263
+ })
264
+ let rr = 'Error: data is not eff. array'
265
+ assert.strict.deepStrictEqual(r, rr)
266
+ })
267
+
268
+ it(`should return 'Error: data is not eff. array' when input undefined`, async function() {
269
+ let r = ''
270
+ await WCluster.cluster(undefined, { mode, seed })
271
+ .catch((err) => {
272
+ r = err.toString()
273
+ })
274
+ let rr = 'Error: data is not eff. array'
275
+ assert.strict.deepStrictEqual(r, rr)
276
+ })
277
+
278
+ })
@@ -0,0 +1,140 @@
1
+ import assert from 'assert'
2
+ import WCluster from '../src/WCluster.mjs'
3
+
4
+
5
+ //確定性PRNG(mulberry32), 使測試資料可重現, 不依賴Math.random
6
+ function mulberry32(a) {
7
+ return function() {
8
+ a |= 0
9
+ a = a + 0x6D2B79F5 | 0
10
+ let t = Math.imul(a ^ a >>> 15, 1 | a)
11
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t
12
+ return ((t ^ t >>> 14) >>> 0) / 4294967296
13
+ }
14
+ }
15
+
16
+ //產生G個分離良好的數值blob(交錯排列, 使各blob點散佈於資料序中), 回傳{data, truth}
17
+ function genBlobs(G, m, D, sep, jit, seed) {
18
+ let rnd = mulberry32(seed)
19
+ let centers = []
20
+ for (let g = 0; g < G; g++) {
21
+ centers.push(Array.from({ length: D }, (_, d) => g * sep + d * 3))
22
+ }
23
+ let data = []
24
+ let truth = []
25
+ for (let i = 0; i < m; i++) {
26
+ for (let g = 0; g < G; g++) {
27
+ data.push(centers[g].map((c) => c + (rnd() * 2 - 1) * jit))
28
+ truth.push(g)
29
+ }
30
+ }
31
+ return { data, truth }
32
+ }
33
+
34
+ //產生G個二元blob(Jaccard場景), 各blob一組base活躍位 + 小幅翻轉
35
+ function genBinaryBlobs(G, m, D, active, flips, seed) {
36
+ let rnd = mulberry32(seed)
37
+ let bases = []
38
+ for (let g = 0; g < G; g++) {
39
+ let base = new Array(D).fill(0)
40
+ let s = new Set()
41
+ while (s.size < active) {
42
+ s.add(Math.floor(rnd() * D))
43
+ }
44
+ s.forEach((i) => { base[i] = 1 })
45
+ bases.push(base)
46
+ }
47
+ let data = []
48
+ let truth = []
49
+ for (let i = 0; i < m; i++) {
50
+ for (let g = 0; g < G; g++) {
51
+ let v = bases[g].slice()
52
+ for (let f = 0; f < flips; f++) {
53
+ let p = Math.floor(rnd() * D)
54
+ v[p] = v[p] ? 0 : 1
55
+ }
56
+ data.push(v)
57
+ truth.push(g)
58
+ }
59
+ }
60
+ return { data, truth }
61
+ }
62
+
63
+ //Jaccard距離(0/1陣列)
64
+ let jaccard = (a, b) => {
65
+ let inter = 0
66
+ let uni = 0
67
+ for (let i = 0; i < a.length; i++) {
68
+ let x = a[i]
69
+ let y = b[i]
70
+ if (x & y) inter++
71
+ if (x | y) uni++
72
+ }
73
+ return uni === 0 ? 0 : 1 - inter / uni
74
+ }
75
+
76
+ //cluster purity: 各分群以多數ground-truth標籤計, 全對則為1
77
+ function purity(ginds, truth) {
78
+ let correct = 0
79
+ let n = truth.length
80
+ for (let g of ginds) {
81
+ let cnt = {}
82
+ for (let i of g) {
83
+ cnt[truth[i]] = (cnt[truth[i]] || 0) + 1
84
+ }
85
+ correct += Math.max(...Object.values(cnt))
86
+ }
87
+ return correct / n
88
+ }
89
+
90
+ //flat是否恰好涵蓋0..n-1各一次(不漏不重)
91
+ function coversAll(ginds, n) {
92
+ let flat = ginds.flat().sort((a, b) => a - b)
93
+ if (flat.length !== n) {
94
+ return false
95
+ }
96
+ for (let i = 0; i < n; i++) {
97
+ if (flat[i] !== i) {
98
+ return false
99
+ }
100
+ }
101
+ return true
102
+ }
103
+
104
+
105
+ describe(`k-medoids large suggest`, function() {
106
+
107
+ this.timeout(60000)
108
+
109
+ it(`should perfectly recover 6 well-separated numeric blobs (n=120, usePCA:false, fasterPAM)`, async function() {
110
+ let G = 6
111
+ let m = 20
112
+ let n = G * m
113
+ let { data, truth } = genBlobs(G, m, 4, 100, 5, 42)
114
+ let r = await WCluster.cluster(data, { mode: 'k-medoids', kNumber: G, usePCA: false, useMethod: 'fasterPAM' })
115
+ assert.strict.strictEqual(coversAll(r.ginds, n), true)
116
+ assert.strict.strictEqual(r.ginds.length, G)
117
+ assert.strict.strictEqual(purity(r.ginds, truth), 1)
118
+ })
119
+
120
+ it(`should perfectly recover 6 binary blobs with Jaccard distance (n=120, D=120, usePCA:false, fasterPAM)`, async function() {
121
+ let G = 6
122
+ let m = 20
123
+ let n = G * m
124
+ let { data, truth } = genBinaryBlobs(G, m, 120, 18, 3, 42)
125
+ let r = await WCluster.cluster(data, { mode: 'k-medoids', kNumber: G, usePCA: false, funDist: jaccard, useMethod: 'fasterPAM' })
126
+ assert.strict.strictEqual(coversAll(r.ginds, n), true)
127
+ assert.strict.strictEqual(r.ginds.length, G)
128
+ assert.strict.strictEqual(purity(r.ginds, truth), 1)
129
+ })
130
+
131
+ it(`should be deterministic on larger data (same ginds on repeated runs, fasterPAM)`, async function() {
132
+ let G = 6
133
+ let m = 20
134
+ let { data } = genBlobs(G, m, 4, 100, 5, 42)
135
+ let r1 = await WCluster.cluster(data, { mode: 'k-medoids', kNumber: G, usePCA: false, useMethod: 'fasterPAM' })
136
+ let r2 = await WCluster.cluster(data, { mode: 'k-medoids', kNumber: G, usePCA: false, useMethod: 'fasterPAM' })
137
+ assert.strict.deepStrictEqual(r1.ginds, r2.ginds)
138
+ })
139
+
140
+ })
@@ -0,0 +1,142 @@
1
+ import assert from 'assert'
2
+ import WCluster from '../src/WCluster.mjs'
3
+
4
+
5
+ //確定性PRNG(mulberry32), 使測試資料可重現, 不依賴Math.random
6
+ function mulberry32(a) {
7
+ return function() {
8
+ a |= 0
9
+ a = a + 0x6D2B79F5 | 0
10
+ let t = Math.imul(a ^ a >>> 15, 1 | a)
11
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t
12
+ return ((t ^ t >>> 14) >>> 0) / 4294967296
13
+ }
14
+ }
15
+
16
+ //產生G個分離良好的數值blob(交錯排列, 使各blob點散佈於資料序中), 回傳{data, truth}
17
+ function genBlobs(G, m, D, sep, jit, seed) {
18
+ let rnd = mulberry32(seed)
19
+ let centers = []
20
+ for (let g = 0; g < G; g++) {
21
+ centers.push(Array.from({ length: D }, (_, d) => g * sep + d * 3))
22
+ }
23
+ let data = []
24
+ let truth = []
25
+ for (let i = 0; i < m; i++) {
26
+ for (let g = 0; g < G; g++) {
27
+ data.push(centers[g].map((c) => c + (rnd() * 2 - 1) * jit))
28
+ truth.push(g)
29
+ }
30
+ }
31
+ return { data, truth }
32
+ }
33
+
34
+ //產生G個二元blob(Jaccard場景), 各blob一組base活躍位 + 小幅翻轉
35
+ function genBinaryBlobs(G, m, D, active, flips, seed) {
36
+ let rnd = mulberry32(seed)
37
+ let bases = []
38
+ for (let g = 0; g < G; g++) {
39
+ let base = new Array(D).fill(0)
40
+ let s = new Set()
41
+ while (s.size < active) {
42
+ s.add(Math.floor(rnd() * D))
43
+ }
44
+ s.forEach((i) => {
45
+ base[i] = 1
46
+ })
47
+ bases.push(base)
48
+ }
49
+ let data = []
50
+ let truth = []
51
+ for (let i = 0; i < m; i++) {
52
+ for (let g = 0; g < G; g++) {
53
+ let v = bases[g].slice()
54
+ for (let f = 0; f < flips; f++) {
55
+ let p = Math.floor(rnd() * D)
56
+ v[p] = v[p] ? 0 : 1
57
+ }
58
+ data.push(v)
59
+ truth.push(g)
60
+ }
61
+ }
62
+ return { data, truth }
63
+ }
64
+
65
+ //Jaccard距離(0/1陣列)
66
+ let jaccard = (a, b) => {
67
+ let inter = 0
68
+ let uni = 0
69
+ for (let i = 0; i < a.length; i++) {
70
+ let x = a[i]
71
+ let y = b[i]
72
+ if (x & y) inter++
73
+ if (x | y) uni++
74
+ }
75
+ return uni === 0 ? 0 : 1 - inter / uni
76
+ }
77
+
78
+ //cluster purity: 各分群以多數ground-truth標籤計, 全對則為1
79
+ function purity(ginds, truth) {
80
+ let correct = 0
81
+ let n = truth.length
82
+ for (let g of ginds) {
83
+ let cnt = {}
84
+ for (let i of g) {
85
+ cnt[truth[i]] = (cnt[truth[i]] || 0) + 1
86
+ }
87
+ correct += Math.max(...Object.values(cnt))
88
+ }
89
+ return correct / n
90
+ }
91
+
92
+ //flat是否恰好涵蓋0..n-1各一次(不漏不重)
93
+ function coversAll(ginds, n) {
94
+ let flat = ginds.flat().sort((a, b) => a - b)
95
+ if (flat.length !== n) {
96
+ return false
97
+ }
98
+ for (let i = 0; i < n; i++) {
99
+ if (flat[i] !== i) {
100
+ return false
101
+ }
102
+ }
103
+ return true
104
+ }
105
+
106
+
107
+ describe(`k-medoids large`, function() {
108
+
109
+ this.timeout(60000)
110
+
111
+ it(`should perfectly recover 6 well-separated numeric blobs (n=120, usePCA:false)`, async function() {
112
+ let G = 6
113
+ let m = 20
114
+ let n = G * m
115
+ let { data, truth } = genBlobs(G, m, 4, 100, 5, 42)
116
+ let r = await WCluster.cluster(data, { mode: 'k-medoids', kNumber: G, usePCA: false, useMethod: 'simple' })
117
+ assert.strict.strictEqual(coversAll(r.ginds, n), true)
118
+ assert.strict.strictEqual(r.ginds.length, G)
119
+ assert.strict.strictEqual(purity(r.ginds, truth), 1)
120
+ })
121
+
122
+ it(`should perfectly recover 6 binary blobs with Jaccard distance (n=120, D=120, usePCA:false)`, async function() {
123
+ let G = 6
124
+ let m = 20
125
+ let n = G * m
126
+ let { data, truth } = genBinaryBlobs(G, m, 120, 18, 3, 42)
127
+ let r = await WCluster.cluster(data, { mode: 'k-medoids', kNumber: G, usePCA: false, funDist: jaccard, useMethod: 'simple' })
128
+ assert.strict.strictEqual(coversAll(r.ginds, n), true)
129
+ assert.strict.strictEqual(r.ginds.length, G)
130
+ assert.strict.strictEqual(purity(r.ginds, truth), 1)
131
+ })
132
+
133
+ it(`should be deterministic on larger data (same ginds on repeated runs)`, async function() {
134
+ let G = 6
135
+ let m = 20
136
+ let { data } = genBlobs(G, m, 4, 100, 5, 42)
137
+ let r1 = await WCluster.cluster(data, { mode: 'k-medoids', kNumber: G, usePCA: false, useMethod: 'simple' })
138
+ let r2 = await WCluster.cluster(data, { mode: 'k-medoids', kNumber: G, usePCA: false, useMethod: 'simple' })
139
+ assert.strict.deepStrictEqual(r1.ginds, r2.ginds)
140
+ })
141
+
142
+ })
@@ -2,7 +2,7 @@ import assert from 'assert'
2
2
  import WCluster from '../src/WCluster.mjs'
3
3
 
4
4
 
5
- describe(`cluster`, function() {
5
+ describe(`k-medoids simple`, function() {
6
6
 
7
7
  let mode = 'k-medoids'
8
8
 
@@ -41,7 +41,7 @@ describe(`cluster`, function() {
41
41
  }
42
42
 
43
43
  it(`should return '${JSON.stringify(clmat)}' when input ${JSON.stringify(mat)}, { mode, kNumber: 2, nCompNIPALS: 2 }`, async function() {
44
- let r = await WCluster.cluster(mat, { mode, kNumber: 2, nCompNIPALS: 2 })
44
+ let r = await WCluster.cluster(mat, { mode, kNumber: 2, nCompNIPALS: 2, useMethod: 'simple' })
45
45
  assert.strict.deepStrictEqual(r, clmat)
46
46
  })
47
47
 
@@ -80,7 +80,7 @@ describe(`cluster`, function() {
80
80
  }
81
81
 
82
82
  it(`should return '${JSON.stringify(clmat2)}' when input ${JSON.stringify(mat2)}, { mode, kNumber: 2, nCompNIPALS: 2 }`, async function() {
83
- let r = await WCluster.cluster(mat2, { mode, kNumber: 2, nCompNIPALS: 2 })
83
+ let r = await WCluster.cluster(mat2, { mode, kNumber: 2, nCompNIPALS: 2, useMethod: 'simple' })
84
84
  assert.strict.deepStrictEqual(r, clmat2)
85
85
  })
86
86
 
@@ -119,7 +119,7 @@ describe(`cluster`, function() {
119
119
  }
120
120
 
121
121
  it(`should return '${JSON.stringify(clmat3)}' when input ${JSON.stringify(mat3)}, { mode, kNumber: 2, nCompNIPALS: 2 }`, async function() {
122
- let r = await WCluster.cluster(mat3, { mode, kNumber: 2, nCompNIPALS: 2 })
122
+ let r = await WCluster.cluster(mat3, { mode, kNumber: 2, nCompNIPALS: 2, useMethod: 'simple' })
123
123
  assert.strict.deepStrictEqual(r, clmat3)
124
124
  })
125
125
 
@@ -158,7 +158,7 @@ describe(`cluster`, function() {
158
158
  }
159
159
 
160
160
  it(`should return '${JSON.stringify(clltdt)}' when input ${JSON.stringify(ltdt)}, { mode, kNumber: 2, nCompNIPALS: 2 }`, async function() {
161
- let r = await WCluster.cluster(ltdt, { mode, kNumber: 2, nCompNIPALS: 2 })
161
+ let r = await WCluster.cluster(ltdt, { mode, kNumber: 2, nCompNIPALS: 2, useMethod: 'simple' })
162
162
  assert.strict.deepStrictEqual(r, clltdt)
163
163
  })
164
164
 
@@ -14,7 +14,7 @@ function main() {
14
14
  //pks
15
15
  let pks = getPks()
16
16
 
17
- //url
17
+ //url, urlww
18
18
  let url = `https://cdn.jsdelivr.net/npm/w-cluster@${pks.version}/dist/w-cluster.umd.js`
19
19
  let urlww = `https://cdn.jsdelivr.net/npm/w-cluster@${pks.version}/dist/w-cluster.wk.umd.js`
20
20