deepbox 0.1.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.
Files changed (173) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +344 -0
  3. package/dist/CSRMatrix-CwGwQRea.d.cts +219 -0
  4. package/dist/CSRMatrix-KzNt6QpS.d.ts +219 -0
  5. package/dist/Tensor-BQLk1ltW.d.cts +147 -0
  6. package/dist/Tensor-g8mUClel.d.ts +147 -0
  7. package/dist/chunk-4S73VUBD.js +677 -0
  8. package/dist/chunk-4S73VUBD.js.map +1 -0
  9. package/dist/chunk-5R4S63PF.js +2925 -0
  10. package/dist/chunk-5R4S63PF.js.map +1 -0
  11. package/dist/chunk-6AE5FKKQ.cjs +9264 -0
  12. package/dist/chunk-6AE5FKKQ.cjs.map +1 -0
  13. package/dist/chunk-AD436M45.js +3854 -0
  14. package/dist/chunk-AD436M45.js.map +1 -0
  15. package/dist/chunk-ALS7ETWZ.cjs +4263 -0
  16. package/dist/chunk-ALS7ETWZ.cjs.map +1 -0
  17. package/dist/chunk-AU7XHGKJ.js +2092 -0
  18. package/dist/chunk-AU7XHGKJ.js.map +1 -0
  19. package/dist/chunk-B5TNKUEY.js +1481 -0
  20. package/dist/chunk-B5TNKUEY.js.map +1 -0
  21. package/dist/chunk-BCR7G3A6.js +9136 -0
  22. package/dist/chunk-BCR7G3A6.js.map +1 -0
  23. package/dist/chunk-C4PKXY74.cjs +1917 -0
  24. package/dist/chunk-C4PKXY74.cjs.map +1 -0
  25. package/dist/chunk-DWZY6PIP.cjs +6400 -0
  26. package/dist/chunk-DWZY6PIP.cjs.map +1 -0
  27. package/dist/chunk-E3EU5FZO.cjs +2113 -0
  28. package/dist/chunk-E3EU5FZO.cjs.map +1 -0
  29. package/dist/chunk-F3JWBINJ.js +1054 -0
  30. package/dist/chunk-F3JWBINJ.js.map +1 -0
  31. package/dist/chunk-FJYLIGJX.js +1940 -0
  32. package/dist/chunk-FJYLIGJX.js.map +1 -0
  33. package/dist/chunk-JSCDE774.cjs +729 -0
  34. package/dist/chunk-JSCDE774.cjs.map +1 -0
  35. package/dist/chunk-LWECRCW2.cjs +2412 -0
  36. package/dist/chunk-LWECRCW2.cjs.map +1 -0
  37. package/dist/chunk-MLBMYKCG.js +6379 -0
  38. package/dist/chunk-MLBMYKCG.js.map +1 -0
  39. package/dist/chunk-OX6QXFMV.cjs +3874 -0
  40. package/dist/chunk-OX6QXFMV.cjs.map +1 -0
  41. package/dist/chunk-PHV2DKRS.cjs +1072 -0
  42. package/dist/chunk-PHV2DKRS.cjs.map +1 -0
  43. package/dist/chunk-PL7TAYKI.js +4056 -0
  44. package/dist/chunk-PL7TAYKI.js.map +1 -0
  45. package/dist/chunk-PR647I7R.js +1898 -0
  46. package/dist/chunk-PR647I7R.js.map +1 -0
  47. package/dist/chunk-QERHVCHC.cjs +2960 -0
  48. package/dist/chunk-QERHVCHC.cjs.map +1 -0
  49. package/dist/chunk-XEG44RF6.cjs +1514 -0
  50. package/dist/chunk-XEG44RF6.cjs.map +1 -0
  51. package/dist/chunk-XMWVME2W.js +2377 -0
  52. package/dist/chunk-XMWVME2W.js.map +1 -0
  53. package/dist/chunk-ZB75FESB.cjs +1979 -0
  54. package/dist/chunk-ZB75FESB.cjs.map +1 -0
  55. package/dist/chunk-ZLW62TJG.cjs +4061 -0
  56. package/dist/chunk-ZLW62TJG.cjs.map +1 -0
  57. package/dist/chunk-ZXKBDFP3.js +4235 -0
  58. package/dist/chunk-ZXKBDFP3.js.map +1 -0
  59. package/dist/core/index.cjs +204 -0
  60. package/dist/core/index.cjs.map +1 -0
  61. package/dist/core/index.d.cts +2 -0
  62. package/dist/core/index.d.ts +2 -0
  63. package/dist/core/index.js +3 -0
  64. package/dist/core/index.js.map +1 -0
  65. package/dist/dataframe/index.cjs +22 -0
  66. package/dist/dataframe/index.cjs.map +1 -0
  67. package/dist/dataframe/index.d.cts +3 -0
  68. package/dist/dataframe/index.d.ts +3 -0
  69. package/dist/dataframe/index.js +5 -0
  70. package/dist/dataframe/index.js.map +1 -0
  71. package/dist/datasets/index.cjs +134 -0
  72. package/dist/datasets/index.cjs.map +1 -0
  73. package/dist/datasets/index.d.cts +3 -0
  74. package/dist/datasets/index.d.ts +3 -0
  75. package/dist/datasets/index.js +5 -0
  76. package/dist/datasets/index.js.map +1 -0
  77. package/dist/index-74AB8Cyh.d.cts +1126 -0
  78. package/dist/index-9oQx1HgV.d.cts +1180 -0
  79. package/dist/index-BJY2SI4i.d.ts +483 -0
  80. package/dist/index-BWGhrDlr.d.ts +733 -0
  81. package/dist/index-B_DK4FKY.d.cts +242 -0
  82. package/dist/index-BbA2Gxfl.d.ts +456 -0
  83. package/dist/index-BgHYAoSS.d.cts +837 -0
  84. package/dist/index-BndMbqsM.d.ts +1439 -0
  85. package/dist/index-C1mfVYoo.d.ts +2517 -0
  86. package/dist/index-CCvlwAmL.d.cts +809 -0
  87. package/dist/index-CDw5CnOU.d.ts +785 -0
  88. package/dist/index-Cn3SdB0O.d.ts +1126 -0
  89. package/dist/index-CrqLlS-a.d.ts +776 -0
  90. package/dist/index-D61yaSMY.d.cts +483 -0
  91. package/dist/index-D9Loo1_A.d.cts +2517 -0
  92. package/dist/index-DIT_OO9C.d.cts +785 -0
  93. package/dist/index-DIp_RrRt.d.ts +242 -0
  94. package/dist/index-DbultU6X.d.cts +1427 -0
  95. package/dist/index-DmEg_LCm.d.cts +776 -0
  96. package/dist/index-DoPWVxPo.d.cts +1439 -0
  97. package/dist/index-DuCxd-8d.d.ts +837 -0
  98. package/dist/index-Dx42TZaY.d.ts +809 -0
  99. package/dist/index-DyZ4QQf5.d.cts +456 -0
  100. package/dist/index-GFAVyOWO.d.ts +1427 -0
  101. package/dist/index-WHQLn0e8.d.cts +733 -0
  102. package/dist/index-ZtI1Iy4L.d.ts +1180 -0
  103. package/dist/index-eJgeni9c.d.cts +1911 -0
  104. package/dist/index-tk4lSYod.d.ts +1911 -0
  105. package/dist/index.cjs +72 -0
  106. package/dist/index.cjs.map +1 -0
  107. package/dist/index.d.cts +17 -0
  108. package/dist/index.d.ts +17 -0
  109. package/dist/index.js +15 -0
  110. package/dist/index.js.map +1 -0
  111. package/dist/linalg/index.cjs +86 -0
  112. package/dist/linalg/index.cjs.map +1 -0
  113. package/dist/linalg/index.d.cts +3 -0
  114. package/dist/linalg/index.d.ts +3 -0
  115. package/dist/linalg/index.js +5 -0
  116. package/dist/linalg/index.js.map +1 -0
  117. package/dist/metrics/index.cjs +158 -0
  118. package/dist/metrics/index.cjs.map +1 -0
  119. package/dist/metrics/index.d.cts +3 -0
  120. package/dist/metrics/index.d.ts +3 -0
  121. package/dist/metrics/index.js +5 -0
  122. package/dist/metrics/index.js.map +1 -0
  123. package/dist/ml/index.cjs +87 -0
  124. package/dist/ml/index.cjs.map +1 -0
  125. package/dist/ml/index.d.cts +3 -0
  126. package/dist/ml/index.d.ts +3 -0
  127. package/dist/ml/index.js +6 -0
  128. package/dist/ml/index.js.map +1 -0
  129. package/dist/ndarray/index.cjs +501 -0
  130. package/dist/ndarray/index.cjs.map +1 -0
  131. package/dist/ndarray/index.d.cts +5 -0
  132. package/dist/ndarray/index.d.ts +5 -0
  133. package/dist/ndarray/index.js +4 -0
  134. package/dist/ndarray/index.js.map +1 -0
  135. package/dist/nn/index.cjs +142 -0
  136. package/dist/nn/index.cjs.map +1 -0
  137. package/dist/nn/index.d.cts +6 -0
  138. package/dist/nn/index.d.ts +6 -0
  139. package/dist/nn/index.js +5 -0
  140. package/dist/nn/index.js.map +1 -0
  141. package/dist/optim/index.cjs +77 -0
  142. package/dist/optim/index.cjs.map +1 -0
  143. package/dist/optim/index.d.cts +4 -0
  144. package/dist/optim/index.d.ts +4 -0
  145. package/dist/optim/index.js +4 -0
  146. package/dist/optim/index.js.map +1 -0
  147. package/dist/plot/index.cjs +114 -0
  148. package/dist/plot/index.cjs.map +1 -0
  149. package/dist/plot/index.d.cts +6 -0
  150. package/dist/plot/index.d.ts +6 -0
  151. package/dist/plot/index.js +5 -0
  152. package/dist/plot/index.js.map +1 -0
  153. package/dist/preprocess/index.cjs +82 -0
  154. package/dist/preprocess/index.cjs.map +1 -0
  155. package/dist/preprocess/index.d.cts +4 -0
  156. package/dist/preprocess/index.d.ts +4 -0
  157. package/dist/preprocess/index.js +5 -0
  158. package/dist/preprocess/index.js.map +1 -0
  159. package/dist/random/index.cjs +74 -0
  160. package/dist/random/index.cjs.map +1 -0
  161. package/dist/random/index.d.cts +3 -0
  162. package/dist/random/index.d.ts +3 -0
  163. package/dist/random/index.js +5 -0
  164. package/dist/random/index.js.map +1 -0
  165. package/dist/stats/index.cjs +142 -0
  166. package/dist/stats/index.cjs.map +1 -0
  167. package/dist/stats/index.d.cts +3 -0
  168. package/dist/stats/index.d.ts +3 -0
  169. package/dist/stats/index.js +5 -0
  170. package/dist/stats/index.js.map +1 -0
  171. package/dist/tensor-B96jjJLQ.d.cts +205 -0
  172. package/dist/tensor-B96jjJLQ.d.ts +205 -0
  173. package/package.json +226 -0
