@mark1russell7/rigor-statistics 0.1.7 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -1
- package/build/index.d.ts +75 -3
- package/build/index.d.ts.map +1 -1
- package/build/index.js +194 -34
- package/build/index.js.map +1 -1
- package/package.json +3 -2
package/build/index.js
CHANGED
|
@@ -533,6 +533,10 @@ function predictScaled(model, inputs) {
|
|
|
533
533
|
for (const term of model.terms) sum += term.weight * product(term.factors, at, inputs);
|
|
534
534
|
return sum + 0;
|
|
535
535
|
}
|
|
536
|
+
/** This function changes a measured value to the scale of a model. A log scale with a floor lifts each smaller value. */
|
|
537
|
+
function toModelScale(value, log, floor) {
|
|
538
|
+
return log ? Math.log10(Math.max(value, floor ?? value)) : value;
|
|
539
|
+
}
|
|
536
540
|
/** This function changes a value from the scale of the model to the scale of the measure. */
|
|
537
541
|
function unscale(model, value) {
|
|
538
542
|
return model.scale === "log" ? 10 ** value : value;
|
|
@@ -581,6 +585,67 @@ function heldOutError(record, measure) {
|
|
|
581
585
|
skill: baselineMae > 0 ? 1 - mae / baselineMae : null
|
|
582
586
|
};
|
|
583
587
|
}
|
|
588
|
+
/** This function gives the stored predictions of a measure in a fold. A fold with no predictions gives `undefined`. */
|
|
589
|
+
function predictionsOf(fold, measure) {
|
|
590
|
+
return fold.predictions !== void 0 && Object.hasOwn(fold.predictions, measure) ? fold.predictions[measure] : void 0;
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* This function gives the held-out error of a chosen set of points, on the scale of the model. It uses the stored
|
|
594
|
+
* predictions of the option `outOfFold`. Thus a reader can get the error of one cell. The result has the fields of
|
|
595
|
+
* `heldOutError`, and the set of all the points gives the same values to the rounding of the sums.
|
|
596
|
+
*
|
|
597
|
+
* Each point counts at most once. A point with no value of the measure, or with no prediction, adds nothing to `n`.
|
|
598
|
+
* The baseline of a point is the mean of the training points of its fold.
|
|
599
|
+
*
|
|
600
|
+
* The function throws a RangeError for an unknown measure and for a record with no predictions of the measure. It
|
|
601
|
+
* also throws for an unknown id, for a repeated id, and for a set with no point to score.
|
|
602
|
+
*/
|
|
603
|
+
function heldOutErrorAt(record, measure, ids) {
|
|
604
|
+
const model = record.models.find((m) => m.measure === measure);
|
|
605
|
+
if (model === void 0) throw new RangeError(`heldOutErrorAt: the record has no model of the measure "${measure}".`);
|
|
606
|
+
if (!record.folds.some((fold) => predictionsOf(fold, measure) !== void 0)) throw new RangeError(`heldOutErrorAt: the record has no predictions of the measure "${measure}". Fit it with the option outOfFold.`);
|
|
607
|
+
const chosen = new Set(ids);
|
|
608
|
+
if (chosen.size !== ids.length) throw new RangeError("heldOutErrorAt: the list of ids has a repeated id.");
|
|
609
|
+
const points = new Map(record.points.map((p) => [p.id, p]));
|
|
610
|
+
for (const id of ids) if (!points.has(id)) throw new RangeError(`heldOutErrorAt: the record has no point "${id}".`);
|
|
611
|
+
const scaled = (point) => {
|
|
612
|
+
const value = point.measures[measure];
|
|
613
|
+
return typeof value === "number" ? toModelScale(value, model.scale === "log", model.floor) : void 0;
|
|
614
|
+
};
|
|
615
|
+
let n = 0;
|
|
616
|
+
let folds = 0;
|
|
617
|
+
let absolute = 0;
|
|
618
|
+
let square = 0;
|
|
619
|
+
let baselineSum = 0;
|
|
620
|
+
for (const fold of record.folds) {
|
|
621
|
+
const predicted = predictionsOf(fold, measure);
|
|
622
|
+
if (predicted === void 0) continue;
|
|
623
|
+
const scored = fold.heldOut.filter((id) => chosen.has(id) && Object.hasOwn(predicted, id));
|
|
624
|
+
if (scored.length === 0) continue;
|
|
625
|
+
const held = new Set(fold.heldOut);
|
|
626
|
+
const baseline = mean(record.points.flatMap((p) => held.has(p.id) || scaled(p) === void 0 ? [] : [scaled(p)]));
|
|
627
|
+
folds += 1;
|
|
628
|
+
for (const id of scored) {
|
|
629
|
+
const y = scaled(points.get(id));
|
|
630
|
+
const error = y - predicted[id];
|
|
631
|
+
n += 1;
|
|
632
|
+
absolute += Math.abs(error);
|
|
633
|
+
square += error * error;
|
|
634
|
+
baselineSum += Math.abs(y - baseline);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
if (n === 0) throw new RangeError(`heldOutErrorAt: no chosen point has a prediction of the measure "${measure}".`);
|
|
638
|
+
const mae = absolute / n;
|
|
639
|
+
const baselineMae = baselineSum / n;
|
|
640
|
+
return {
|
|
641
|
+
n,
|
|
642
|
+
folds,
|
|
643
|
+
mae,
|
|
644
|
+
rmse: Math.sqrt(square / n),
|
|
645
|
+
baselineMae,
|
|
646
|
+
skill: baselineMae > 0 ? 1 - mae / baselineMae : null
|
|
647
|
+
};
|
|
648
|
+
}
|
|
584
649
|
/**
|
|
585
650
|
* This function gives the usual value of a dimension: a measured median, or the most common level among `among`. A
|
|
586
651
|
* tie gives the first level. A dimension with no measured value gives its low end or its first level.
|
|
@@ -695,7 +760,9 @@ function frameOf(name, record, measure, vary, options) {
|
|
|
695
760
|
}
|
|
696
761
|
const others = record.dimensions.filter((d) => !vary.includes(d.id));
|
|
697
762
|
const held = heldValues(name, record, vary, hold, neededLevels(name, varied, vary));
|
|
698
|
-
const
|
|
763
|
+
const placed = record.points.filter((p) => vary.every((id) => p.values[id] !== null) && others.every((d) => matches(d, p.values[d.id], whenHolds(d, p.values) ? held[d.id] : null, tolerance)));
|
|
764
|
+
const matched = placed.filter((p) => typeof p.measures[measure] === "number");
|
|
765
|
+
const missing = placed.filter((p) => typeof p.measures[measure] !== "number");
|
|
699
766
|
const error = heldOutError(record, measure);
|
|
700
767
|
const nearest = nearestOf(record.dimensions, record.points, measure);
|
|
701
768
|
const band = (inputs) => {
|
|
@@ -712,16 +779,21 @@ function frameOf(name, record, measure, vary, options) {
|
|
|
712
779
|
held,
|
|
713
780
|
axes: varied.map((d) => axisValues(d, steps)),
|
|
714
781
|
matched,
|
|
782
|
+
missing,
|
|
715
783
|
error,
|
|
716
784
|
band
|
|
717
785
|
};
|
|
718
786
|
}
|
|
719
787
|
/** This function gives the group field of a measured point of a view. */
|
|
720
788
|
const groupOf = (point) => point.group === void 0 ? {} : { group: point.group };
|
|
789
|
+
/** This function gives the declared reason of a point with no value of a measure, or `null` for no stated reason. */
|
|
790
|
+
const reasonOf = (point, measure) => point.absent !== void 0 && Object.hasOwn(point.absent, measure) ? point.absent[measure] : null;
|
|
721
791
|
/**
|
|
722
792
|
* This function gives a view of a measure over one dimension of a parsed record:
|
|
723
793
|
* - `curve`: the fitted value at each level, or at `steps` values of a continuous dimension, with its band.
|
|
724
794
|
* - `points`: the measured points that match the held values. This list is empty when no point matches them.
|
|
795
|
+
* - `absent`: the points that match the held values and have no value of the measure, each with its reason. A chart
|
|
796
|
+
* can draw each reason apart.
|
|
725
797
|
* - `held`: the value of each other dimension.
|
|
726
798
|
* - `error`: the held-out error.
|
|
727
799
|
*
|
|
@@ -745,7 +817,7 @@ const groupOf = (point) => point.group === void 0 ? {} : { group: point.group };
|
|
|
745
817
|
*/
|
|
746
818
|
function sliceResponse(record, measure, options) {
|
|
747
819
|
const { vary } = options;
|
|
748
|
-
const { held, axes, matched, error, band } = frameOf("sliceResponse", record, measure, [vary], options);
|
|
820
|
+
const { held, axes, matched, missing, error, band } = frameOf("sliceResponse", record, measure, [vary], options);
|
|
749
821
|
return {
|
|
750
822
|
measure,
|
|
751
823
|
vary,
|
|
@@ -763,6 +835,12 @@ function sliceResponse(record, measure, options) {
|
|
|
763
835
|
value: p.measures[measure],
|
|
764
836
|
...groupOf(p)
|
|
765
837
|
})),
|
|
838
|
+
absent: missing.map((p) => ({
|
|
839
|
+
id: p.id,
|
|
840
|
+
at: p.values[vary],
|
|
841
|
+
reason: reasonOf(p, measure),
|
|
842
|
+
...groupOf(p)
|
|
843
|
+
})),
|
|
766
844
|
error
|
|
767
845
|
};
|
|
768
846
|
}
|
|
@@ -775,7 +853,7 @@ function sliceResponse(record, measure, options) {
|
|
|
775
853
|
function surfaceResponse(record, measure, options) {
|
|
776
854
|
const { vary } = options;
|
|
777
855
|
const [xId, yId] = vary;
|
|
778
|
-
const { held, axes, matched, error, band } = frameOf("surfaceResponse", record, measure, vary, options);
|
|
856
|
+
const { held, axes, matched, missing, error, band } = frameOf("surfaceResponse", record, measure, vary, options);
|
|
779
857
|
const x = axes[0];
|
|
780
858
|
const y = axes[1];
|
|
781
859
|
return {
|
|
@@ -795,6 +873,12 @@ function surfaceResponse(record, measure, options) {
|
|
|
795
873
|
value: p.measures[measure],
|
|
796
874
|
...groupOf(p)
|
|
797
875
|
})),
|
|
876
|
+
absent: missing.map((p) => ({
|
|
877
|
+
id: p.id,
|
|
878
|
+
at: [p.values[xId], p.values[yId]],
|
|
879
|
+
reason: reasonOf(p, measure),
|
|
880
|
+
...groupOf(p)
|
|
881
|
+
})),
|
|
798
882
|
error
|
|
799
883
|
};
|
|
800
884
|
}
|
|
@@ -951,13 +1035,30 @@ function checkMeasure(value, path, faults) {
|
|
|
951
1035
|
check(object, "unit", path, isString, "text", faults);
|
|
952
1036
|
check(object, "direction", path, (v) => v === "higher" || v === "lower" || v === "none", `"higher", "lower" or "none"`, faults);
|
|
953
1037
|
}
|
|
954
|
-
/** This function examines one
|
|
955
|
-
function
|
|
1038
|
+
/** This function examines one declared reason. */
|
|
1039
|
+
function checkReason(value, path, faults) {
|
|
1040
|
+
const object = fields(value, path, ["id", "label"], [], faults);
|
|
1041
|
+
if (object === void 0) return;
|
|
1042
|
+
check(object, "id", path, isId, "text with one character or more", faults);
|
|
1043
|
+
check(object, "label", path, isString, "text", faults);
|
|
1044
|
+
}
|
|
1045
|
+
/** This function examines the reasons of a point: each has a declaration, and each is on a measure with no number. */
|
|
1046
|
+
function checkAbsent(value, path, measuresOfPoint, measures, reasons, faults) {
|
|
1047
|
+
const absent = fields(value, path, [], [...measures], faults);
|
|
1048
|
+
if (absent === void 0) return;
|
|
1049
|
+
for (const measure of measures) {
|
|
1050
|
+
if (!Object.hasOwn(absent, measure)) continue;
|
|
1051
|
+
need(reasons.has(absent[measure]), at(path, measure), `the record declares no reason ${shown(absent[measure])}.`, faults);
|
|
1052
|
+
need(typeof peek(measuresOfPoint, measure) !== "number", at(path, measure), "this measure has a number, so it has no reason.", faults);
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
/** This function examines one point, with the dimensions, the measures and the reasons of the record. */
|
|
1056
|
+
function checkPoint(value, path, dimensions, measures, reasons, faults) {
|
|
956
1057
|
const object = fields(value, path, [
|
|
957
1058
|
"id",
|
|
958
1059
|
"values",
|
|
959
1060
|
"measures"
|
|
960
|
-
], ["group"], faults);
|
|
1061
|
+
], ["group", "absent"], faults);
|
|
961
1062
|
if (object === void 0) return;
|
|
962
1063
|
check(object, "id", path, isId, "text with one character or more", faults);
|
|
963
1064
|
check(object, "group", path, isId, "text with one character or more", faults);
|
|
@@ -982,6 +1083,7 @@ function checkPoint(value, path, dimensions, measures, faults) {
|
|
|
982
1083
|
else need(present, at(inside, dimension.id), `this is null when the dimension "${parent}" is ${shown(values[parent])}.`, faults);
|
|
983
1084
|
}
|
|
984
1085
|
}
|
|
1086
|
+
if (Object.hasOwn(object, "absent")) checkAbsent(object["absent"], at(path, "absent"), object["measures"], measures, reasons, faults);
|
|
985
1087
|
if (Object.hasOwn(object, "measures")) {
|
|
986
1088
|
const inside = at(path, "measures");
|
|
987
1089
|
const values = fields(object["measures"], inside, [...measures], [], faults);
|
|
@@ -1003,7 +1105,7 @@ function responseDataFaults(value, required, optional) {
|
|
|
1003
1105
|
"measures",
|
|
1004
1106
|
"points",
|
|
1005
1107
|
...required
|
|
1006
|
-
], optional, faults);
|
|
1108
|
+
], ["reasons", ...optional], faults);
|
|
1007
1109
|
if (object === void 0) return faults;
|
|
1008
1110
|
check(object, "schema", "", (v) => v === RESPONSE_RECORDS_SCHEMA, `"${RESPONSE_RECORDS_SCHEMA}"`, faults);
|
|
1009
1111
|
check(object, "name", "", isId, "text with one character or more", faults);
|
|
@@ -1016,9 +1118,12 @@ function responseDataFaults(value, required, optional) {
|
|
|
1016
1118
|
const measures = Object.hasOwn(object, "measures") ? list(object["measures"], "measures", 1, faults) : [];
|
|
1017
1119
|
measures.forEach((m, i) => checkMeasure(m, `measures[${i}]`, faults));
|
|
1018
1120
|
const measureIds = uniqueIds(measures, "measures", faults);
|
|
1121
|
+
const reasons = Object.hasOwn(object, "reasons") ? list(object["reasons"], "reasons", 0, faults) : [];
|
|
1122
|
+
reasons.forEach((r, i) => checkReason(r, `reasons[${i}]`, faults));
|
|
1123
|
+
const reasonIds = uniqueIds(reasons, "reasons", faults);
|
|
1019
1124
|
if (faults.length > 0) return faults;
|
|
1020
1125
|
const points = list(object["points"], "points", 1, faults);
|
|
1021
|
-
points.forEach((p, i) => checkPoint(p, `points[${i}]`, dimensions, measureIds, faults));
|
|
1126
|
+
points.forEach((p, i) => checkPoint(p, `points[${i}]`, dimensions, measureIds, reasonIds, faults));
|
|
1022
1127
|
uniqueIds(points, "points", faults);
|
|
1023
1128
|
return faults;
|
|
1024
1129
|
}
|
|
@@ -1230,6 +1335,24 @@ function checkFoldError(value, path, count, faults) {
|
|
|
1230
1335
|
"baselineMae"
|
|
1231
1336
|
]) check(object, key, path, isNotNegative, "a finite number of 0 or more", faults);
|
|
1232
1337
|
}
|
|
1338
|
+
/** This function examines the stored predictions of a fold against its held-out points and its errors. */
|
|
1339
|
+
function checkPredictions(value, path, held, errors, measures, faults) {
|
|
1340
|
+
const predictions = fields(value, path, [], measures, faults);
|
|
1341
|
+
if (predictions === void 0) return;
|
|
1342
|
+
for (const measure of measures) {
|
|
1343
|
+
if (!Object.hasOwn(predictions, measure)) continue;
|
|
1344
|
+
const inside = at(path, measure);
|
|
1345
|
+
const stored = fields(predictions[measure], inside, [], Object.keys(peek(predictions, measure) ?? {}), faults);
|
|
1346
|
+
if (stored === void 0) continue;
|
|
1347
|
+
need(peek(errors, measure) !== void 0, inside, "this fold has no error of this measure.", faults);
|
|
1348
|
+
const scored = new Set(held.filter((p) => typeof p.measures[measure] === "number").map((p) => p.id));
|
|
1349
|
+
for (const id of Object.keys(stored)) {
|
|
1350
|
+
need(scored.has(id), at(inside, id), `this fold has no held-out point ${shown(id)} with a value of the measure.`, faults);
|
|
1351
|
+
check(stored, id, inside, isNumber, "a finite number", faults);
|
|
1352
|
+
}
|
|
1353
|
+
for (const id of scored) need(Object.hasOwn(stored, id), at(inside, id), "this held-out point has no prediction.", faults);
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1233
1356
|
/** This function adds the points of a fold to `foldOfPoint`, and it gives them. Each bad id gives a fault. */
|
|
1234
1357
|
function heldPoints(value, path, f, points, foldOfPoint, faults) {
|
|
1235
1358
|
const held = [];
|
|
@@ -1265,15 +1388,18 @@ function checkFolds(value, data, faults) {
|
|
|
1265
1388
|
const sizes = groupSizes(data.points);
|
|
1266
1389
|
const foldOfPoint = /* @__PURE__ */ new Map();
|
|
1267
1390
|
const covered = /* @__PURE__ */ new Set();
|
|
1268
|
-
list(value, "folds", 1, faults)
|
|
1391
|
+
const folds = list(value, "folds", 1, faults);
|
|
1392
|
+
const predicted = measures.filter((m) => folds.some((fold) => peek(peek(fold, "predictions"), m) !== void 0));
|
|
1393
|
+
folds.forEach((item, f) => {
|
|
1269
1394
|
const path = `folds[${f}]`;
|
|
1270
|
-
const object = fields(item, path, ["heldOut", "errors"], [], faults);
|
|
1395
|
+
const object = fields(item, path, ["heldOut", "errors"], ["predictions"], faults);
|
|
1271
1396
|
if (object === void 0) return;
|
|
1272
1397
|
const held = Object.hasOwn(object, "heldOut") ? heldPoints(object["heldOut"], at(path, "heldOut"), f, points, foldOfPoint, faults) : [];
|
|
1273
1398
|
for (const [group, count] of groupSizes(held)) {
|
|
1274
1399
|
const size = sizes.get(group);
|
|
1275
1400
|
need(count === size, at(path, "heldOut"), `the ${group} has ${count} of its ${size} points in this fold, so it is on both sides of the split.`, faults);
|
|
1276
1401
|
}
|
|
1402
|
+
if (Object.hasOwn(object, "predictions")) checkPredictions(object["predictions"], at(path, "predictions"), held, object["errors"], measures, faults);
|
|
1277
1403
|
if (!Object.hasOwn(object, "errors")) return;
|
|
1278
1404
|
const errors = fields(object["errors"], at(path, "errors"), [], measures, faults);
|
|
1279
1405
|
if (errors === void 0) return;
|
|
@@ -1282,6 +1408,7 @@ function checkFolds(value, data, faults) {
|
|
|
1282
1408
|
const count = held.filter((p) => p.measures[measure] !== null).length;
|
|
1283
1409
|
checkFoldError(errors[measure], at(at(path, "errors"), measure), count, faults);
|
|
1284
1410
|
if (count > 0) covered.add(measure);
|
|
1411
|
+
need(!predicted.includes(measure) || peek(object["predictions"], measure) !== void 0, at(path, "predictions"), `another fold has predictions of the measure "${measure}", so this fold needs them too.`, faults);
|
|
1285
1412
|
}
|
|
1286
1413
|
});
|
|
1287
1414
|
for (const measure of measures) need(covered.has(measure), "folds", `no fold has a held-out error of the measure "${measure}".`, faults);
|
|
@@ -1300,6 +1427,15 @@ function checkFolds(value, data, faults) {
|
|
|
1300
1427
|
* - All the points of a group are in one fold, or all are in no fold. Thus no group is on both sides of a split.
|
|
1301
1428
|
* - The count `n` of an error is the number of held-out points with a value of the measure.
|
|
1302
1429
|
* - Each measure has an error in one fold or more.
|
|
1430
|
+
*
|
|
1431
|
+
* These rules apply to the predictions of a fold:
|
|
1432
|
+
* - Each prediction is a finite number of a held-out point of the fold with a value of the measure.
|
|
1433
|
+
* - The fold has a prediction of each such point, and an error of the measure.
|
|
1434
|
+
* - When one fold has predictions of a measure, each fold with an error of the measure has them.
|
|
1435
|
+
*
|
|
1436
|
+
* These rules apply to the reasons of a point:
|
|
1437
|
+
* - Each reason is on a known measure with no number. A measure with `null` and no reason is good.
|
|
1438
|
+
* - Each reason has a declaration in the field `reasons` of the record.
|
|
1303
1439
|
*/
|
|
1304
1440
|
function parseResponseRecord(value) {
|
|
1305
1441
|
try {
|
|
@@ -1433,6 +1569,15 @@ function expansion(dimensions, degree, interactions) {
|
|
|
1433
1569
|
});
|
|
1434
1570
|
return terms;
|
|
1435
1571
|
}
|
|
1572
|
+
/** This function gives the terms that are not 0 at one point or more. A fold gives a term with no point the weight 0. */
|
|
1573
|
+
function seenTerms(terms, dimensions, points) {
|
|
1574
|
+
const inputs = checkedInputs(dimensions, terms);
|
|
1575
|
+
const places = points.map((point) => ({
|
|
1576
|
+
at: coordinates(inputs, point.values),
|
|
1577
|
+
values: point.values
|
|
1578
|
+
}));
|
|
1579
|
+
return terms.filter((term) => places.some(({ at, values }) => product(term, at, values) !== 0));
|
|
1580
|
+
}
|
|
1436
1581
|
/** This function gives the inputs of a ridge fit: one for each dimension of a term. It throws for a bad term. */
|
|
1437
1582
|
function checkedInputs(dimensions, terms) {
|
|
1438
1583
|
const used = new Set(terms.flatMap((t) => t.map((f) => f.dimension)));
|
|
@@ -1550,19 +1695,23 @@ function tableLearner(rows, dimensions) {
|
|
|
1550
1695
|
return (r) => cells.get(keys[r]) ?? fallback;
|
|
1551
1696
|
};
|
|
1552
1697
|
}
|
|
1553
|
-
/** This function gives the
|
|
1554
|
-
function
|
|
1698
|
+
/** This function gives the score of a fold. A fold with no held-out row or no training row has no score. */
|
|
1699
|
+
function foldScore(rows, fold, learner) {
|
|
1555
1700
|
const train = rows.filter((row) => row.fold !== fold).map((row) => row.y);
|
|
1556
1701
|
const held = rows.flatMap((row, r) => row.fold === fold ? [r] : []);
|
|
1557
1702
|
if (held.length === 0 || train.length === 0) return void 0;
|
|
1558
1703
|
const predict = learner(fold);
|
|
1559
1704
|
const baseline = mean(train);
|
|
1560
|
-
const
|
|
1705
|
+
const predicted = held.map((r) => predict(r));
|
|
1706
|
+
const errors = held.map((r, i) => rows[r].y - predicted[i]);
|
|
1561
1707
|
return {
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1708
|
+
error: {
|
|
1709
|
+
n: held.length,
|
|
1710
|
+
mae: mean(errors.map(Math.abs)),
|
|
1711
|
+
rmse: Math.sqrt(mean(errors.map((e) => e * e))),
|
|
1712
|
+
baselineMae: mean(held.map((r) => Math.abs(rows[r].y - baseline)))
|
|
1713
|
+
},
|
|
1714
|
+
predictions: Object.fromEntries(held.map((r, i) => [rows[r].point.id, predicted[i]]))
|
|
1566
1715
|
};
|
|
1567
1716
|
}
|
|
1568
1717
|
/** This function gives the rows of a measure. It throws a RangeError for a value that a log scale cannot read. */
|
|
@@ -1573,12 +1722,12 @@ function rowsOf(points, measure, log, floor, foldOf) {
|
|
|
1573
1722
|
if (log && floor === void 0 && value <= 0) throw new RangeError(`fitResponse: the point "${point.id}" has the value ${value} of the log measure "${measure}". Give logFloor.`);
|
|
1574
1723
|
return [{
|
|
1575
1724
|
point,
|
|
1576
|
-
y:
|
|
1725
|
+
y: toModelScale(value, log, floor),
|
|
1577
1726
|
fold: foldOf.get(groupKey(point))
|
|
1578
1727
|
}];
|
|
1579
1728
|
});
|
|
1580
1729
|
}
|
|
1581
|
-
/** This function gives the model of one measure on all its rows, and the
|
|
1730
|
+
/** This function gives the model of one measure on all its rows, and the score of each fold. */
|
|
1582
1731
|
function fitMeasure(rows, measure, log, floor, plan) {
|
|
1583
1732
|
if (rows.length === 0) throw new RangeError(`fitResponse: no point has a value of the measure "${measure.id}".`);
|
|
1584
1733
|
const base = {
|
|
@@ -1588,8 +1737,8 @@ function fitMeasure(rows, measure, log, floor, plan) {
|
|
|
1588
1737
|
};
|
|
1589
1738
|
const ridge = plan.kind === "ridge" ? ridgeLearner(rows, plan) : void 0;
|
|
1590
1739
|
const learner = ridge?.learner ?? tableLearner(rows, plan.ids);
|
|
1591
|
-
const
|
|
1592
|
-
if (
|
|
1740
|
+
const scores = Array.from({ length: plan.folds }, (_, f) => foldScore(rows, f, learner));
|
|
1741
|
+
if (scores.every((s) => s === void 0)) throw new RangeError(`fitResponse: all the values of the measure "${measure.id}" are in one fold. Give more groups.`);
|
|
1593
1742
|
const conditional = plan.conditional.length === 0 ? {} : { conditional: plan.conditional };
|
|
1594
1743
|
if (ridge === void 0) return {
|
|
1595
1744
|
model: {
|
|
@@ -1600,7 +1749,7 @@ function fitMeasure(rows, measure, log, floor, plan) {
|
|
|
1600
1749
|
cells: tableCells(rows, plan.ids),
|
|
1601
1750
|
fallback: mean(rows.map((r) => r.y))
|
|
1602
1751
|
},
|
|
1603
|
-
|
|
1752
|
+
scores
|
|
1604
1753
|
};
|
|
1605
1754
|
const { lambda, beta } = ridge.final();
|
|
1606
1755
|
if (!beta.every(Number.isFinite)) throw new RangeError(`fitResponse: the fit of the measure "${measure.id}" with lambda ${lambda} is singular. Give a larger lambda.`);
|
|
@@ -1617,7 +1766,7 @@ function fitMeasure(rows, measure, log, floor, plan) {
|
|
|
1617
1766
|
intercept: beta[0],
|
|
1618
1767
|
terms
|
|
1619
1768
|
},
|
|
1620
|
-
|
|
1769
|
+
scores
|
|
1621
1770
|
};
|
|
1622
1771
|
}
|
|
1623
1772
|
/** This function examines the options that do not depend on the data, and it gives the number of folds and the penalties. */
|
|
@@ -1659,7 +1808,10 @@ function kindOf(dimensions, model) {
|
|
|
1659
1808
|
* The penalty: each fold of a ridge fit chooses from `lambdas` with the other folds. Thus the held-out error of a
|
|
1660
1809
|
* fold is not optimistic. The model of the record chooses with all the folds.
|
|
1661
1810
|
*
|
|
1662
|
-
* The errors: a fold scores the model of the other folds on its held-out points, on the scale of the model.
|
|
1811
|
+
* The errors: a fold scores the model of the other folds on its held-out points, on the scale of the model. With the
|
|
1812
|
+
* option `outOfFold`, the fold also stores the prediction of each of these points.
|
|
1813
|
+
*
|
|
1814
|
+
* The reasons: a measure with a stated reason is `null` for the fit. The field `reasons` of the data stays in the record.
|
|
1663
1815
|
*
|
|
1664
1816
|
* The result does not depend on the order of the points, except for the field `points`, which keeps the order of
|
|
1665
1817
|
* the data. The same data and options give the same bytes in one JavaScript engine.
|
|
@@ -1676,8 +1828,10 @@ function fitResponse(data, options = {}) {
|
|
|
1676
1828
|
const log = new Set(options.log ?? []);
|
|
1677
1829
|
for (const id of log) if (!measures.some((m) => m.id === id)) throw new RangeError(`fitResponse: the option log names the unknown measure "${id}".`);
|
|
1678
1830
|
const kind = kindOf(dimensions, options.model ?? "auto");
|
|
1679
|
-
const
|
|
1680
|
-
|
|
1831
|
+
const interactions = options.interactions ?? true;
|
|
1832
|
+
const expanded = kind === "table" ? [] : options.terms ?? expansion(dimensions, degree, interactions !== false);
|
|
1833
|
+
const terms = interactions === "seen" && options.terms === void 0 ? seenTerms(expanded, dimensions, data.points) : expanded;
|
|
1834
|
+
if (terms.length + 1 > 1024) throw new RangeError(`fitResponse: the expansion has ${terms.length} terms, and the limit is 1023. Set interactions to false or "seen", or give terms.`);
|
|
1681
1835
|
const inputs = checkedInputs(dimensions, terms);
|
|
1682
1836
|
const points = [...data.points].sort((a, b) => byText(a.id, b.id));
|
|
1683
1837
|
const groups = [...new Set(points.map(groupKey))].sort(byText);
|
|
@@ -1695,18 +1849,24 @@ function fitResponse(data, options = {}) {
|
|
|
1695
1849
|
inputs
|
|
1696
1850
|
};
|
|
1697
1851
|
const fits = measures.map((m) => fitMeasure(rowsOf(points, m.id, log.has(m.id), options.logFloor, foldOf), m, log.has(m.id), options.logFloor, plan));
|
|
1698
|
-
const foldRecords = Array.from({ length: folds }, (_, f) =>
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1852
|
+
const foldRecords = Array.from({ length: folds }, (_, f) => {
|
|
1853
|
+
const scored = measures.flatMap((m, i) => {
|
|
1854
|
+
const score = fits[i]?.scores[f];
|
|
1855
|
+
return score === void 0 ? [] : [[m.id, score]];
|
|
1856
|
+
});
|
|
1857
|
+
return {
|
|
1858
|
+
heldOut: points.filter((p) => foldOf.get(groupKey(p)) === f).map((p) => p.id),
|
|
1859
|
+
errors: Object.fromEntries(scored.map(([id, score]) => [id, score.error])),
|
|
1860
|
+
...options.outOfFold === true ? { predictions: Object.fromEntries(scored.map(([id, score]) => [id, score.predictions])) } : {}
|
|
1861
|
+
};
|
|
1862
|
+
});
|
|
1863
|
+
const reasons = data.reasons === void 0 ? {} : { reasons: data.reasons };
|
|
1705
1864
|
return {
|
|
1706
1865
|
schema: RESPONSE_RECORDS_SCHEMA,
|
|
1707
1866
|
name: data.name,
|
|
1708
1867
|
dimensions,
|
|
1709
1868
|
measures,
|
|
1869
|
+
...reasons,
|
|
1710
1870
|
points: data.points,
|
|
1711
1871
|
models: fits.map((fit) => fit.model),
|
|
1712
1872
|
folds: foldRecords
|
|
@@ -2167,6 +2327,6 @@ function calibration(forecasts) {
|
|
|
2167
2327
|
interval: wilson(hits, n)
|
|
2168
2328
|
}));
|
|
2169
2329
|
}
|
|
2170
|
-
export { KENDALL_EXACT, RESPONSE_LAMBDAS, RESPONSE_MAX_POWER, RESPONSE_MAX_TERMS, RESPONSE_RECORDS_SCHEMA, Z95, binomialInterval, bootstrap, bootstrapClusters, bootstrapLeastSquares, bootstrapMedian, bootstrapMedianRatio, bootstrapTwo, brier, calibration, compareRatios, fitResponse, heldOutError, holm, kendallTau, leastSquares, mean, median, medianInterval, negativeBinomialInterval, noiseFloor, noiseVerdict, pairedPermutation, parseResponseRecord, predictResponse, quantile, quantileSorted, ranks, ridgeSolve, sd, signTest, signTestPower, sliceResponse, sorted, spearman, summarize, surfaceResponse, wilson };
|
|
2330
|
+
export { KENDALL_EXACT, RESPONSE_LAMBDAS, RESPONSE_MAX_POWER, RESPONSE_MAX_TERMS, RESPONSE_RECORDS_SCHEMA, Z95, binomialInterval, bootstrap, bootstrapClusters, bootstrapLeastSquares, bootstrapMedian, bootstrapMedianRatio, bootstrapTwo, brier, calibration, compareRatios, fitResponse, heldOutError, heldOutErrorAt, holm, kendallTau, leastSquares, mean, median, medianInterval, negativeBinomialInterval, noiseFloor, noiseVerdict, pairedPermutation, parseResponseRecord, predictResponse, quantile, quantileSorted, ranks, ridgeSolve, sd, signTest, signTestPower, sliceResponse, sorted, spearman, summarize, surfaceResponse, wilson };
|
|
2171
2331
|
|
|
2172
2332
|
//# sourceMappingURL=index.js.map
|