Skip to content

Commit a369a2e

Browse files
authored
Accept metric as function (#816)
* Accept metric as function * Fix test * Fix test
1 parent 50f3641 commit a369a2e

24 files changed

+487
-265
lines changed

lib/model/agglomerative.js

+15-11
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,26 @@
1313
*/
1414
class AgglomerativeClustering {
1515
/**
16-
* @param {'euclid' | 'manhattan' | 'chebyshev'} metric Metric name
16+
* @param {'euclid' | 'manhattan' | 'chebyshev' | function (number[], number[]): number} metric Metric name
1717
*/
1818
constructor(metric = 'euclid') {
1919
this._root = null
2020
this._metric = metric
2121

22-
switch (this._metric) {
23-
case 'euclid':
24-
this._d = (a, b) => Math.sqrt(a.reduce((s, v, i) => s + (v - b[i]) ** 2, 0))
25-
break
26-
case 'manhattan':
27-
this._d = (a, b) => a.reduce((s, v, i) => s + Math.abs(v - b[i]), 0)
28-
break
29-
case 'chebyshev':
30-
this._d = (a, b) => a.reduce((s, v, i) => Math.max(s, Math.abs(v - b[i])), -Infinity)
31-
break
22+
if (typeof this._metric === 'function') {
23+
this._d = this._metric
24+
} else {
25+
switch (this._metric) {
26+
case 'euclid':
27+
this._d = (a, b) => Math.sqrt(a.reduce((s, v, i) => s + (v - b[i]) ** 2, 0))
28+
break
29+
case 'manhattan':
30+
this._d = (a, b) => a.reduce((s, v, i) => s + Math.abs(v - b[i]), 0)
31+
break
32+
case 'chebyshev':
33+
this._d = (a, b) => a.reduce((s, v, i) => Math.max(s, Math.abs(v - b[i])), -Infinity)
34+
break
35+
}
3236
}
3337
}
3438

lib/model/dbscan.js

+15-11
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,27 @@ export default class DBSCAN {
66
/**
77
* @param {number} [eps=0.5] Radius to determine neighborhood
88
* @param {number} [minPts=5] Minimum size of cluster
9-
* @param {'euclid' | 'manhattan' | 'chebyshev'} [metric=euclid] Metric name
9+
* @param {'euclid' | 'manhattan' | 'chebyshev' | function (number[], number[]): number} [metric=euclid] Metric name
1010
*/
1111
constructor(eps = 0.5, minPts = 5, metric = 'euclid') {
1212
this._eps = eps
1313
this._minPts = minPts
1414

1515
this._metric = metric
16-
switch (this._metric) {
17-
case 'euclid':
18-
this._d = (a, b) => Math.sqrt(a.reduce((s, v, i) => s + (v - b[i]) ** 2, 0))
19-
break
20-
case 'manhattan':
21-
this._d = (a, b) => a.reduce((s, v, i) => s + Math.abs(v - b[i]), 0)
22-
break
23-
case 'chebyshev':
24-
this._d = (a, b) => Math.max(...a.map((v, i) => Math.abs(v - b[i])))
25-
break
16+
if (typeof this._metric === 'function') {
17+
this._d = this._metric
18+
} else {
19+
switch (this._metric) {
20+
case 'euclid':
21+
this._d = (a, b) => Math.sqrt(a.reduce((s, v, i) => s + (v - b[i]) ** 2, 0))
22+
break
23+
case 'manhattan':
24+
this._d = (a, b) => a.reduce((s, v, i) => s + Math.abs(v - b[i]), 0)
25+
break
26+
case 'chebyshev':
27+
this._d = (a, b) => Math.max(...a.map((v, i) => Math.abs(v - b[i])))
28+
break
29+
}
2630
}
2731
}
2832

lib/model/enan.js

+23-19
Original file line numberDiff line numberDiff line change
@@ -5,31 +5,35 @@ export default class ENaN {
55
// Extend natural neighbor: a novel classification method with self-adaptive neighborhood parameters in different stages
66
// https://arxiv.org/ftp/arxiv/papers/1612/1612.02310.pdf
77
/**
8-
* @param {'euclid' | 'manhattan' | 'chebyshev' | 'minkowski'} [metric=euclid] Metric name
8+
* @param {'euclid' | 'manhattan' | 'chebyshev' | 'minkowski' | function (number[], number[]): number} [metric=euclid] Metric name
99
*/
1010
constructor(metric = 'euclid') {
1111
this._p = []
1212
this._c = []
1313

1414
this._metric = metric
15-
switch (this._metric) {
16-
case 'euclid':
17-
this._d = (a, b) => Math.sqrt(a.reduce((s, v, i) => s + (v - b[i]) ** 2, 0))
18-
break
19-
case 'manhattan':
20-
this._d = (a, b) => a.reduce((s, v, i) => s + Math.abs(v - b[i]), 0)
21-
break
22-
case 'chebyshev':
23-
this._d = (a, b) => Math.max(...a.map((v, i) => Math.abs(v - b[i])))
24-
break
25-
case 'minkowski':
26-
this._dp = 2
27-
this._d = (a, b) =>
28-
Math.pow(
29-
a.reduce((s, v, i) => s + (v - b[i]) ** this._dp, 0),
30-
1 / this._dp
31-
)
32-
break
15+
if (typeof this._metric === 'function') {
16+
this._d = this._metric
17+
} else {
18+
switch (this._metric) {
19+
case 'euclid':
20+
this._d = (a, b) => Math.sqrt(a.reduce((s, v, i) => s + (v - b[i]) ** 2, 0))
21+
break
22+
case 'manhattan':
23+
this._d = (a, b) => a.reduce((s, v, i) => s + Math.abs(v - b[i]), 0)
24+
break
25+
case 'chebyshev':
26+
this._d = (a, b) => Math.max(...a.map((v, i) => Math.abs(v - b[i])))
27+
break
28+
case 'minkowski':
29+
this._dp = 2
30+
this._d = (a, b) =>
31+
Math.pow(
32+
a.reduce((s, v, i) => s + (v - b[i]) ** this._dp, 0),
33+
1 / this._dp
34+
)
35+
break
36+
}
3337
}
3438
}
3539

lib/model/enn.js

+23-19
Original file line numberDiff line numberDiff line change
@@ -8,31 +8,35 @@ export default class ENN {
88
/**
99
* @param {0 | 1 | 2} [version=1] Version
1010
* @param {number} [k=5] Number of neighborhoods
11-
* @param {'euclid' | 'manhattan' | 'chebyshev' | 'minkowski'} [metric=euclid] Metric name
11+
* @param {'euclid' | 'manhattan' | 'chebyshev' | 'minkowski' | function (number[], number[]): number} [metric=euclid] Metric name
1212
*/
1313
constructor(version = 1, k = 5, metric = 'euclid') {
1414
this._k = k
1515
this._v = version
1616

1717
this._metric = metric
18-
switch (this._metric) {
19-
case 'euclid':
20-
this._d = (a, b) => Math.sqrt(a.reduce((s, v, i) => s + (v - b[i]) ** 2, 0))
21-
break
22-
case 'manhattan':
23-
this._d = (a, b) => a.reduce((s, v, i) => s + Math.abs(v - b[i]), 0)
24-
break
25-
case 'chebyshev':
26-
this._d = (a, b) => Math.max(...a.map((v, i) => Math.abs(v - b[i])))
27-
break
28-
case 'minkowski':
29-
this._dp = 2
30-
this._d = (a, b) =>
31-
Math.pow(
32-
a.reduce((s, v, i) => s + (v - b[i]) ** this._dp, 0),
33-
1 / this._dp
34-
)
35-
break
18+
if (typeof this._metric === 'function') {
19+
this._d = this._metric
20+
} else {
21+
switch (this._metric) {
22+
case 'euclid':
23+
this._d = (a, b) => Math.sqrt(a.reduce((s, v, i) => s + (v - b[i]) ** 2, 0))
24+
break
25+
case 'manhattan':
26+
this._d = (a, b) => a.reduce((s, v, i) => s + Math.abs(v - b[i]), 0)
27+
break
28+
case 'chebyshev':
29+
this._d = (a, b) => Math.max(...a.map((v, i) => Math.abs(v - b[i])))
30+
break
31+
case 'minkowski':
32+
this._dp = 2
33+
this._d = (a, b) =>
34+
Math.pow(
35+
a.reduce((s, v, i) => s + (v - b[i]) ** this._dp, 0),
36+
1 / this._dp
37+
)
38+
break
39+
}
3640
}
3741
}
3842

lib/model/hdbscan.js

+15-11
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,27 @@ export default class HDBSCAN {
88
/**
99
* @param {number} [minClusterSize=5] Minimum number of clusters to be recognized as a cluster
1010
* @param {number} [minPts=5] Number of neighborhood with core distance
11-
* @param {'euclid' | 'manhattan' | 'chebyshev'} [metric=euclid] Metric name
11+
* @param {'euclid' | 'manhattan' | 'chebyshev' | function (number[], number[]): number} [metric=euclid] Metric name
1212
*/
1313
constructor(minClusterSize = 5, minPts = 5, metric = 'euclid') {
1414
this._minClusterSize = minClusterSize
1515
this._minPts = minPts
1616

1717
this._metric = metric
18-
switch (this._metric) {
19-
case 'euclid':
20-
this._d = (a, b) => Math.sqrt(a.reduce((s, v, i) => s + (v - b[i]) ** 2, 0))
21-
break
22-
case 'manhattan':
23-
this._d = (a, b) => a.reduce((s, v, i) => s + Math.abs(v - b[i]), 0)
24-
break
25-
case 'chebyshev':
26-
this._d = (a, b) => Math.max(...a.map((v, i) => Math.abs(v - b[i])))
27-
break
18+
if (typeof this._metric === 'function') {
19+
this._d = this._metric
20+
} else {
21+
switch (this._metric) {
22+
case 'euclid':
23+
this._d = (a, b) => Math.sqrt(a.reduce((s, v, i) => s + (v - b[i]) ** 2, 0))
24+
break
25+
case 'manhattan':
26+
this._d = (a, b) => a.reduce((s, v, i) => s + Math.abs(v - b[i]), 0)
27+
break
28+
case 'chebyshev':
29+
this._d = (a, b) => Math.max(...a.map((v, i) => Math.abs(v - b[i])))
30+
break
31+
}
2832
}
2933
}
3034

lib/model/inverse_distance_weighting.js

+23-19
Original file line numberDiff line numberDiff line change
@@ -7,31 +7,35 @@ export default class InverseDistanceWeighting {
77
/**
88
* @param {number} [k=5] Number of neighborhoods
99
* @param {number} [p=2] Power parameter
10-
* @param {'euclid' | 'manhattan' | 'chebyshev' | 'minkowski'} [metric=euclid] Metric name
10+
* @param {'euclid' | 'manhattan' | 'chebyshev' | 'minkowski' | function (number[], number[]): number} [metric=euclid] Metric name
1111
*/
1212
constructor(k = 5, p = 2, metric = 'euclid') {
1313
this._k = k
1414
this._p = p
1515

1616
this._metric = metric
17-
switch (this._metric) {
18-
case 'euclid':
19-
this._d = (a, b) => Math.sqrt(a.reduce((s, v, i) => s + (v - b[i]) ** 2, 0))
20-
break
21-
case 'manhattan':
22-
this._d = (a, b) => a.reduce((s, v, i) => s + Math.abs(v - b[i]), 0)
23-
break
24-
case 'chebyshev':
25-
this._d = (a, b) => Math.max(...a.map((v, i) => Math.abs(v - b[i])))
26-
break
27-
case 'minkowski':
28-
this._dp = 2
29-
this._d = (a, b) =>
30-
Math.pow(
31-
a.reduce((s, v, i) => s + (v - b[i]) ** this._dp, 0),
32-
1 / this._dp
33-
)
34-
break
17+
if (typeof this._metric === 'function') {
18+
this._d = this._metric
19+
} else {
20+
switch (this._metric) {
21+
case 'euclid':
22+
this._d = (a, b) => Math.sqrt(a.reduce((s, v, i) => s + (v - b[i]) ** 2, 0))
23+
break
24+
case 'manhattan':
25+
this._d = (a, b) => a.reduce((s, v, i) => s + Math.abs(v - b[i]), 0)
26+
break
27+
case 'chebyshev':
28+
this._d = (a, b) => Math.max(...a.map((v, i) => Math.abs(v - b[i])))
29+
break
30+
case 'minkowski':
31+
this._dp = 2
32+
this._d = (a, b) =>
33+
Math.pow(
34+
a.reduce((s, v, i) => s + (v - b[i]) ** this._dp, 0),
35+
1 / this._dp
36+
)
37+
break
38+
}
3539
}
3640
}
3741

lib/model/knearestneighbor.js

+28-24
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,36 @@
44
class KNNBase {
55
/**
66
* @param {number} [k=5] Number of neighborhoods
7-
* @param {'euclid' | 'manhattan' | 'chebyshev' | 'minkowski'} [metric=euclid] Metric name
7+
* @param {'euclid' | 'manhattan' | 'chebyshev' | 'minkowski' | function (number[], number[]): number} [metric=euclid] Metric name
88
*/
99
constructor(k = 5, metric = 'euclid') {
1010
this._p = []
1111
this._c = []
1212
this._k = k
1313

1414
this._metric = metric
15-
switch (this._metric) {
16-
case 'euclid':
17-
this._d = (a, b) => Math.sqrt(a.reduce((s, v, i) => s + (v - b[i]) ** 2, 0))
18-
break
19-
case 'manhattan':
20-
this._d = (a, b) => a.reduce((s, v, i) => s + Math.abs(v - b[i]), 0)
21-
break
22-
case 'chebyshev':
23-
this._d = (a, b) => Math.max(...a.map((v, i) => Math.abs(v - b[i])))
24-
break
25-
case 'minkowski':
26-
this._dp = 2
27-
this._d = (a, b) =>
28-
Math.pow(
29-
a.reduce((s, v, i) => s + (v - b[i]) ** this._dp, 0),
30-
1 / this._dp
31-
)
32-
break
15+
if (typeof this._metric === 'function') {
16+
this._d = this._metric
17+
} else {
18+
switch (this._metric) {
19+
case 'euclid':
20+
this._d = (a, b) => Math.sqrt(a.reduce((s, v, i) => s + (v - b[i]) ** 2, 0))
21+
break
22+
case 'manhattan':
23+
this._d = (a, b) => a.reduce((s, v, i) => s + Math.abs(v - b[i]), 0)
24+
break
25+
case 'chebyshev':
26+
this._d = (a, b) => Math.max(...a.map((v, i) => Math.abs(v - b[i])))
27+
break
28+
case 'minkowski':
29+
this._dp = 2
30+
this._d = (a, b) =>
31+
Math.pow(
32+
a.reduce((s, v, i) => s + (v - b[i]) ** this._dp, 0),
33+
1 / this._dp
34+
)
35+
break
36+
}
3337
}
3438
}
3539

@@ -72,7 +76,7 @@ class KNNBase {
7276
export class KNN extends KNNBase {
7377
/**
7478
* @param {number} [k=5] Number of neighborhoods
75-
* @param {'euclid' | 'manhattan' | 'chebyshev' | 'minkowski'} [metric=euclid] Metric name
79+
* @param {'euclid' | 'manhattan' | 'chebyshev' | 'minkowski' | function (number[], number[]): number} [metric=euclid] Metric name
7680
*/
7781
constructor(k = 5, metric = 'euclid') {
7882
super(k, metric)
@@ -144,7 +148,7 @@ export class KNN extends KNNBase {
144148
export class KNNRegression extends KNNBase {
145149
/**
146150
* @param {number} [k=5] Number of neighborhoods
147-
* @param {'euclid' | 'manhattan' | 'chebyshev' | 'minkowski'} [metric=euclid] Metric name
151+
* @param {'euclid' | 'manhattan' | 'chebyshev' | 'minkowski' | function (number[], number[]): number} [metric=euclid] Metric name
148152
*/
149153
constructor(k = 5, metric = 'euclid') {
150154
super(k, metric)
@@ -192,7 +196,7 @@ export class KNNRegression extends KNNBase {
192196
export class KNNAnomaly extends KNNBase {
193197
/**
194198
* @param {number} [k=5] Number of neighborhoods
195-
* @param {'euclid' | 'manhattan' | 'chebyshev' | 'minkowski'} [metric=euclid] Metric name
199+
* @param {'euclid' | 'manhattan' | 'chebyshev' | 'minkowski' | function (number[], number[]): number} [metric=euclid] Metric name
196200
*/
197201
constructor(k = 5, metric = 'euclid') {
198202
super(k, metric)
@@ -239,7 +243,7 @@ export class KNNDensityEstimation extends KNNBase {
239243
// https://home.hiroshima-u.ac.jp/tkurita/lecture/prnn/node12.html
240244
/**
241245
* @param {number} [k=5] Number of neighborhoods
242-
* @param {'euclid' | 'manhattan' | 'chebyshev' | 'minkowski'} [metric=euclid] Metric name
246+
* @param {'euclid' | 'manhattan' | 'chebyshev' | 'minkowski' | function (number[], number[]): number} [metric=euclid] Metric name
243247
*/
244248
constructor(k = 5, metric = 'euclid') {
245249
super(k, metric)
@@ -306,7 +310,7 @@ export class SemiSupervisedKNN extends KNNBase {
306310
// https://products.sint.co.jp/aisia/blog/vol1-20
307311
/**
308312
* @param {number} [k=5] Number of neighborhoods
309-
* @param {'euclid' | 'manhattan' | 'chebyshev' | 'minkowski'} [metric=euclid] Metric name
313+
* @param {'euclid' | 'manhattan' | 'chebyshev' | 'minkowski' | function (number[], number[]): number} [metric=euclid] Metric name
310314
*/
311315
constructor(k = 5, metric = 'euclid') {
312316
super(k, metric)

0 commit comments

Comments
 (0)