@@ -0,0 +1,1940 @@
1
+ import { tensor } from './chunk-BCR7G3A6.js';
2
+ import { __export, InvalidParameterError, DataValidationError, ShapeError, getNumericElement, DTypeError, getStringElement, getBigIntElement, isTypedArray, isNumericTypedArray } from './chunk-4S73VUBD.js';
3
+
4
+ // src/metrics/index.ts
5
+ var metrics_exports = {};
6
+ __export(metrics_exports, {
7
+ accuracy: () => accuracy,
8
+ adjustedMutualInfoScore: () => adjustedMutualInfoScore,
9
+ adjustedR2Score: () => adjustedR2Score,
10
+ adjustedRandScore: () => adjustedRandScore,
11
+ averagePrecisionScore: () => averagePrecisionScore,
12
+ balancedAccuracyScore: () => balancedAccuracyScore,
13
+ calinskiHarabaszScore: () => calinskiHarabaszScore,
14
+ classificationReport: () => classificationReport,
15
+ cohenKappaScore: () => cohenKappaScore,
16
+ completenessScore: () => completenessScore,
17
+ confusionMatrix: () => confusionMatrix,
18
+ daviesBouldinScore: () => daviesBouldinScore,
19
+ explainedVarianceScore: () => explainedVarianceScore,
20
+ f1Score: () => f1Score,
21
+ fbetaScore: () => fbetaScore,
22
+ fowlkesMallowsScore: () => fowlkesMallowsScore,
23
+ hammingLoss: () => hammingLoss,
24
+ homogeneityScore: () => homogeneityScore,
25
+ jaccardScore: () => jaccardScore,
26
+ logLoss: () => logLoss,
27
+ mae: () => mae,
28
+ mape: () => mape,
29
+ matthewsCorrcoef: () => matthewsCorrcoef,
30
+ maxError: () => maxError,
31
+ medianAbsoluteError: () => medianAbsoluteError,
32
+ mse: () => mse,
33
+ normalizedMutualInfoScore: () => normalizedMutualInfoScore,
34
+ precision: () => precision,
35
+ precisionRecallCurve: () => precisionRecallCurve,
36
+ r2Score: () => r2Score,
37
+ recall: () => recall,
38
+ rmse: () => rmse,
39
+ rocAucScore: () => rocAucScore,
40
+ rocCurve: () => rocCurve,
41
+ silhouetteSamples: () => silhouetteSamples,
42
+ silhouetteScore: () => silhouetteScore,
43
+ vMeasureScore: () => vMeasureScore
44
+ });
45
+
46
+ // src/metrics/_internal.ts
47
+ function computeLogicalStrides(shape) {
48
+ const strides = new Array(shape.length);
49
+ let stride = 1;
50
+ for (let i = shape.length - 1; i >= 0; i--) {
51
+ const dim = shape[i];
52
+ if (dim === void 0) {
53
+ throw new ShapeError("Tensor shape must be fully defined");
54
+ }
55
+ strides[i] = stride;
56
+ stride *= dim;
57
+ }
58
+ return strides;
59
+ }
60
+ function createFlatOffsetter(t) {
61
+ const base = t.offset;
62
+ if (t.ndim <= 1) {
63
+ const stride0 = t.strides[0] ?? 1;
64
+ return (flatIndex) => base + flatIndex * stride0;
65
+ }
66
+ const logicalStrides = computeLogicalStrides(t.shape);
67
+ const strides = t.strides;
68
+ return (flatIndex) => {
69
+ let rem = flatIndex;
70
+ let offset = base;
71
+ for (let axis = 0; axis < logicalStrides.length; axis++) {
72
+ const axisLogicalStride = logicalStrides[axis] ?? 1;
73
+ const coord = Math.floor(rem / axisLogicalStride);
74
+ rem -= coord * axisLogicalStride;
75
+ offset += coord * (strides[axis] ?? 0);
76
+ }
77
+ return offset;
78
+ };
79
+ }
80
+ function assertFiniteNumber(value, name, detail) {
81
+ if (!Number.isFinite(value)) {
82
+ throw new DataValidationError(
83
+ `${name} must contain only finite numbers; found ${String(value)} at ${detail}`
84
+ );
85
+ }
86
+ }
87
+ function assertVectorLike(t, name) {
88
+ if (t.ndim <= 1) return;
89
+ if (t.ndim === 2 && (t.shape[1] ?? 0) === 1) return;
90
+ throw new ShapeError(`${name} must be 1D or a column vector`);
91
+ }
92
+ function assertSameSizeVectors(a, b, nameA, nameB) {
93
+ if (a.size !== b.size) {
94
+ throw new ShapeError(
95
+ `${nameA} (size ${a.size}) and ${nameB} (size ${b.size}) must have same size`
96
+ );
97
+ }
98
+ assertVectorLike(a, nameA);
99
+ assertVectorLike(b, nameB);
100
+ }
101
+ function assertSameSize(a, b, nameA, nameB) {
102
+ if (a.size !== b.size) {
103
+ throw new ShapeError(
104
+ `${nameA} (size ${a.size}) and ${nameB} (size ${b.size}) must have same size`
105
+ );
106
+ }
107
+ }
108
+
109
+ // src/metrics/classification.ts
110
+ function getNumericLabelData(t) {
111
+ if (t.dtype === "string") {
112
+ throw new DTypeError("metrics do not support string labels");
113
+ }
114
+ if (t.dtype === "int64") {
115
+ throw new DTypeError("metrics do not support int64 tensors");
116
+ }
117
+ const data = t.data;
118
+ if (!isTypedArray(data) || !isNumericTypedArray(data)) {
119
+ throw new DTypeError("metrics require numeric tensors");
120
+ }
121
+ return data;
122
+ }
123
+ function readNumericLabel(data, offsetter, index, name) {
124
+ const value = getNumericElement(data, offsetter(index));
125
+ assertFiniteNumber(value, name, `index ${index}`);
126
+ return value;
127
+ }
128
+ function ensureBinaryValue(value, name, index) {
129
+ if (value !== 0 && value !== 1) {
130
+ throw new InvalidParameterError(
131
+ `${name} must contain only binary values (0 or 1); found ${String(value)} at index ${index}`,
132
+ name,
133
+ value
134
+ );
135
+ }
136
+ }
137
+ function assertBinaryLabels(yTrue, yPred) {
138
+ if (yTrue.dtype === "string" || yPred.dtype === "string") {
139
+ throw new InvalidParameterError(
140
+ "classificationReport requires binary numeric labels (0 or 1)",
141
+ "yTrue"
142
+ );
143
+ }
144
+ const yTrueData = getNumericLabelData(yTrue);
145
+ const yPredData = getNumericLabelData(yPred);
146
+ const trueOffset = createFlatOffsetter(yTrue);
147
+ const predOffset = createFlatOffsetter(yPred);
148
+ for (let i = 0; i < yTrue.size; i++) {
149
+ const trueVal = readNumericLabel(yTrueData, trueOffset, i, "yTrue");
150
+ const predVal = readNumericLabel(yPredData, predOffset, i, "yPred");
151
+ ensureBinaryValue(trueVal, "yTrue", i);
152
+ ensureBinaryValue(predVal, "yPred", i);
153
+ }
154
+ }
155
+ function assertComparableLabelTypes(yTrue, yPred) {
156
+ const trueKind = yTrue.dtype === "string" ? "string" : yTrue.dtype === "int64" ? "int64" : "numeric";
157
+ const predKind = yPred.dtype === "string" ? "string" : yPred.dtype === "int64" ? "int64" : "numeric";
158
+ if (trueKind !== predKind) {
159
+ throw new DTypeError("yTrue and yPred must use compatible label types");
160
+ }
161
+ return trueKind;
162
+ }
163
+ function readComparableLabel(t, offsetter, index, name) {
164
+ const offset = offsetter(index);
165
+ const data = t.data;
166
+ if (Array.isArray(data)) {
167
+ return getStringElement(data, offset);
168
+ }
169
+ if (data instanceof BigInt64Array) {
170
+ return getBigIntElement(data, offset);
171
+ }
172
+ if (!isTypedArray(data) || !isNumericTypedArray(data)) {
173
+ throw new DTypeError(`${name} must be numeric or string labels`);
174
+ }
175
+ const value = getNumericElement(data, offset);
176
+ assertFiniteNumber(value, name, `index ${index}`);
177
+ return value;
178
+ }
179
+ function buildClassStats(yTrue, yPred) {
180
+ assertComparableLabelTypes(yTrue, yPred);
181
+ const trueOffset = createFlatOffsetter(yTrue);
182
+ const predOffset = createFlatOffsetter(yPred);
183
+ const stats = /* @__PURE__ */ new Map();
184
+ let totalTp = 0;
185
+ let totalFp = 0;
186
+ let totalFn = 0;
187
+ for (let i = 0; i < yTrue.size; i++) {
188
+ const trueVal = readComparableLabel(yTrue, trueOffset, i, "yTrue");
189
+ const predVal = readComparableLabel(yPred, predOffset, i, "yPred");
190
+ let trueStats = stats.get(trueVal);
191
+ if (!trueStats) {
192
+ trueStats = { tp: 0, fp: 0, fn: 0, support: 0 };
193
+ stats.set(trueVal, trueStats);
194
+ }
195
+ let predStats = stats.get(predVal);
196
+ if (!predStats) {
197
+ predStats = { tp: 0, fp: 0, fn: 0, support: 0 };
198
+ stats.set(predVal, predStats);
199
+ }
200
+ trueStats.support += 1;
201
+ if (trueVal === predVal) {
202
+ trueStats.tp += 1;
203
+ totalTp += 1;
204
+ } else {
205
+ predStats.fp += 1;
206
+ trueStats.fn += 1;
207
+ totalFp += 1;
208
+ totalFn += 1;
209
+ }
210
+ }
211
+ const classes = Array.from(stats.keys()).sort((a, b) => {
212
+ if (typeof a === "number" && typeof b === "number") return a - b;
213
+ if (typeof a === "string" && typeof b === "string") return a.localeCompare(b);
214
+ if (typeof a === "bigint" && typeof b === "bigint") return a === b ? 0 : a < b ? -1 : 1;
215
+ return String(a).localeCompare(String(b));
216
+ });
217
+ return { classes, stats, totalTp, totalFp, totalFn };
218
+ }
219
+ function accuracy(yTrue, yPred) {
220
+ assertSameSizeVectors(yTrue, yPred, "yTrue", "yPred");
221
+ if (yTrue.size === 0) return 0;
222
+ assertComparableLabelTypes(yTrue, yPred);
223
+ const trueOffset = createFlatOffsetter(yTrue);
224
+ const predOffset = createFlatOffsetter(yPred);
225
+ let correct = 0;
226
+ for (let i = 0; i < yTrue.size; i++) {
227
+ const trueVal = readComparableLabel(yTrue, trueOffset, i, "yTrue");
228
+ const predVal = readComparableLabel(yPred, predOffset, i, "yPred");
229
+ if (trueVal === predVal) {
230
+ correct++;
231
+ }
232
+ }
233
+ return correct / yTrue.size;
234
+ }
235
+ function precision(yTrue, yPred, average = "binary") {
236
+ assertSameSizeVectors(yTrue, yPred, "yTrue", "yPred");
237
+ if (yTrue.size === 0) return average === null ? [] : 0;
238
+ if (average === "binary") {
239
+ if (yTrue.dtype === "string" || yPred.dtype === "string") {
240
+ throw new InvalidParameterError(
241
+ "Binary average requires numeric labels (0/1). Use 'macro', 'micro', or 'weighted' for string labels."
242
+ );
243
+ }
244
+ const yTrueData = getNumericLabelData(yTrue);
245
+ const yPredData = getNumericLabelData(yPred);
246
+ const trueOffset = createFlatOffsetter(yTrue);
247
+ const predOffset = createFlatOffsetter(yPred);
248
+ let tp = 0;
249
+ let fp = 0;
250
+ for (let i = 0; i < yTrue.size; i++) {
251
+ const trueVal = readNumericLabel(yTrueData, trueOffset, i, "yTrue");
252
+ const predVal = readNumericLabel(yPredData, predOffset, i, "yPred");
253
+ ensureBinaryValue(trueVal, "yTrue", i);
254
+ ensureBinaryValue(predVal, "yPred", i);
255
+ if (predVal === 1) {
256
+ if (trueVal === 1) {
257
+ tp++;
258
+ } else {
259
+ fp++;
260
+ }
261
+ }
262
+ }
263
+ return tp + fp === 0 ? 0 : tp / (tp + fp);
264
+ }
265
+ const { classes, stats, totalTp, totalFp } = buildClassStats(yTrue, yPred);
266
+ const precisions = [];
267
+ const supports = [];
268
+ for (const cls of classes) {
269
+ const classStats = stats.get(cls);
270
+ const tp = classStats?.tp ?? 0;
271
+ const fp = classStats?.fp ?? 0;
272
+ const support = classStats?.support ?? 0;
273
+ precisions.push(tp + fp === 0 ? 0 : tp / (tp + fp));
274
+ supports.push(support);
275
+ }
276
+ if (average === null) {
277
+ return precisions;
278
+ }
279
+ if (average === "micro") {
280
+ return totalTp + totalFp === 0 ? 0 : totalTp / (totalTp + totalFp);
281
+ }
282
+ if (average === "macro") {
283
+ const sum = precisions.reduce((acc, val) => acc + val, 0);
284
+ return precisions.length === 0 ? 0 : sum / precisions.length;
285
+ }
286
+ if (average === "weighted") {
287
+ let weightedSum = 0;
288
+ let totalSupport = 0;
289
+ for (let i = 0; i < precisions.length; i++) {
290
+ weightedSum += (precisions[i] ?? 0) * (supports[i] ?? 0);
291
+ totalSupport += supports[i] ?? 0;
292
+ }
293
+ return totalSupport === 0 ? 0 : weightedSum / totalSupport;
294
+ }
295
+ throw new InvalidParameterError(
296
+ `Invalid average parameter: ${average}. Must be one of: 'binary', 'micro', 'macro', 'weighted', or null`,
297
+ "average",
298
+ average
299
+ );
300
+ }
301
+ function recall(yTrue, yPred, average = "binary") {
302
+ assertSameSizeVectors(yTrue, yPred, "yTrue", "yPred");
303
+ if (yTrue.size === 0) return average === null ? [] : 0;
304
+ if (average === "binary") {
305
+ if (yTrue.dtype === "string" || yPred.dtype === "string") {
306
+ throw new InvalidParameterError(
307
+ "Binary average requires numeric labels (0/1). Use 'macro', 'micro', or 'weighted' for string labels."
308
+ );
309
+ }
310
+ const yTrueData = getNumericLabelData(yTrue);
311
+ const yPredData = getNumericLabelData(yPred);
312
+ const trueOffset = createFlatOffsetter(yTrue);
313
+ const predOffset = createFlatOffsetter(yPred);
314
+ let tp = 0;
315
+ let fn = 0;
316
+ for (let i = 0; i < yTrue.size; i++) {
317
+ const trueVal = readNumericLabel(yTrueData, trueOffset, i, "yTrue");
318
+ const predVal = readNumericLabel(yPredData, predOffset, i, "yPred");
319
+ ensureBinaryValue(trueVal, "yTrue", i);
320
+ ensureBinaryValue(predVal, "yPred", i);
321
+ if (trueVal === 1) {
322
+ if (predVal === 1) {
323
+ tp++;
324
+ } else {
325
+ fn++;
326
+ }
327
+ }
328
+ }
329
+ return tp + fn === 0 ? 0 : tp / (tp + fn);
330
+ }
331
+ const { classes, stats, totalTp, totalFn } = buildClassStats(yTrue, yPred);
332
+ const recalls = [];
333
+ const supports = [];
334
+ for (const cls of classes) {
335
+ const classStats = stats.get(cls);
336
+ const tp = classStats?.tp ?? 0;
337
+ const fn = classStats?.fn ?? 0;
338
+ const support = classStats?.support ?? 0;
339
+ recalls.push(tp + fn === 0 ? 0 : tp / (tp + fn));
340
+ supports.push(support);
341
+ }
342
+ if (average === null) {
343
+ return recalls;
344
+ }
345
+ if (average === "micro") {
346
+ return totalTp + totalFn === 0 ? 0 : totalTp / (totalTp + totalFn);
347
+ }
348
+ if (average === "macro") {
349
+ const sum = recalls.reduce((acc, val) => acc + val, 0);
350
+ return recalls.length === 0 ? 0 : sum / recalls.length;
351
+ }
352
+ if (average === "weighted") {
353
+ let weightedSum = 0;
354
+ let totalSupport = 0;
355
+ for (let i = 0; i < recalls.length; i++) {
356
+ weightedSum += (recalls[i] ?? 0) * (supports[i] ?? 0);
357
+ totalSupport += supports[i] ?? 0;
358
+ }
359
+ return totalSupport === 0 ? 0 : weightedSum / totalSupport;
360
+ }
361
+ throw new InvalidParameterError(
362
+ `Invalid average parameter: ${average}. Must be one of: 'binary', 'micro', 'macro', 'weighted', or null`,
363
+ "average",
364
+ average
365
+ );
366
+ }
367
+ function f1Score(yTrue, yPred, average = "binary") {
368
+ if (average === "binary" || average === "micro") {
369
+ const p = precision(yTrue, yPred, average);
370
+ const r = recall(yTrue, yPred, average);
371
+ return p + r === 0 ? 0 : 2 * p * r / (p + r);
372
+ }
373
+ const prec = precision(yTrue, yPred, null);
374
+ const rec = recall(yTrue, yPred, null);
375
+ const f1Scores = [];
376
+ for (let i = 0; i < prec.length; i++) {
377
+ const p = prec[i] ?? 0;
378
+ const r = rec[i] ?? 0;
379
+ f1Scores.push(p + r === 0 ? 0 : 2 * p * r / (p + r));
380
+ }
381
+ if (average === null) {
382
+ return f1Scores;
383
+ }
384
+ if (f1Scores.length === 0) return 0;
385
+ if (average === "macro") {
386
+ const sum = f1Scores.reduce((acc, val) => acc + val, 0);
387
+ return sum / f1Scores.length;
388
+ }
389
+ if (average === "weighted") {
390
+ const { classes, stats } = buildClassStats(yTrue, yPred);
391
+ let weightedSum = 0;
392
+ let totalSupport = 0;
393
+ for (let i = 0; i < f1Scores.length; i++) {
394
+ const cls = classes[i];
395
+ const support = cls !== void 0 ? stats.get(cls)?.support ?? 0 : 0;
396
+ weightedSum += (f1Scores[i] ?? 0) * support;
397
+ totalSupport += support;
398
+ }
399
+ return totalSupport === 0 ? 0 : weightedSum / totalSupport;
400
+ }
401
+ throw new InvalidParameterError(
402
+ `Invalid average parameter: ${average}. Must be one of: 'binary', 'micro', 'macro', 'weighted', or null`,
403
+ "average",
404
+ average
405
+ );
406
+ }
407
+ function fbetaScore(yTrue, yPred, beta, average = "binary") {
408
+ if (!Number.isFinite(beta) || beta <= 0) {
409
+ throw new InvalidParameterError("beta must be a positive finite number", "beta", beta);
410
+ }
411
+ const betaSq = beta * beta;
412
+ if (average === "binary" || average === "micro") {
413
+ const p = precision(yTrue, yPred, average);
414
+ const r = recall(yTrue, yPred, average);
415
+ return p + r === 0 ? 0 : (1 + betaSq) * p * r / (betaSq * p + r);
416
+ }
417
+ const prec = precision(yTrue, yPred, null);
418
+ const rec = recall(yTrue, yPred, null);
419
+ const fbetaScores = [];
420
+ for (let i = 0; i < prec.length; i++) {
421
+ const p = prec[i] ?? 0;
422
+ const r = rec[i] ?? 0;
423
+ fbetaScores.push(p + r === 0 ? 0 : (1 + betaSq) * p * r / (betaSq * p + r));
424
+ }
425
+ if (average === null) {
426
+ return fbetaScores;
427
+ }
428
+ if (fbetaScores.length === 0) return 0;
429
+ if (average === "macro") {
430
+ const sum = fbetaScores.reduce((acc, val) => acc + val, 0);
431
+ return sum / fbetaScores.length;
432
+ }
433
+ if (average === "weighted") {
434
+ const { classes, stats } = buildClassStats(yTrue, yPred);
435
+ let weightedSum = 0;
436
+ let totalSupport = 0;
437
+ for (let i = 0; i < fbetaScores.length; i++) {
438
+ const cls = classes[i];
439
+ const support = cls !== void 0 ? stats.get(cls)?.support ?? 0 : 0;
440
+ weightedSum += (fbetaScores[i] ?? 0) * support;
441
+ totalSupport += support;
442
+ }
443
+ return totalSupport === 0 ? 0 : weightedSum / totalSupport;
444
+ }
445
+ throw new InvalidParameterError(
446
+ `Invalid average parameter: ${average}. Must be one of: 'binary', 'micro', 'macro', 'weighted', or null`,
447
+ "average",
448
+ average
449
+ );
450
+ }
451
+ function confusionMatrix(yTrue, yPred) {
452
+ assertSameSizeVectors(yTrue, yPred, "yTrue", "yPred");
453
+ assertComparableLabelTypes(yTrue, yPred);
454
+ if (yTrue.size === 0) {
455
+ return tensor([]).reshape([0, 0]);
456
+ }
457
+ const trueOffset = createFlatOffsetter(yTrue);
458
+ const predOffset = createFlatOffsetter(yPred);
459
+ const labelSet = /* @__PURE__ */ new Set();
460
+ for (let i = 0; i < yTrue.size; i++) {
461
+ labelSet.add(readComparableLabel(yTrue, trueOffset, i, "yTrue"));
462
+ labelSet.add(readComparableLabel(yPred, predOffset, i, "yPred"));
463
+ }
464
+ const labels = Array.from(labelSet).sort((a, b) => {
465
+ if (typeof a === "number" && typeof b === "number") return a - b;
466
+ if (typeof a === "string" && typeof b === "string") return a.localeCompare(b);
467
+ if (typeof a === "bigint" && typeof b === "bigint") return a === b ? 0 : a < b ? -1 : 1;
468
+ return String(a).localeCompare(String(b));
469
+ });
470
+ const labelToIndex = /* @__PURE__ */ new Map();
471
+ for (let i = 0; i < labels.length; i++) {
472
+ const label = labels[i];
473
+ if (label === void 0) continue;
474
+ labelToIndex.set(label, i);
475
+ }
476
+ const nClasses = labels.length;
477
+ const matrix = Array.from({ length: nClasses }, () => new Array(nClasses).fill(0));
478
+ for (let i = 0; i < yTrue.size; i++) {
479
+ const trueLabel = readComparableLabel(yTrue, trueOffset, i, "yTrue");
480
+ const predLabel = readComparableLabel(yPred, predOffset, i, "yPred");
481
+ const r = labelToIndex.get(trueLabel);
482
+ const c = labelToIndex.get(predLabel);
483
+ if (r === void 0 || c === void 0) continue;
484
+ const row = matrix[r];
485
+ if (row) row[c] = (row[c] ?? 0) + 1;
486
+ }
487
+ return tensor(matrix);
488
+ }
489
+ function classificationReport(yTrue, yPred) {
490
+ assertSameSizeVectors(yTrue, yPred, "yTrue", "yPred");
491
+ if (yTrue.size === 0) return "Classification Report:\n (empty)";
492
+ assertBinaryLabels(yTrue, yPred);
493
+ const { classes, stats } = buildClassStats(yTrue, yPred);
494
+ const precs = precision(yTrue, yPred, null);
495
+ const recs = recall(yTrue, yPred, null);
496
+ const f1s = f1Score(yTrue, yPred, null);
497
+ const acc = accuracy(yTrue, yPred);
498
+ const maxClassLen = Math.max(...classes.map((c) => String(c).length), "Class".length);
499
+ const colWidth = Math.max(12, maxClassLen + 2);
500
+ let report = "Classification Report:\n";
501
+ report += "Class".padEnd(colWidth) + "Precision".padEnd(12) + "Recall".padEnd(12) + "F1-Score".padEnd(12) + "Support\n";
502
+ report += `${"-".repeat(colWidth + 36 + 7)}
503
+ `;
504
+ let totalSupport = 0;
505
+ let weightedPrec = 0;
506
+ let weightedRec = 0;
507
+ let weightedF1 = 0;
508
+ let macroPrec = 0;
509
+ let macroRec = 0;
510
+ let macroF1 = 0;
511
+ for (const [i, cls] of classes.entries()) {
512
+ const p = precs[i] ?? 0;
513
+ const r = recs[i] ?? 0;
514
+ const f1 = f1s[i] ?? 0;
515
+ const s = stats.get(cls)?.support ?? 0;
516
+ totalSupport += s;
517
+ weightedPrec += p * s;
518
+ weightedRec += r * s;
519
+ weightedF1 += f1 * s;
520
+ macroPrec += p;
521
+ macroRec += r;
522
+ macroF1 += f1;
523
+ report += String(cls).padEnd(colWidth) + p.toFixed(4).padEnd(12) + r.toFixed(4).padEnd(12) + f1.toFixed(4).padEnd(12) + String(s) + "\n";
524
+ }
525
+ report += "\n";
526
+ const nClasses = classes.length;
527
+ if (nClasses > 0) {
528
+ macroPrec /= nClasses;
529
+ macroRec /= nClasses;
530
+ macroF1 /= nClasses;
531
+ } else {
532
+ macroPrec = 0;
533
+ macroRec = 0;
534
+ macroF1 = 0;
535
+ }
536
+ weightedPrec = totalSupport === 0 ? 0 : weightedPrec / totalSupport;
537
+ weightedRec = totalSupport === 0 ? 0 : weightedRec / totalSupport;
538
+ weightedF1 = totalSupport === 0 ? 0 : weightedF1 / totalSupport;
539
+ report += "Accuracy".padEnd(colWidth) + "".padEnd(12) + "".padEnd(12) + acc.toFixed(4).padEnd(12) + String(totalSupport) + "\n";
540
+ report += "Macro Avg".padEnd(colWidth) + macroPrec.toFixed(4).padEnd(12) + macroRec.toFixed(4).padEnd(12) + macroF1.toFixed(4).padEnd(12) + String(totalSupport) + "\n";
541
+ report += "Weighted Avg".padEnd(colWidth) + weightedPrec.toFixed(4).padEnd(12) + weightedRec.toFixed(4).padEnd(12) + weightedF1.toFixed(4).padEnd(12) + String(totalSupport);
542
+ return report;
543
+ }
544
+ function rocCurve(yTrue, yScore) {
545
+ assertSameSizeVectors(yTrue, yScore, "yTrue", "yScore");
546
+ const n = yTrue.size;
547
+ if (n === 0) return [tensor([]), tensor([]), tensor([])];
548
+ const yTrueData = getNumericLabelData(yTrue);
549
+ const yScoreData = getNumericLabelData(yScore);
550
+ const trueOffset = createFlatOffsetter(yTrue);
551
+ const scoreOffset = createFlatOffsetter(yScore);
552
+ const pairs = [];
553
+ let nPos = 0;
554
+ let nNeg = 0;
555
+ for (let i = 0; i < n; i++) {
556
+ const label = readNumericLabel(yTrueData, trueOffset, i, "yTrue");
557
+ ensureBinaryValue(label, "yTrue", i);
558
+ const score = readNumericLabel(yScoreData, scoreOffset, i, "yScore");
559
+ pairs.push({ score, label });
560
+ if (label === 1) nPos++;
561
+ else nNeg++;
562
+ }
563
+ pairs.sort((a, b) => b.score - a.score);
564
+ if (nPos === 0 || nNeg === 0) return [tensor([]), tensor([]), tensor([])];
565
+ const fpr = [0];
566
+ const tpr = [0];
567
+ const thresholds = [Infinity];
568
+ let tp = 0;
569
+ let fp = 0;
570
+ let idx = 0;
571
+ while (idx < pairs.length) {
572
+ const threshold = pairs[idx]?.score ?? 0;
573
+ while (idx < pairs.length && (pairs[idx]?.score ?? 0) === threshold) {
574
+ const label = pairs[idx]?.label ?? 0;
575
+ if (label === 1) tp++;
576
+ else fp++;
577
+ idx++;
578
+ }
579
+ fpr.push(fp / nNeg);
580
+ tpr.push(tp / nPos);
581
+ thresholds.push(threshold);
582
+ }
583
+ return [tensor(fpr), tensor(tpr), tensor(thresholds)];
584
+ }
585
+ function rocAucScore(yTrue, yScore) {
586
+ const curves = rocCurve(yTrue, yScore);
587
+ const fprT = curves[0];
588
+ const tprT = curves[1];
589
+ if (!fprT || !tprT || fprT.size === 0 || tprT.size === 0) return 0.5;
590
+ const fprData = getNumericLabelData(fprT);
591
+ const tprData = getNumericLabelData(tprT);
592
+ const fprOffset = createFlatOffsetter(fprT);
593
+ const tprOffset = createFlatOffsetter(tprT);
594
+ let auc = 0;
595
+ let prevX = 0;
596
+ let prevY = 0;
597
+ for (let i = 1; i < fprT.size; i++) {
598
+ const x = readNumericLabel(fprData, fprOffset, i, "fpr");
599
+ const y = readNumericLabel(tprData, tprOffset, i, "tpr");
600
+ auc += (x - prevX) * ((y + prevY) / 2);
601
+ prevX = x;
602
+ prevY = y;
603
+ }
604
+ return auc;
605
+ }
606
+ function precisionRecallCurve(yTrue, yScore) {
607
+ assertSameSizeVectors(yTrue, yScore, "yTrue", "yScore");
608
+ const n = yTrue.size;
609
+ if (n === 0) return [tensor([]), tensor([]), tensor([])];
610
+ const yTrueData = getNumericLabelData(yTrue);
611
+ const yScoreData = getNumericLabelData(yScore);
612
+ const trueOffset = createFlatOffsetter(yTrue);
613
+ const scoreOffset = createFlatOffsetter(yScore);
614
+ const pairs = [];
615
+ let nPos = 0;
616
+ for (let i = 0; i < n; i++) {
617
+ const label = readNumericLabel(yTrueData, trueOffset, i, "yTrue");
618
+ ensureBinaryValue(label, "yTrue", i);
619
+ const score = readNumericLabel(yScoreData, scoreOffset, i, "yScore");
620
+ pairs.push({ score, label });
621
+ if (label === 1) nPos++;
622
+ }
623
+ pairs.sort((a, b) => b.score - a.score);
624
+ if (nPos === 0) return [tensor([]), tensor([]), tensor([])];
625
+ const prec = [1];
626
+ const rec = [0];
627
+ const thresholds = [Infinity];
628
+ let tp = 0;
629
+ let fp = 0;
630
+ let idx = 0;
631
+ while (idx < pairs.length) {
632
+ const threshold = pairs[idx]?.score ?? 0;
633
+ while (idx < pairs.length && (pairs[idx]?.score ?? 0) === threshold) {
634
+ const label = pairs[idx]?.label ?? 0;
635
+ if (label === 1) tp++;
636
+ else fp++;
637
+ idx++;
638
+ }
639
+ const precisionVal = tp + fp === 0 ? 1 : tp / (tp + fp);
640
+ const recallVal = tp / nPos;
641
+ prec.push(precisionVal);
642
+ rec.push(recallVal);
643
+ thresholds.push(threshold);
644
+ }
645
+ return [tensor(prec), tensor(rec), tensor(thresholds)];
646
+ }
647
+ function averagePrecisionScore(yTrue, yScore) {
648
+ const curves = precisionRecallCurve(yTrue, yScore);
649
+ const precT = curves[0];
650
+ const recT = curves[1];
651
+ if (!precT || !recT || precT.size === 0 || recT.size === 0) return 0;
652
+ const precData = getNumericLabelData(precT);
653
+ const recData = getNumericLabelData(recT);
654
+ const precOffset = createFlatOffsetter(precT);
655
+ const recOffset = createFlatOffsetter(recT);
656
+ let ap = 0;
657
+ let prevRecall = readNumericLabel(recData, recOffset, 0, "recall");
658
+ for (let i = 1; i < recT.size; i++) {
659
+ const recall2 = readNumericLabel(recData, recOffset, i, "recall");
660
+ const precision2 = readNumericLabel(precData, precOffset, i, "precision");
661
+ const deltaRecall = recall2 - prevRecall;
662
+ if (deltaRecall > 0) {
663
+ ap += deltaRecall * precision2;
664
+ }
665
+ prevRecall = recall2;
666
+ }
667
+ return ap;
668
+ }
669
+ function logLoss(yTrue, yPred) {
670
+ assertSameSizeVectors(yTrue, yPred, "yTrue", "yPred");
671
+ if (yTrue.size === 0) return 0;
672
+ const yTrueData = getNumericLabelData(yTrue);
673
+ const yPredData = getNumericLabelData(yPred);
674
+ const trueOffset = createFlatOffsetter(yTrue);
675
+ const predOffset = createFlatOffsetter(yPred);
676
+ const eps = 1e-15;
677
+ let loss = 0;
678
+ for (let i = 0; i < yTrue.size; i++) {
679
+ const trueVal = readNumericLabel(yTrueData, trueOffset, i, "yTrue");
680
+ ensureBinaryValue(trueVal, "yTrue", i);
681
+ const predRaw = readNumericLabel(yPredData, predOffset, i, "yPred");
682
+ if (predRaw < 0 || predRaw > 1) {
683
+ throw new InvalidParameterError(
684
+ `yPred must contain probabilities in range [0, 1], found ${String(predRaw)} at index ${i}`,
685
+ "yPred",
686
+ predRaw
687
+ );
688
+ }
689
+ const predVal = Math.max(eps, Math.min(1 - eps, predRaw));
690
+ loss -= trueVal * Math.log(predVal) + (1 - trueVal) * Math.log(1 - predVal);
691
+ }
692
+ return loss / yTrue.size;
693
+ }
694
+ function hammingLoss(yTrue, yPred) {
695
+ assertSameSizeVectors(yTrue, yPred, "yTrue", "yPred");
696
+ if (yTrue.size === 0) return 0;
697
+ assertComparableLabelTypes(yTrue, yPred);
698
+ const trueOffset = createFlatOffsetter(yTrue);
699
+ const predOffset = createFlatOffsetter(yPred);
700
+ let errors = 0;
701
+ for (let i = 0; i < yTrue.size; i++) {
702
+ const trueVal = readComparableLabel(yTrue, trueOffset, i, "yTrue");
703
+ const predVal = readComparableLabel(yPred, predOffset, i, "yPred");
704
+ if (trueVal !== predVal) {
705
+ errors++;
706
+ }
707
+ }
708
+ return errors / yTrue.size;
709
+ }
710
+ function jaccardScore(yTrue, yPred) {
711
+ assertSameSizeVectors(yTrue, yPred, "yTrue", "yPred");
712
+ if (yTrue.size === 0) return 1;
713
+ const yTrueData = getNumericLabelData(yTrue);
714
+ const yPredData = getNumericLabelData(yPred);
715
+ const trueOffset = createFlatOffsetter(yTrue);
716
+ const predOffset = createFlatOffsetter(yPred);
717
+ let tp = 0, fp = 0, fn = 0;
718
+ for (let i = 0; i < yTrue.size; i++) {
719
+ const trueVal = readNumericLabel(yTrueData, trueOffset, i, "yTrue");
720
+ const predVal = readNumericLabel(yPredData, predOffset, i, "yPred");
721
+ ensureBinaryValue(trueVal, "yTrue", i);
722
+ ensureBinaryValue(predVal, "yPred", i);
723
+ if (trueVal === 1 && predVal === 1) tp++;
724
+ else if (trueVal === 0 && predVal === 1) fp++;
725
+ else if (trueVal === 1 && predVal === 0) fn++;
726
+ }
727
+ return tp + fp + fn === 0 ? 1 : tp / (tp + fp + fn);
728
+ }
729
+ function matthewsCorrcoef(yTrue, yPred) {
730
+ assertSameSizeVectors(yTrue, yPred, "yTrue", "yPred");
731
+ if (yTrue.size === 0) return 0;
732
+ const yTrueData = getNumericLabelData(yTrue);
733
+ const yPredData = getNumericLabelData(yPred);
734
+ const trueOffset = createFlatOffsetter(yTrue);
735
+ const predOffset = createFlatOffsetter(yPred);
736
+ let tp = 0, tn = 0, fp = 0, fn = 0;
737
+ for (let i = 0; i < yTrue.size; i++) {
738
+ const trueVal = readNumericLabel(yTrueData, trueOffset, i, "yTrue");
739
+ const predVal = readNumericLabel(yPredData, predOffset, i, "yPred");
740
+ ensureBinaryValue(trueVal, "yTrue", i);
741
+ ensureBinaryValue(predVal, "yPred", i);
742
+ if (trueVal === 1 && predVal === 1) tp++;
743
+ else if (trueVal === 0 && predVal === 0) tn++;
744
+ else if (trueVal === 0 && predVal === 1) fp++;
745
+ else if (trueVal === 1 && predVal === 0) fn++;
746
+ }
747
+ const numerator = tp * tn - fp * fn;
748
+ const denominator = Math.sqrt((tp + fp) * (tp + fn) * (tn + fp) * (tn + fn));
749
+ return denominator === 0 ? 0 : numerator / denominator;
750
+ }
751
+ function cohenKappaScore(yTrue, yPred) {
752
+ assertSameSizeVectors(yTrue, yPred, "yTrue", "yPred");
753
+ const n = yTrue.size;
754
+ if (n === 0) return 0;
755
+ const yTrueData = getNumericLabelData(yTrue);
756
+ const yPredData = getNumericLabelData(yPred);
757
+ const trueOffset = createFlatOffsetter(yTrue);
758
+ const predOffset = createFlatOffsetter(yPred);
759
+ let po = 0;
760
+ const trueCount = /* @__PURE__ */ new Map();
761
+ const predCount = /* @__PURE__ */ new Map();
762
+ for (let i = 0; i < n; i++) {
763
+ const t = readNumericLabel(yTrueData, trueOffset, i, "yTrue");
764
+ const p = readNumericLabel(yPredData, predOffset, i, "yPred");
765
+ if (t === p) {
766
+ po++;
767
+ }
768
+ trueCount.set(t, (trueCount.get(t) ?? 0) + 1);
769
+ predCount.set(p, (predCount.get(p) ?? 0) + 1);
770
+ }
771
+ po /= n;
772
+ let pe = 0;
773
+ const allClasses = /* @__PURE__ */ new Set([...trueCount.keys(), ...predCount.keys()]);
774
+ for (const c of allClasses) {
775
+ const trueProb = (trueCount.get(c) ?? 0) / n;
776
+ const predProb = (predCount.get(c) ?? 0) / n;
777
+ pe += trueProb * predProb;
778
+ }
779
+ const denom = 1 - pe;
780
+ if (denom === 0) return po === 1 ? 1 : 0;
781
+ return (po - pe) / denom;
782
+ }
783
+ function balancedAccuracyScore(yTrue, yPred) {
784
+ assertSameSizeVectors(yTrue, yPred, "yTrue", "yPred");
785
+ if (yTrue.size === 0) return 0;
786
+ const { classes, stats } = buildClassStats(yTrue, yPred);
787
+ let sumRecall = 0;
788
+ let classCount = 0;
789
+ for (const cls of classes) {
790
+ const classStats = stats.get(cls);
791
+ const support = classStats?.support ?? 0;
792
+ if (support === 0) continue;
793
+ const tp = classStats?.tp ?? 0;
794
+ const fn = classStats?.fn ?? 0;
795
+ const recall2 = tp + fn > 0 ? tp / (tp + fn) : 0;
796
+ sumRecall += recall2;
797
+ classCount++;
798
+ }
799
+ return classCount === 0 ? 0 : sumRecall / classCount;
800
+ }
801
+
802
+ // src/metrics/clustering.ts
803
+ function isBigIntTypedArray(x) {
804
+ return x instanceof BigInt64Array || x instanceof BigUint64Array;
805
+ }
806
+ function readIndex(arr, index, name) {
807
+ const v = arr[index];
808
+ if (v === void 0) {
809
+ throw new DataValidationError(`${name} index out of range: ${index}`);
810
+ }
811
+ return v;
812
+ }
813
+ function getNumericTensorData(t, name) {
814
+ if (t.dtype === "string") {
815
+ throw new DTypeError(`${name} must be numeric (string tensors not supported)`);
816
+ }
817
+ if (t.dtype === "int64") {
818
+ throw new DTypeError(`${name} must be numeric (int64 tensors not supported)`);
819
+ }
820
+ const data = t.data;
821
+ if (!isTypedArray(data) || !isNumericTypedArray(data)) {
822
+ throw new DTypeError(`${name} must be a numeric tensor`);
823
+ }
824
+ return data;
825
+ }
826
+ function getFeatureAccessor(X) {
827
+ const data = getNumericTensorData(X, "X");
828
+ if (X.ndim === 0 || X.ndim > 2) {
829
+ throw new ShapeError("X must be a 1D or 2D tensor");
830
+ }
831
+ const nSamples = X.shape[0] ?? 0;
832
+ const nFeatures = X.ndim === 1 ? 1 : X.shape[1] ?? 0;
833
+ if (nFeatures === 0) {
834
+ throw new ShapeError("X must have at least one feature");
835
+ }
836
+ const logicalStrides = computeLogicalStrides(X.shape);
837
+ const logical0 = logicalStrides[0];
838
+ if (logical0 === void 0) throw new ShapeError("Invalid logical strides");
839
+ const sampleStride = X.strides[0] ?? logical0 ?? nFeatures;
840
+ const featureStride = X.ndim === 1 ? 0 : X.strides[1] ?? logicalStrides[1] ?? 1;
841
+ if (X.ndim === 2 && featureStride === 0) {
842
+ throw new ShapeError("X must have a non-degenerate feature stride");
843
+ }
844
+ return {
845
+ data,
846
+ nSamples,
847
+ nFeatures,
848
+ sampleStride,
849
+ featureStride,
850
+ offset: X.offset
851
+ };
852
+ }
853
+ function readLabelValue(labels, offsetter, index, name) {
854
+ const data = labels.data;
855
+ const flat = offsetter(index);
856
+ if (isBigIntTypedArray(data)) {
857
+ const v = data[flat];
858
+ if (v === void 0) {
859
+ throw new DataValidationError(`${name} must contain a value for index ${index}`);
860
+ }
861
+ return v;
862
+ }
863
+ if (isTypedArray(data) && isNumericTypedArray(data)) {
864
+ const v = getNumericElement(data, flat);
865
+ assertFiniteNumber(v, name, `index ${index}`);
866
+ if ((labels.dtype === "float32" || labels.dtype === "float64") && !Number.isInteger(v)) {
867
+ throw new DataValidationError(
868
+ `${name} must contain discrete labels; found non-integer ${String(v)} at index ${index}`
869
+ );
870
+ }
871
+ if (labels.dtype === "int64" && (!Number.isInteger(v) || !Number.isSafeInteger(v))) {
872
+ throw new DataValidationError(
873
+ `${name} contains an int64 value that cannot be represented safely as a number at index ${index}`
874
+ );
875
+ }
876
+ return v;
877
+ }
878
+ if (Array.isArray(data)) {
879
+ const v = data[flat];
880
+ if (v === void 0 || v === null) {
881
+ throw new DataValidationError(`${name} must contain a value for index ${index}`);
882
+ }
883
+ if (typeof v === "string") return v;
884
+ if (typeof v === "boolean") return v;
885
+ if (typeof v === "number") {
886
+ assertFiniteNumber(v, name, `index ${index}`);
887
+ if ((labels.dtype === "float32" || labels.dtype === "float64") && !Number.isInteger(v)) {
888
+ throw new DataValidationError(
889
+ `${name} must contain discrete labels; found non-integer ${String(v)} at index ${index}`
890
+ );
891
+ }
892
+ if (labels.dtype === "int64" && (!Number.isInteger(v) || !Number.isSafeInteger(v))) {
893
+ throw new DataValidationError(
894
+ `${name} contains an int64 value that cannot be represented safely as a number at index ${index}`
895
+ );
896
+ }
897
+ return v;
898
+ }
899
+ throw new DTypeError(`${name} must contain primitive labels (string, boolean, number, bigint)`);
900
+ }
901
+ throw new DTypeError(`${name} has unsupported backing storage for labels`);
902
+ }
903
+ function encodeLabels(labels, name) {
904
+ if (labels.dtype === "string") {
905
+ throw new DTypeError(`${name}: string labels not supported for clustering metrics`);
906
+ }
907
+ const n = labels.size;
908
+ const codes = new Int32Array(n);
909
+ const offsetter = createFlatOffsetter(labels);
910
+ const map = /* @__PURE__ */ new Map();
911
+ let next = 0;
912
+ for (let i = 0; i < n; i++) {
913
+ const raw = readLabelValue(labels, offsetter, i, name);
914
+ const existing = map.get(raw);
915
+ if (existing === void 0) {
916
+ map.set(raw, next);
917
+ codes[i] = next;
918
+ next++;
919
+ } else {
920
+ codes[i] = existing;
921
+ }
922
+ }
923
+ return { codes, nClusters: next };
924
+ }
925
+ function comb2(x) {
926
+ return x <= 1 ? 0 : x * (x - 1) / 2;
927
+ }
928
+ function buildContingencyStats(labelsTrue, labelsPred) {
929
+ assertSameSize(labelsTrue, labelsPred, "labelsTrue", "labelsPred");
930
+ const n = labelsTrue.size;
931
+ const encT = encodeLabels(labelsTrue, "labelsTrue");
932
+ const encP = encodeLabels(labelsPred, "labelsPred");
933
+ const trueCodes = encT.codes;
934
+ const predCodes = encP.codes;
935
+ const nTrue = encT.nClusters;
936
+ const nPred = encP.nClusters;
937
+ const trueCount = new Int32Array(nTrue);
938
+ const predCount = new Int32Array(nPred);
939
+ for (let i = 0; i < n; i++) {
940
+ const t = readIndex(trueCodes, i, "trueCodes");
941
+ const p = readIndex(predCodes, i, "predCodes");
942
+ trueCount[t] = (trueCount[t] ?? 0) + 1;
943
+ predCount[p] = (predCount[p] ?? 0) + 1;
944
+ }
945
+ const denseSize = nTrue * nPred;
946
+ const maxDenseCells = 4e6;
947
+ if (denseSize > 0 && denseSize <= maxDenseCells) {
948
+ const contingency2 = new Int32Array(denseSize);
949
+ for (let i = 0; i < n; i++) {
950
+ const t = readIndex(trueCodes, i, "trueCodes");
951
+ const p = readIndex(predCodes, i, "predCodes");
952
+ const idx = t * nPred + p;
953
+ contingency2[idx] = (contingency2[idx] ?? 0) + 1;
954
+ }
955
+ return {
956
+ contingencyDense: contingency2,
957
+ contingencySparse: null,
958
+ trueCount,
959
+ predCount,
960
+ nTrue,
961
+ nPred,
962
+ n
963
+ };
964
+ }
965
+ const contingency = /* @__PURE__ */ new Map();
966
+ for (let i = 0; i < n; i++) {
967
+ const t = readIndex(trueCodes, i, "trueCodes");
968
+ const p = readIndex(predCodes, i, "predCodes");
969
+ const key = t * nPred + p;
970
+ contingency.set(key, (contingency.get(key) ?? 0) + 1);
971
+ }
972
+ return {
973
+ contingencyDense: null,
974
+ contingencySparse: contingency,
975
+ trueCount,
976
+ predCount,
977
+ nTrue,
978
+ nPred,
979
+ n
980
+ };
981
+ }
982
+ function entropyFromCountArray(counts, n) {
983
+ if (n === 0) return 0;
984
+ let h = 0;
985
+ for (let i = 0; i < counts.length; i++) {
986
+ const c = readIndex(counts, i, "counts");
987
+ if (c > 0) {
988
+ const p = c / n;
989
+ h -= p * Math.log(p);
990
+ }
991
+ }
992
+ return h;
993
+ }
994
+ function mutualInformationFromContingency(stats) {
995
+ const { contingencyDense, contingencySparse, trueCount, predCount, nPred, n } = stats;
996
+ if (n === 0) return 0;
997
+ let mi = 0;
998
+ if (contingencyDense) {
999
+ for (let idx = 0; idx < contingencyDense.length; idx++) {
1000
+ const nij = readIndex(contingencyDense, idx, "contingencyDense");
1001
+ if (nij <= 0) continue;
1002
+ const t = Math.floor(idx / nPred);
1003
+ const p = idx - t * nPred;
1004
+ const ni = readIndex(trueCount, t, "trueCount");
1005
+ const nj = readIndex(predCount, p, "predCount");
1006
+ if (ni > 0 && nj > 0) {
1007
+ mi += nij / n * Math.log(n * nij / (ni * nj));
1008
+ }
1009
+ }
1010
+ return mi;
1011
+ }
1012
+ if (contingencySparse) {
1013
+ for (const [key, nij] of contingencySparse) {
1014
+ if (nij <= 0) continue;
1015
+ const t = Math.floor(key / nPred);
1016
+ const p = key - t * nPred;
1017
+ const ni = readIndex(trueCount, t, "trueCount");
1018
+ const nj = readIndex(predCount, p, "predCount");
1019
+ if (ni > 0 && nj > 0) {
1020
+ mi += nij / n * Math.log(n * nij / (ni * nj));
1021
+ }
1022
+ }
1023
+ return mi;
1024
+ }
1025
+ return 0;
1026
+ }
1027
+ function buildLogFactorials(n) {
1028
+ const out = new Float64Array(n + 1);
1029
+ for (let i = 1; i <= n; i++) {
1030
+ out[i] = (out[i - 1] ?? 0) + Math.log(i);
1031
+ }
1032
+ return out;
1033
+ }
1034
+ function logCombination(n, k, logFactorials) {
1035
+ if (k < 0 || k > n) return Number.NEGATIVE_INFINITY;
1036
+ const a = readIndex(logFactorials, n, "logFactorials");
1037
+ const b = readIndex(logFactorials, k, "logFactorials");
1038
+ const c = readIndex(logFactorials, n - k, "logFactorials");
1039
+ return a - b - c;
1040
+ }
1041
+ var LOG_EXP_UNDERFLOW_CUTOFF = -745;
1042
+ function expectedMutualInformation(stats) {
1043
+ const { trueCount, predCount, n } = stats;
1044
+ if (n <= 1) return 0;
1045
+ const rowSums = Array.from(trueCount);
1046
+ const colSums = Array.from(predCount);
1047
+ const logFactorials = buildLogFactorials(n);
1048
+ let emi = 0;
1049
+ let comp = 0;
1050
+ for (const a of rowSums) {
1051
+ if (a <= 0) continue;
1052
+ for (const b of colSums) {
1053
+ if (b <= 0) continue;
1054
+ const nijMin = Math.max(1, a + b - n);
1055
+ const nijMax = Math.min(a, b);
1056
+ if (nijMin > nijMax) continue;
1057
+ const logDenominator = logCombination(n, b, logFactorials);
1058
+ for (let nij = nijMin; nij <= nijMax; nij++) {
1059
+ const logProbability = logCombination(a, nij, logFactorials) + logCombination(n - a, b - nij, logFactorials) - logDenominator;
1060
+ if (logProbability < LOG_EXP_UNDERFLOW_CUTOFF) continue;
1061
+ const probability = Math.exp(logProbability);
1062
+ if (!Number.isFinite(probability) || probability === 0) continue;
1063
+ const miTerm = nij / n * Math.log(n * nij / (a * b));
1064
+ const y = probability * miTerm - comp;
1065
+ const t = emi + y;
1066
+ comp = t - emi - y;
1067
+ emi = t;
1068
+ }
1069
+ }
1070
+ }
1071
+ return emi;
1072
+ }
1073
+ function averageEntropy(hTrue, hPred, method) {
1074
+ if (method === "min") return Math.min(hTrue, hPred);
1075
+ if (method === "max") return Math.max(hTrue, hPred);
1076
+ if (method === "geometric") return Math.sqrt(hTrue * hPred);
1077
+ return (hTrue + hPred) / 2;
1078
+ }
1079
+ function euclideanDistance(data, offset, sampleStride, featureStride, nFeatures, i, j) {
1080
+ let sum = 0;
1081
+ const baseI = offset + i * sampleStride;
1082
+ const baseJ = offset + j * sampleStride;
1083
+ for (let k = 0; k < nFeatures; k++) {
1084
+ const vi = getNumericElement(data, baseI + k * featureStride);
1085
+ const vj = getNumericElement(data, baseJ + k * featureStride);
1086
+ assertFiniteNumber(vi, "X", `sample ${i}, feature ${k}`);
1087
+ assertFiniteNumber(vj, "X", `sample ${j}, feature ${k}`);
1088
+ const d = vi - vj;
1089
+ sum += d * d;
1090
+ }
1091
+ return Math.sqrt(sum);
1092
+ }
1093
+ function getPrecomputedDistanceAccessor(X, n) {
1094
+ const data = getNumericTensorData(X, "X");
1095
+ if (X.ndim !== 2) {
1096
+ throw new ShapeError("X must be a 2D tensor for metric='precomputed'");
1097
+ }
1098
+ const rows = X.shape[0] ?? 0;
1099
+ const cols = X.shape[1] ?? 0;
1100
+ if (rows !== n || cols !== n) {
1101
+ throw new ShapeError(
1102
+ "For metric='precomputed', X must be a square [n_samples, n_samples] matrix"
1103
+ );
1104
+ }
1105
+ const logicalStrides = computeLogicalStrides(X.shape);
1106
+ const rowStride = X.strides[0] ?? logicalStrides[0] ?? n;
1107
+ const colStride = X.strides[1] ?? logicalStrides[1] ?? 1;
1108
+ if (rowStride === 0 || colStride === 0) {
1109
+ throw new ShapeError("Precomputed distance matrix must have non-degenerate strides");
1110
+ }
1111
+ const base = X.offset;
1112
+ return (i, j) => {
1113
+ const v = getNumericElement(data, base + i * rowStride + j * colStride);
1114
+ assertFiniteNumber(v, "X", `distance[${i},${j}]`);
1115
+ if (v < 0) {
1116
+ throw new DataValidationError(
1117
+ `Precomputed distances must be non-negative; found ${String(v)} at [${i},${j}]`
1118
+ );
1119
+ }
1120
+ return v;
1121
+ };
1122
+ }
1123
+ function validateSilhouetteLabels(labels, nSamples) {
1124
+ if (labels.size !== nSamples) {
1125
+ throw new ShapeError("labels length must match number of samples");
1126
+ }
1127
+ const enc = encodeLabels(labels, "labels");
1128
+ const k = enc.nClusters;
1129
+ if (k < 2 || k > nSamples - 1) {
1130
+ throw new InvalidParameterError(
1131
+ "silhouette requires 2 <= n_clusters <= n_samples - 1",
1132
+ "n_clusters",
1133
+ k
1134
+ );
1135
+ }
1136
+ return enc;
1137
+ }
1138
+ function reservoirSampleIndices(n, k, seed) {
1139
+ let state = (seed ?? 0) >>> 0;
1140
+ const hasSeed = seed !== void 0;
1141
+ const randU32 = () => {
1142
+ if (!hasSeed) return Math.random() * 4294967296 >>> 0;
1143
+ state = 1664525 * state + 1013904223 >>> 0;
1144
+ return state;
1145
+ };
1146
+ const randInt = (exclusiveMax) => randU32() % exclusiveMax;
1147
+ const out = new Int32Array(k);
1148
+ for (let i = 0; i < k; i++) out[i] = i;
1149
+ for (let i = k; i < n; i++) {
1150
+ const j = randInt(i + 1);
1151
+ if (j < k) out[j] = i;
1152
+ }
1153
+ return out;
1154
+ }
1155
+ function reencodeSubset(codes) {
1156
+ const out = new Int32Array(codes.length);
1157
+ const map = /* @__PURE__ */ new Map();
1158
+ let next = 0;
1159
+ for (let i = 0; i < codes.length; i++) {
1160
+ const v = readIndex(codes, i, "codes");
1161
+ const existing = map.get(v);
1162
+ if (existing === void 0) {
1163
+ map.set(v, next);
1164
+ out[i] = next;
1165
+ next++;
1166
+ } else {
1167
+ out[i] = existing;
1168
+ }
1169
+ }
1170
+ return { codes: out, nClusters: next };
1171
+ }
1172
+ function silhouetteMeanEuclidean(X, labels, indices) {
1173
+ const { data, nSamples, nFeatures, sampleStride, featureStride, offset } = getFeatureAccessor(X);
1174
+ const n = indices ? indices.length : nSamples;
1175
+ if (nSamples < 2 || n < 2) {
1176
+ throw new InvalidParameterError("silhouette requires at least 2 samples", "n_samples", n);
1177
+ }
1178
+ const encAll = validateSilhouetteLabels(labels, nSamples);
1179
+ const subsetCodesRaw = new Int32Array(n);
1180
+ for (let i = 0; i < n; i++) {
1181
+ const src = indices ? readIndex(indices, i, "indices") : i;
1182
+ const c = readIndex(encAll.codes, src, "labels.codes");
1183
+ subsetCodesRaw[i] = c;
1184
+ }
1185
+ const re = reencodeSubset(subsetCodesRaw);
1186
+ const codes = re.codes;
1187
+ const k = re.nClusters;
1188
+ if (k < 2 || k > n - 1) {
1189
+ throw new InvalidParameterError(
1190
+ "silhouette requires 2 <= n_clusters <= n_samples - 1",
1191
+ "n_clusters",
1192
+ k
1193
+ );
1194
+ }
1195
+ const clusterSizes = new Int32Array(k);
1196
+ for (let i = 0; i < n; i++) {
1197
+ const ci = readIndex(codes, i, "codes");
1198
+ clusterSizes[ci] = (clusterSizes[ci] ?? 0) + 1;
1199
+ }
1200
+ const sumsToClusters = new Float64Array(k);
1201
+ let sum = 0;
1202
+ let comp = 0;
1203
+ for (let i = 0; i < n; i++) {
1204
+ const ci = readIndex(codes, i, "codes");
1205
+ const sizeOwn = readIndex(clusterSizes, ci, "clusterSizes");
1206
+ if (sizeOwn <= 1) continue;
1207
+ sumsToClusters.fill(0);
1208
+ const srcI = indices ? readIndex(indices, i, "indices") : i;
1209
+ for (let j = 0; j < n; j++) {
1210
+ if (i === j) continue;
1211
+ const srcJ = indices ? readIndex(indices, j, "indices") : j;
1212
+ const cj = readIndex(codes, j, "codes");
1213
+ const d = euclideanDistance(data, offset, sampleStride, featureStride, nFeatures, srcI, srcJ);
1214
+ sumsToClusters[cj] = (sumsToClusters[cj] ?? 0) + d;
1215
+ }
1216
+ const a = readIndex(sumsToClusters, ci, "sumsToClusters") / (sizeOwn - 1);
1217
+ let b = Infinity;
1218
+ for (let cl = 0; cl < k; cl++) {
1219
+ if (cl === ci) continue;
1220
+ const sz = readIndex(clusterSizes, cl, "clusterSizes");
1221
+ if (sz <= 0) continue;
1222
+ const mean = readIndex(sumsToClusters, cl, "sumsToClusters") / sz;
1223
+ if (mean < b) b = mean;
1224
+ }
1225
+ if (!Number.isFinite(b) || b === Infinity) continue;
1226
+ const denom = Math.max(a, b);
1227
+ const s = denom > 0 ? (b - a) / denom : 0;
1228
+ const y = s - comp;
1229
+ const t = sum + y;
1230
+ comp = t - sum - y;
1231
+ sum = t;
1232
+ }
1233
+ return sum / n;
1234
+ }
1235
+ function silhouetteMeanPrecomputed(X, labels, indices) {
1236
+ const nSamples = labels.size;
1237
+ if (nSamples < 2) {
1238
+ throw new InvalidParameterError(
1239
+ "silhouette requires at least 2 samples",
1240
+ "n_samples",
1241
+ nSamples
1242
+ );
1243
+ }
1244
+ const encAll = validateSilhouetteLabels(labels, nSamples);
1245
+ const dist = getPrecomputedDistanceAccessor(X, nSamples);
1246
+ const n = indices ? indices.length : nSamples;
1247
+ if (n < 2) {
1248
+ throw new InvalidParameterError("silhouette requires at least 2 samples", "n_samples", n);
1249
+ }
1250
+ for (let i = 0; i < n; i++) {
1251
+ const src = indices ? readIndex(indices, i, "indices") : i;
1252
+ const d0 = dist(src, src);
1253
+ if (!Number.isFinite(d0) || Math.abs(d0) > 1e-12) {
1254
+ throw new DataValidationError(
1255
+ `Precomputed distance matrix diagonal must be ~0; found ${String(d0)} at [${src},${src}]`
1256
+ );
1257
+ }
1258
+ }
1259
+ const subsetCodesRaw = new Int32Array(n);
1260
+ for (let i = 0; i < n; i++) {
1261
+ const src = indices ? readIndex(indices, i, "indices") : i;
1262
+ const c = readIndex(encAll.codes, src, "labels.codes");
1263
+ subsetCodesRaw[i] = c;
1264
+ }
1265
+ const re = reencodeSubset(subsetCodesRaw);
1266
+ const codes = re.codes;
1267
+ const k = re.nClusters;
1268
+ if (k < 2 || k > n - 1) {
1269
+ throw new InvalidParameterError(
1270
+ "silhouette requires 2 <= n_clusters <= n_samples - 1",
1271
+ "n_clusters",
1272
+ k
1273
+ );
1274
+ }
1275
+ const clusterSizes = new Int32Array(k);
1276
+ for (let i = 0; i < n; i++) {
1277
+ const ci = readIndex(codes, i, "codes");
1278
+ clusterSizes[ci] = (clusterSizes[ci] ?? 0) + 1;
1279
+ }
1280
+ const sumsToClusters = new Float64Array(k);
1281
+ let sum = 0;
1282
+ let comp = 0;
1283
+ for (let i = 0; i < n; i++) {
1284
+ const ci = readIndex(codes, i, "codes");
1285
+ const sizeOwn = readIndex(clusterSizes, ci, "clusterSizes");
1286
+ if (sizeOwn <= 1) continue;
1287
+ sumsToClusters.fill(0);
1288
+ const srcI = indices ? readIndex(indices, i, "indices") : i;
1289
+ for (let j = 0; j < n; j++) {
1290
+ if (i === j) continue;
1291
+ const srcJ = indices ? readIndex(indices, j, "indices") : j;
1292
+ const cj = readIndex(codes, j, "codes");
1293
+ const d = dist(srcI, srcJ);
1294
+ sumsToClusters[cj] = (sumsToClusters[cj] ?? 0) + d;
1295
+ }
1296
+ const a = readIndex(sumsToClusters, ci, "sumsToClusters") / (sizeOwn - 1);
1297
+ let b = Infinity;
1298
+ for (let cl = 0; cl < k; cl++) {
1299
+ if (cl === ci) continue;
1300
+ const sz = readIndex(clusterSizes, cl, "clusterSizes");
1301
+ if (sz <= 0) continue;
1302
+ const mean = readIndex(sumsToClusters, cl, "sumsToClusters") / sz;
1303
+ if (mean < b) b = mean;
1304
+ }
1305
+ if (!Number.isFinite(b) || b === Infinity) continue;
1306
+ const denom = Math.max(a, b);
1307
+ const s = denom > 0 ? (b - a) / denom : 0;
1308
+ const y = s - comp;
1309
+ const t = sum + y;
1310
+ comp = t - sum - y;
1311
+ sum = t;
1312
+ }
1313
+ return sum / n;
1314
+ }
1315
+ function silhouetteScore(X, labels, metric = "euclidean", options) {
1316
+ const sampleSize = options?.sampleSize;
1317
+ const randomState = options?.randomState;
1318
+ const nSamples = labels.size;
1319
+ if (nSamples < 2) {
1320
+ throw new InvalidParameterError(
1321
+ "silhouette requires at least 2 samples",
1322
+ "n_samples",
1323
+ nSamples
1324
+ );
1325
+ }
1326
+ const maxFull = 2e3;
1327
+ if (sampleSize === void 0 && nSamples > maxFull) {
1328
+ throw new InvalidParameterError(
1329
+ `silhouetteScore is O(n\xB2) and n_samples=${nSamples} is too large for full computation; provide options.sampleSize`,
1330
+ "sampleSize",
1331
+ sampleSize
1332
+ );
1333
+ }
1334
+ if (sampleSize !== void 0) {
1335
+ if (!Number.isFinite(sampleSize) || !Number.isInteger(sampleSize)) {
1336
+ throw new InvalidParameterError("sampleSize must be an integer", "sampleSize", sampleSize);
1337
+ }
1338
+ if (sampleSize < 2 || sampleSize > nSamples) {
1339
+ throw new InvalidParameterError(
1340
+ "sampleSize must satisfy 2 <= sampleSize <= n_samples",
1341
+ "sampleSize",
1342
+ sampleSize
1343
+ );
1344
+ }
1345
+ }
1346
+ const indices = sampleSize !== void 0 && sampleSize < nSamples ? reservoirSampleIndices(nSamples, sampleSize, randomState) : null;
1347
+ if (metric === "euclidean") return silhouetteMeanEuclidean(X, labels, indices);
1348
+ if (metric === "precomputed") return silhouetteMeanPrecomputed(X, labels, indices);
1349
+ throw new InvalidParameterError(
1350
+ `Unsupported metric: '${String(metric)}'. Must be 'euclidean' or 'precomputed'`,
1351
+ "metric",
1352
+ metric
1353
+ );
1354
+ }
1355
+ function silhouetteSamples(X, labels, metric = "euclidean") {
1356
+ const nSamples = labels.size;
1357
+ if (nSamples < 2) {
1358
+ throw new InvalidParameterError(
1359
+ "silhouette requires at least 2 samples",
1360
+ "n_samples",
1361
+ nSamples
1362
+ );
1363
+ }
1364
+ const enc = validateSilhouetteLabels(labels, nSamples);
1365
+ const codes = enc.codes;
1366
+ const k = enc.nClusters;
1367
+ const silhouettes = new Float64Array(nSamples);
1368
+ const clusterSizes = new Int32Array(k);
1369
+ for (let i = 0; i < nSamples; i++) {
1370
+ const ci = readIndex(codes, i, "codes");
1371
+ clusterSizes[ci] = (clusterSizes[ci] ?? 0) + 1;
1372
+ }
1373
+ const sumsToClusters = new Float64Array(k);
1374
+ if (metric === "euclidean") {
1375
+ const { data, nFeatures, sampleStride, featureStride, offset } = getFeatureAccessor(X);
1376
+ for (let i = 0; i < nSamples; i++) {
1377
+ const ci = readIndex(codes, i, "codes");
1378
+ const sizeOwn = readIndex(clusterSizes, ci, "clusterSizes");
1379
+ if (sizeOwn <= 1) {
1380
+ silhouettes[i] = 0;
1381
+ continue;
1382
+ }
1383
+ sumsToClusters.fill(0);
1384
+ for (let j = 0; j < nSamples; j++) {
1385
+ if (i === j) continue;
1386
+ const cj = readIndex(codes, j, "codes");
1387
+ const d = euclideanDistance(data, offset, sampleStride, featureStride, nFeatures, i, j);
1388
+ sumsToClusters[cj] = (sumsToClusters[cj] ?? 0) + d;
1389
+ }
1390
+ const a = readIndex(sumsToClusters, ci, "sumsToClusters") / (sizeOwn - 1);
1391
+ let b = Infinity;
1392
+ for (let cl = 0; cl < k; cl++) {
1393
+ if (cl === ci) continue;
1394
+ const sz = readIndex(clusterSizes, cl, "clusterSizes");
1395
+ if (sz <= 0) continue;
1396
+ const mean = readIndex(sumsToClusters, cl, "sumsToClusters") / sz;
1397
+ if (mean < b) b = mean;
1398
+ }
1399
+ if (!Number.isFinite(b) || b === Infinity) {
1400
+ silhouettes[i] = 0;
1401
+ continue;
1402
+ }
1403
+ const denom = Math.max(a, b);
1404
+ silhouettes[i] = denom > 0 ? (b - a) / denom : 0;
1405
+ }
1406
+ return tensor(silhouettes);
1407
+ }
1408
+ if (metric === "precomputed") {
1409
+ const dist = getPrecomputedDistanceAccessor(X, nSamples);
1410
+ for (let i = 0; i < nSamples; i++) {
1411
+ const d0 = dist(i, i);
1412
+ if (!Number.isFinite(d0) || Math.abs(d0) > 1e-12) {
1413
+ throw new DataValidationError(
1414
+ `Precomputed distance matrix diagonal must be ~0; found ${String(d0)} at [${i},${i}]`
1415
+ );
1416
+ }
1417
+ }
1418
+ for (let i = 0; i < nSamples; i++) {
1419
+ const ci = readIndex(codes, i, "codes");
1420
+ const sizeOwn = readIndex(clusterSizes, ci, "clusterSizes");
1421
+ if (sizeOwn <= 1) {
1422
+ silhouettes[i] = 0;
1423
+ continue;
1424
+ }
1425
+ sumsToClusters.fill(0);
1426
+ for (let j = 0; j < nSamples; j++) {
1427
+ if (i === j) continue;
1428
+ const cj = readIndex(codes, j, "codes");
1429
+ const d = dist(i, j);
1430
+ sumsToClusters[cj] = (sumsToClusters[cj] ?? 0) + d;
1431
+ }
1432
+ const a = readIndex(sumsToClusters, ci, "sumsToClusters") / (sizeOwn - 1);
1433
+ let b = Infinity;
1434
+ for (let cl = 0; cl < k; cl++) {
1435
+ if (cl === ci) continue;
1436
+ const sz = readIndex(clusterSizes, cl, "clusterSizes");
1437
+ if (sz <= 0) continue;
1438
+ const mean = readIndex(sumsToClusters, cl, "sumsToClusters") / sz;
1439
+ if (mean < b) b = mean;
1440
+ }
1441
+ if (!Number.isFinite(b) || b === Infinity) {
1442
+ silhouettes[i] = 0;
1443
+ continue;
1444
+ }
1445
+ const denom = Math.max(a, b);
1446
+ silhouettes[i] = denom > 0 ? (b - a) / denom : 0;
1447
+ }
1448
+ return tensor(silhouettes);
1449
+ }
1450
+ throw new InvalidParameterError(
1451
+ `Unsupported metric: '${String(metric)}'. Must be 'euclidean' or 'precomputed'`,
1452
+ "metric",
1453
+ metric
1454
+ );
1455
+ }
1456
+ function daviesBouldinScore(X, labels) {
1457
+ const { data, nSamples, nFeatures, sampleStride, featureStride, offset } = getFeatureAccessor(X);
1458
+ if (nSamples === 0) return 0;
1459
+ if (labels.size !== nSamples) {
1460
+ throw new ShapeError("labels length must match number of samples");
1461
+ }
1462
+ const enc = encodeLabels(labels, "labels");
1463
+ const codes = enc.codes;
1464
+ const k = enc.nClusters;
1465
+ if (k < 2) return 0;
1466
+ const centroids = new Array(k);
1467
+ for (let c = 0; c < k; c++) centroids[c] = new Float64Array(nFeatures);
1468
+ const clusterSizes = new Int32Array(k);
1469
+ for (let i = 0; i < nSamples; i++) {
1470
+ const c = readIndex(codes, i, "codes");
1471
+ clusterSizes[c] = (clusterSizes[c] ?? 0) + 1;
1472
+ const base = offset + i * sampleStride;
1473
+ const centroid = readIndex(centroids, c, "centroids");
1474
+ for (let f = 0; f < nFeatures; f++) {
1475
+ const v = getNumericElement(data, base + f * featureStride);
1476
+ assertFiniteNumber(v, "X", `sample ${i}, feature ${f}`);
1477
+ centroid[f] = (centroid[f] ?? 0) + v;
1478
+ }
1479
+ }
1480
+ for (let c = 0; c < k; c++) {
1481
+ const sz = readIndex(clusterSizes, c, "clusterSizes");
1482
+ if (sz <= 0) continue;
1483
+ const centroid = readIndex(centroids, c, "centroids");
1484
+ for (let f = 0; f < nFeatures; f++) {
1485
+ centroid[f] = (centroid[f] ?? 0) / sz;
1486
+ }
1487
+ }
1488
+ const scatterSum = new Float64Array(k);
1489
+ for (let i = 0; i < nSamples; i++) {
1490
+ const c = readIndex(codes, i, "codes");
1491
+ const centroid = readIndex(centroids, c, "centroids");
1492
+ let distSq = 0;
1493
+ const base = offset + i * sampleStride;
1494
+ for (let f = 0; f < nFeatures; f++) {
1495
+ const v = getNumericElement(data, base + f * featureStride);
1496
+ assertFiniteNumber(v, "X", `sample ${i}, feature ${f}`);
1497
+ const d = v - (centroid[f] ?? 0);
1498
+ distSq += d * d;
1499
+ }
1500
+ scatterSum[c] = (scatterSum[c] ?? 0) + Math.sqrt(distSq);
1501
+ }
1502
+ const S = new Float64Array(k);
1503
+ for (let c = 0; c < k; c++) {
1504
+ const sz = readIndex(clusterSizes, c, "clusterSizes");
1505
+ const sc = readIndex(scatterSum, c, "scatterSum");
1506
+ S[c] = sz > 0 ? sc / sz : 0;
1507
+ }
1508
+ let db = 0;
1509
+ for (let i = 0; i < k; i++) {
1510
+ let maxRatio = Number.NEGATIVE_INFINITY;
1511
+ const ci = readIndex(centroids, i, "centroids");
1512
+ for (let j = 0; j < k; j++) {
1513
+ if (i === j) continue;
1514
+ const cj = readIndex(centroids, j, "centroids");
1515
+ let distSq = 0;
1516
+ for (let f = 0; f < nFeatures; f++) {
1517
+ const d = (ci[f] ?? 0) - (cj[f] ?? 0);
1518
+ distSq += d * d;
1519
+ }
1520
+ const dist = Math.sqrt(distSq);
1521
+ const si = readIndex(S, i, "S");
1522
+ const sj = readIndex(S, j, "S");
1523
+ const ratio = dist === 0 ? Number.POSITIVE_INFINITY : (si + sj) / dist;
1524
+ if (ratio > maxRatio) maxRatio = ratio;
1525
+ }
1526
+ db += maxRatio;
1527
+ }
1528
+ return db / k;
1529
+ }
1530
+ function calinskiHarabaszScore(X, labels) {
1531
+ const { data, nSamples, nFeatures, sampleStride, featureStride, offset } = getFeatureAccessor(X);
1532
+ if (nSamples === 0) return 0;
1533
+ if (labels.size !== nSamples) {
1534
+ throw new ShapeError("labels length must match number of samples");
1535
+ }
1536
+ const enc = encodeLabels(labels, "labels");
1537
+ const codes = enc.codes;
1538
+ const k = enc.nClusters;
1539
+ const overallMean = new Float64Array(nFeatures);
1540
+ for (let i = 0; i < nSamples; i++) {
1541
+ const base = offset + i * sampleStride;
1542
+ for (let f = 0; f < nFeatures; f++) {
1543
+ const v = getNumericElement(data, base + f * featureStride);
1544
+ assertFiniteNumber(v, "X", `sample ${i}, feature ${f}`);
1545
+ overallMean[f] = (overallMean[f] ?? 0) + v;
1546
+ }
1547
+ }
1548
+ for (let f = 0; f < nFeatures; f++) {
1549
+ overallMean[f] = (overallMean[f] ?? 0) / nSamples;
1550
+ }
1551
+ const centroids = new Array(k);
1552
+ for (let c = 0; c < k; c++) centroids[c] = new Float64Array(nFeatures);
1553
+ const clusterSizes = new Int32Array(k);
1554
+ for (let i = 0; i < nSamples; i++) {
1555
+ const c = readIndex(codes, i, "codes");
1556
+ clusterSizes[c] = (clusterSizes[c] ?? 0) + 1;
1557
+ const base = offset + i * sampleStride;
1558
+ const centroid = readIndex(centroids, c, "centroids");
1559
+ for (let f = 0; f < nFeatures; f++) {
1560
+ const v = getNumericElement(data, base + f * featureStride);
1561
+ assertFiniteNumber(v, "X", `sample ${i}, feature ${f}`);
1562
+ centroid[f] = (centroid[f] ?? 0) + v;
1563
+ }
1564
+ }
1565
+ for (let c = 0; c < k; c++) {
1566
+ const sz = readIndex(clusterSizes, c, "clusterSizes");
1567
+ if (sz <= 0) continue;
1568
+ const centroid = readIndex(centroids, c, "centroids");
1569
+ for (let f = 0; f < nFeatures; f++) {
1570
+ centroid[f] = (centroid[f] ?? 0) / sz;
1571
+ }
1572
+ }
1573
+ let bgss = 0;
1574
+ for (let c = 0; c < k; c++) {
1575
+ const sz = readIndex(clusterSizes, c, "clusterSizes");
1576
+ if (sz <= 0) continue;
1577
+ const centroid = readIndex(centroids, c, "centroids");
1578
+ let distSq = 0;
1579
+ for (let f = 0; f < nFeatures; f++) {
1580
+ const d = (centroid[f] ?? 0) - (overallMean[f] ?? 0);
1581
+ distSq += d * d;
1582
+ }
1583
+ bgss += sz * distSq;
1584
+ }
1585
+ let wgss = 0;
1586
+ for (let i = 0; i < nSamples; i++) {
1587
+ const c = readIndex(codes, i, "codes");
1588
+ const centroid = readIndex(centroids, c, "centroids");
1589
+ const base = offset + i * sampleStride;
1590
+ for (let f = 0; f < nFeatures; f++) {
1591
+ const v = getNumericElement(data, base + f * featureStride);
1592
+ assertFiniteNumber(v, "X", `sample ${i}, feature ${f}`);
1593
+ const d = v - (centroid[f] ?? 0);
1594
+ wgss += d * d;
1595
+ }
1596
+ }
1597
+ if (k < 2 || wgss === 0) return 0;
1598
+ return bgss / (k - 1) / (wgss / (nSamples - k));
1599
+ }
1600
+ function adjustedRandScore(labelsTrue, labelsPred) {
1601
+ assertSameSize(labelsTrue, labelsPred, "labelsTrue", "labelsPred");
1602
+ const n = labelsTrue.size;
1603
+ if (n <= 1) return 1;
1604
+ const stats = buildContingencyStats(labelsTrue, labelsPred);
1605
+ const { contingencyDense, contingencySparse, trueCount, predCount } = stats;
1606
+ let sumComb = 0;
1607
+ if (contingencyDense) {
1608
+ for (let idx = 0; idx < contingencyDense.length; idx++) {
1609
+ const nij = readIndex(contingencyDense, idx, "contingencyDense");
1610
+ if (nij > 0) sumComb += comb2(nij);
1611
+ }
1612
+ } else if (contingencySparse) {
1613
+ for (const nij of contingencySparse.values()) {
1614
+ sumComb += comb2(nij);
1615
+ }
1616
+ }
1617
+ let sumCombTrue = 0;
1618
+ for (let i = 0; i < trueCount.length; i++) {
1619
+ sumCombTrue += comb2(readIndex(trueCount, i, "trueCount"));
1620
+ }
1621
+ let sumCombPred = 0;
1622
+ for (let j = 0; j < predCount.length; j++) {
1623
+ sumCombPred += comb2(readIndex(predCount, j, "predCount"));
1624
+ }
1625
+ const totalPairs = comb2(n);
1626
+ if (totalPairs === 0) return 1;
1627
+ const expectedIndex = sumCombTrue * sumCombPred / totalPairs;
1628
+ const maxIndex = (sumCombTrue + sumCombPred) / 2;
1629
+ const denom = maxIndex - expectedIndex;
1630
+ if (denom === 0) return 1;
1631
+ return (sumComb - expectedIndex) / denom;
1632
+ }
1633
+ function adjustedMutualInfoScore(labelsTrue, labelsPred, averageMethod = "arithmetic") {
1634
+ const stats = buildContingencyStats(labelsTrue, labelsPred);
1635
+ const { n, trueCount, predCount } = stats;
1636
+ if (n <= 1) return 1;
1637
+ const mi = mutualInformationFromContingency(stats);
1638
+ const hTrue = entropyFromCountArray(trueCount, n);
1639
+ const hPred = entropyFromCountArray(predCount, n);
1640
+ const emi = expectedMutualInformation(stats);
1641
+ const normalizer = averageEntropy(hTrue, hPred, averageMethod);
1642
+ if (Math.abs(normalizer) < 1e-15) return 1;
1643
+ const denom = normalizer - emi;
1644
+ if (Math.abs(denom) < 1e-15) return 0;
1645
+ const ami = (mi - emi) / denom;
1646
+ if (!Number.isFinite(ami)) return 0;
1647
+ if (ami > 1) return 1;
1648
+ if (ami < -1) return -1;
1649
+ return ami;
1650
+ }
1651
+ function normalizedMutualInfoScore(labelsTrue, labelsPred, averageMethod = "arithmetic") {
1652
+ const stats = buildContingencyStats(labelsTrue, labelsPred);
1653
+ const { n, trueCount, predCount } = stats;
1654
+ const mi = mutualInformationFromContingency(stats);
1655
+ const ht = entropyFromCountArray(trueCount, n);
1656
+ const hp = entropyFromCountArray(predCount, n);
1657
+ if (ht === 0 || hp === 0) {
1658
+ return ht === 0 && hp === 0 ? 1 : 0;
1659
+ }
1660
+ const normalizer = averageEntropy(ht, hp, averageMethod);
1661
+ if (normalizer === 0) return 0;
1662
+ const nmi = mi / normalizer;
1663
+ if (nmi > 1) return 1;
1664
+ if (nmi < 0) return 0;
1665
+ return nmi;
1666
+ }
1667
+ function fowlkesMallowsScore(labelsTrue, labelsPred) {
1668
+ const stats = buildContingencyStats(labelsTrue, labelsPred);
1669
+ const { contingencyDense, contingencySparse, trueCount, predCount, n } = stats;
1670
+ if (n === 0) return 1;
1671
+ let tk = 0;
1672
+ if (contingencyDense) {
1673
+ for (let idx = 0; idx < contingencyDense.length; idx++) {
1674
+ const nij = readIndex(contingencyDense, idx, "contingencyDense");
1675
+ if (nij > 0) tk += comb2(nij);
1676
+ }
1677
+ } else if (contingencySparse) {
1678
+ for (const nij of contingencySparse.values()) {
1679
+ tk += comb2(nij);
1680
+ }
1681
+ }
1682
+ let pk = 0;
1683
+ for (let i = 0; i < trueCount.length; i++) pk += comb2(readIndex(trueCount, i, "trueCount"));
1684
+ let qk = 0;
1685
+ for (let j = 0; j < predCount.length; j++) qk += comb2(readIndex(predCount, j, "predCount"));
1686
+ if (pk === 0 || qk === 0) return 0;
1687
+ return tk / Math.sqrt(pk * qk);
1688
+ }
1689
+ function homogeneityScore(labelsTrue, labelsPred) {
1690
+ const stats = buildContingencyStats(labelsTrue, labelsPred);
1691
+ const { contingencyDense, contingencySparse, predCount, trueCount, nPred, n } = stats;
1692
+ if (n === 0) return 1;
1693
+ let hck = 0;
1694
+ if (contingencyDense) {
1695
+ for (let idx = 0; idx < contingencyDense.length; idx++) {
1696
+ const nij = readIndex(contingencyDense, idx, "contingencyDense");
1697
+ if (nij <= 0) continue;
1698
+ const p = idx - Math.floor(idx / nPred) * nPred;
1699
+ const nj = readIndex(predCount, p, "predCount");
1700
+ if (nj > 0) hck -= nij / n * Math.log(nij / nj);
1701
+ }
1702
+ } else if (contingencySparse) {
1703
+ for (const [key, nij] of contingencySparse) {
1704
+ if (nij <= 0) continue;
1705
+ const p = key - Math.floor(key / nPred) * nPred;
1706
+ const nj = readIndex(predCount, p, "predCount");
1707
+ if (nj > 0) hck -= nij / n * Math.log(nij / nj);
1708
+ }
1709
+ }
1710
+ const hc = entropyFromCountArray(trueCount, n);
1711
+ return hc === 0 ? 1 : 1 - hck / hc;
1712
+ }
1713
+ function completenessScore(labelsTrue, labelsPred) {
1714
+ const stats = buildContingencyStats(labelsTrue, labelsPred);
1715
+ const { contingencyDense, contingencySparse, trueCount, predCount, nPred, n } = stats;
1716
+ if (n === 0) return 1;
1717
+ let hkc = 0;
1718
+ if (contingencyDense) {
1719
+ for (let idx = 0; idx < contingencyDense.length; idx++) {
1720
+ const nij = readIndex(contingencyDense, idx, "contingencyDense");
1721
+ if (nij <= 0) continue;
1722
+ const t = Math.floor(idx / nPred);
1723
+ const ni = readIndex(trueCount, t, "trueCount");
1724
+ if (ni > 0) hkc -= nij / n * Math.log(nij / ni);
1725
+ }
1726
+ } else if (contingencySparse) {
1727
+ for (const [key, nij] of contingencySparse) {
1728
+ if (nij <= 0) continue;
1729
+ const t = Math.floor(key / nPred);
1730
+ const ni = readIndex(trueCount, t, "trueCount");
1731
+ if (ni > 0) hkc -= nij / n * Math.log(nij / ni);
1732
+ }
1733
+ }
1734
+ const hk = entropyFromCountArray(predCount, n);
1735
+ return hk === 0 ? 1 : 1 - hkc / hk;
1736
+ }
1737
+ function vMeasureScore(labelsTrue, labelsPred, beta = 1) {
1738
+ if (!Number.isFinite(beta) || beta <= 0) {
1739
+ throw new InvalidParameterError("beta must be a positive finite number", "beta", beta);
1740
+ }
1741
+ const h = homogeneityScore(labelsTrue, labelsPred);
1742
+ const c = completenessScore(labelsTrue, labelsPred);
1743
+ if (h + c === 0) return 0;
1744
+ return (1 + beta) * h * c / (beta * h + c);
1745
+ }
1746
+
1747
+ // src/metrics/regression.ts
1748
+ function getNumericRegressionData(t, name) {
1749
+ if (t.dtype === "string") {
1750
+ throw new DTypeError(`${name} must be numeric tensors`);
1751
+ }
1752
+ if (t.dtype === "int64") {
1753
+ throw new DTypeError(`${name} must be numeric tensors (int64 not supported)`);
1754
+ }
1755
+ const data = t.data;
1756
+ if (!isTypedArray(data) || !isNumericTypedArray(data)) {
1757
+ throw new DTypeError(`${name} must be numeric tensors`);
1758
+ }
1759
+ return data;
1760
+ }
1761
+ function readNumeric(data, offsetter, index, name) {
1762
+ const value = getNumericElement(data, offsetter(index));
1763
+ assertFiniteNumber(value, name, `index ${index}`);
1764
+ return value;
1765
+ }
1766
+ function mse(yTrue, yPred) {
1767
+ assertSameSizeVectors(yTrue, yPred, "yTrue", "yPred");
1768
+ const yTrueData = getNumericRegressionData(yTrue, "yTrue");
1769
+ const yPredData = getNumericRegressionData(yPred, "yPred");
1770
+ if (yTrue.size === 0) return 0;
1771
+ const trueOffset = createFlatOffsetter(yTrue);
1772
+ const predOffset = createFlatOffsetter(yPred);
1773
+ let sumSquaredError = 0;
1774
+ for (let i = 0; i < yTrue.size; i++) {
1775
+ const diff = readNumeric(yTrueData, trueOffset, i, "yTrue") - readNumeric(yPredData, predOffset, i, "yPred");
1776
+ sumSquaredError += diff * diff;
1777
+ }
1778
+ return sumSquaredError / yTrue.size;
1779
+ }
1780
+ function rmse(yTrue, yPred) {
1781
+ return Math.sqrt(mse(yTrue, yPred));
1782
+ }
1783
+ function mae(yTrue, yPred) {
1784
+ assertSameSizeVectors(yTrue, yPred, "yTrue", "yPred");
1785
+ const yTrueData = getNumericRegressionData(yTrue, "yTrue");
1786
+ const yPredData = getNumericRegressionData(yPred, "yPred");
1787
+ if (yTrue.size === 0) return 0;
1788
+ const trueOffset = createFlatOffsetter(yTrue);
1789
+ const predOffset = createFlatOffsetter(yPred);
1790
+ let sumAbsError = 0;
1791
+ for (let i = 0; i < yTrue.size; i++) {
1792
+ const diff = readNumeric(yTrueData, trueOffset, i, "yTrue") - readNumeric(yPredData, predOffset, i, "yPred");
1793
+ sumAbsError += Math.abs(diff);
1794
+ }
1795
+ return sumAbsError / yTrue.size;
1796
+ }
1797
+ function r2Score(yTrue, yPred) {
1798
+ assertSameSizeVectors(yTrue, yPred, "yTrue", "yPred");
1799
+ const yTrueData = getNumericRegressionData(yTrue, "yTrue");
1800
+ const yPredData = getNumericRegressionData(yPred, "yPred");
1801
+ if (yTrue.size === 0) {
1802
+ throw new InvalidParameterError("r2Score requires at least one sample", "yTrue", yTrue.size);
1803
+ }
1804
+ const trueOffset = createFlatOffsetter(yTrue);
1805
+ const predOffset = createFlatOffsetter(yPred);
1806
+ let sumTrue = 0;
1807
+ for (let i = 0; i < yTrue.size; i++) {
1808
+ sumTrue += readNumeric(yTrueData, trueOffset, i, "yTrue");
1809
+ }
1810
+ const mean = sumTrue / yTrue.size;
1811
+ let ssRes = 0;
1812
+ let ssTot = 0;
1813
+ for (let i = 0; i < yTrue.size; i++) {
1814
+ const trueVal = readNumeric(yTrueData, trueOffset, i, "yTrue");
1815
+ const predVal = readNumeric(yPredData, predOffset, i, "yPred");
1816
+ ssRes += (trueVal - predVal) ** 2;
1817
+ ssTot += (trueVal - mean) ** 2;
1818
+ }
1819
+ if (ssTot === 0) {
1820
+ return ssRes === 0 ? 1 : 0;
1821
+ }
1822
+ return 1 - ssRes / ssTot;
1823
+ }
1824
+ function adjustedR2Score(yTrue, yPred, nFeatures) {
1825
+ if (!Number.isFinite(nFeatures) || !Number.isInteger(nFeatures) || nFeatures < 0) {
1826
+ throw new InvalidParameterError(
1827
+ "nFeatures must be a non-negative integer",
1828
+ "nFeatures",
1829
+ nFeatures
1830
+ );
1831
+ }
1832
+ assertSameSizeVectors(yTrue, yPred, "yTrue", "yPred");
1833
+ const n = yTrue.size;
1834
+ const p = nFeatures;
1835
+ if (n <= p + 1) {
1836
+ throw new InvalidParameterError(
1837
+ `Adjusted R\xB2 requires n > p + 1 (samples > features + 1). Got n=${n}, p=${p}`,
1838
+ "nFeatures",
1839
+ nFeatures
1840
+ );
1841
+ }
1842
+ const r2 = r2Score(yTrue, yPred);
1843
+ return 1 - (1 - r2) * (n - 1) / (n - p - 1);
1844
+ }
1845
+ function mape(yTrue, yPred) {
1846
+ assertSameSizeVectors(yTrue, yPred, "yTrue", "yPred");
1847
+ const yTrueData = getNumericRegressionData(yTrue, "yTrue");
1848
+ const yPredData = getNumericRegressionData(yPred, "yPred");
1849
+ if (yTrue.size === 0) return 0;
1850
+ const trueOffset = createFlatOffsetter(yTrue);
1851
+ const predOffset = createFlatOffsetter(yPred);
1852
+ let sumPercentError = 0;
1853
+ let nonZeroCount = 0;
1854
+ for (let i = 0; i < yTrue.size; i++) {
1855
+ const trueVal = readNumeric(yTrueData, trueOffset, i, "yTrue");
1856
+ const predVal = readNumeric(yPredData, predOffset, i, "yPred");
1857
+ if (trueVal !== 0) {
1858
+ sumPercentError += Math.abs((trueVal - predVal) / trueVal);
1859
+ nonZeroCount++;
1860
+ }
1861
+ }
1862
+ if (nonZeroCount === 0) {
1863
+ return 0;
1864
+ }
1865
+ return sumPercentError / nonZeroCount * 100;
1866
+ }
1867
+ function medianAbsoluteError(yTrue, yPred) {
1868
+ assertSameSizeVectors(yTrue, yPred, "yTrue", "yPred");
1869
+ const yTrueData = getNumericRegressionData(yTrue, "yTrue");
1870
+ const yPredData = getNumericRegressionData(yPred, "yPred");
1871
+ if (yTrue.size === 0) return 0;
1872
+ const trueOffset = createFlatOffsetter(yTrue);
1873
+ const predOffset = createFlatOffsetter(yPred);
1874
+ const errors = [];
1875
+ for (let i = 0; i < yTrue.size; i++) {
1876
+ const diff = Math.abs(
1877
+ readNumeric(yTrueData, trueOffset, i, "yTrue") - readNumeric(yPredData, predOffset, i, "yPred")
1878
+ );
1879
+ errors.push(diff);
1880
+ }
1881
+ errors.sort((a, b) => a - b);
1882
+ const mid = Math.floor(errors.length / 2);
1883
+ return errors.length % 2 !== 0 ? errors[mid] ?? 0 : ((errors[mid - 1] ?? 0) + (errors[mid] ?? 0)) / 2;
1884
+ }
1885
+ function maxError(yTrue, yPred) {
1886
+ assertSameSizeVectors(yTrue, yPred, "yTrue", "yPred");
1887
+ const yTrueData = getNumericRegressionData(yTrue, "yTrue");
1888
+ const yPredData = getNumericRegressionData(yPred, "yPred");
1889
+ const trueOffset = createFlatOffsetter(yTrue);
1890
+ const predOffset = createFlatOffsetter(yPred);
1891
+ let maxErr = 0;
1892
+ for (let i = 0; i < yTrue.size; i++) {
1893
+ const diff = Math.abs(
1894
+ readNumeric(yTrueData, trueOffset, i, "yTrue") - readNumeric(yPredData, predOffset, i, "yPred")
1895
+ );
1896
+ maxErr = Math.max(maxErr, diff);
1897
+ }
1898
+ return maxErr;
1899
+ }
1900
+ function explainedVarianceScore(yTrue, yPred) {
1901
+ assertSameSizeVectors(yTrue, yPred, "yTrue", "yPred");
1902
+ const yTrueData = getNumericRegressionData(yTrue, "yTrue");
1903
+ const yPredData = getNumericRegressionData(yPred, "yPred");
1904
+ if (yTrue.size === 0) {
1905
+ throw new InvalidParameterError(
1906
+ "explainedVarianceScore requires at least one sample",
1907
+ "yTrue",
1908
+ yTrue.size
1909
+ );
1910
+ }
1911
+ const trueOffset = createFlatOffsetter(yTrue);
1912
+ const predOffset = createFlatOffsetter(yPred);
1913
+ let sumTrue = 0;
1914
+ let sumResidual = 0;
1915
+ for (let i = 0; i < yTrue.size; i++) {
1916
+ const trueVal = readNumeric(yTrueData, trueOffset, i, "yTrue");
1917
+ const predVal = readNumeric(yPredData, predOffset, i, "yPred");
1918
+ sumTrue += trueVal;
1919
+ sumResidual += trueVal - predVal;
1920
+ }
1921
+ const meanTrue = sumTrue / yTrue.size;
1922
+ const meanResidual = sumResidual / yTrue.size;
1923
+ let varResidual = 0;
1924
+ let varTrue = 0;
1925
+ for (let i = 0; i < yTrue.size; i++) {
1926
+ const trueVal = readNumeric(yTrueData, trueOffset, i, "yTrue");
1927
+ const predVal = readNumeric(yPredData, predOffset, i, "yPred");
1928
+ const residual = trueVal - predVal;
1929
+ varResidual += (residual - meanResidual) ** 2;
1930
+ varTrue += (trueVal - meanTrue) ** 2;
1931
+ }
1932
+ if (varTrue === 0) {
1933
+ return varResidual === 0 ? 1 : 0;
1934
+ }
1935
+ return 1 - varResidual / varTrue;
1936
+ }
1937
+
1938
+ export { accuracy, adjustedMutualInfoScore, adjustedR2Score, adjustedRandScore, averagePrecisionScore, balancedAccuracyScore, calinskiHarabaszScore, classificationReport, cohenKappaScore, completenessScore, confusionMatrix, daviesBouldinScore, explainedVarianceScore, f1Score, fbetaScore, fowlkesMallowsScore, hammingLoss, homogeneityScore, jaccardScore, logLoss, mae, mape, matthewsCorrcoef, maxError, medianAbsoluteError, metrics_exports, mse, normalizedMutualInfoScore, precision, precisionRecallCurve, r2Score, recall, rmse, rocAucScore, rocCurve, silhouetteSamples, silhouetteScore, vMeasureScore };
1939
+ //# sourceMappingURL=chunk-FJYLIGJX.js.map
1940
+ //# sourceMappingURL=chunk-FJYLIGJX.js.map