@compstats/core 0.2.0 → 0.4.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 (46) hide show
  1. package/CHANGELOG.md +105 -0
  2. package/README.md +127 -110
  3. package/dist/3d.js +1120 -122
  4. package/dist/3d.js.map +16 -9
  5. package/dist/core/arith.d.ts.map +1 -1
  6. package/dist/core/linalg/cov.d.ts +50 -0
  7. package/dist/core/linalg/cov.d.ts.map +1 -0
  8. package/dist/core/linalg/eigen.d.ts +53 -0
  9. package/dist/core/linalg/eigen.d.ts.map +1 -0
  10. package/dist/core/linalg/lm.d.ts +78 -0
  11. package/dist/core/linalg/lm.d.ts.map +1 -0
  12. package/dist/core/linalg/lu.d.ts +154 -0
  13. package/dist/core/linalg/lu.d.ts.map +1 -0
  14. package/dist/core/linalg/matrix.d.ts +131 -0
  15. package/dist/core/linalg/matrix.d.ts.map +1 -0
  16. package/dist/core/linalg/modelMatrix.d.ts +69 -0
  17. package/dist/core/linalg/modelMatrix.d.ts.map +1 -0
  18. package/dist/core/linalg/namedVector.d.ts +37 -0
  19. package/dist/core/linalg/namedVector.d.ts.map +1 -0
  20. package/dist/core/linalg/ops.d.ts +120 -0
  21. package/dist/core/linalg/ops.d.ts.map +1 -0
  22. package/dist/core/linalg/prcomp.d.ts +66 -0
  23. package/dist/core/linalg/prcomp.d.ts.map +1 -0
  24. package/dist/core/linalg/qr.d.ts +134 -0
  25. package/dist/core/linalg/qr.d.ts.map +1 -0
  26. package/dist/core/linalg/vector.d.ts +68 -0
  27. package/dist/core/linalg/vector.d.ts.map +1 -0
  28. package/dist/core/moderation.d.ts +6 -3
  29. package/dist/core/moderation.d.ts.map +1 -1
  30. package/dist/core/ols.d.ts +4 -7
  31. package/dist/core/ols.d.ts.map +1 -1
  32. package/dist/data/moderationData.d.ts +2 -2
  33. package/dist/data/pcaDegenerate.d.ts +1 -1
  34. package/dist/index.d.ts +1 -1
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +1601 -863
  37. package/dist/index.js.map +17 -11
  38. package/dist/linalg.d.ts +36 -0
  39. package/dist/linalg.d.ts.map +1 -0
  40. package/dist/linalg.js +1860 -0
  41. package/dist/linalg.js.map +24 -0
  42. package/dist/plot/moderation3d.d.ts +1 -1
  43. package/dist/plot/sampling.d.ts +45 -0
  44. package/dist/plot/sampling.d.ts.map +1 -1
  45. package/dist/plot/scatter3d.d.ts +1 -1
  46. package/package.json +16 -5
package/dist/index.js CHANGED
@@ -48,6 +48,9 @@ function meanAbsoluteDeviation(values) {
48
48
  return mean(values.map((value) => Math.abs(value - center)));
49
49
  }
50
50
  function fusedMultiplyAdd(a, b, c) {
51
+ if (a * b === 0) {
52
+ return c + a * b;
53
+ }
51
54
  const [product, productError] = twoProduct(a, b);
52
55
  const [sum2, sumError] = twoSum(c, product);
53
56
  const rounded = sum2 + (sumError + productError);
@@ -630,960 +633,1549 @@ function requireNumericColumn(data, name, role) {
630
633
  }
631
634
  return column;
632
635
  }
633
- // src/core/ols.ts
634
- var DEFAULT_LEAST_SQUARES_TOLERANCE = 0.0000001;
635
- function leastSquares(design, y, options = {}) {
636
- const { weights, tolerance = DEFAULT_LEAST_SQUARES_TOLERANCE } = options;
637
- const rows = design.length;
638
- if (rows === 0) {
639
- throw new RangeError("least squares needs at least one row");
636
+ // src/core/special.ts
637
+ var LANCZOS_G = 607 / 128;
638
+ var LANCZOS_LEAD = 0.9999999999999971;
639
+ var LANCZOS_TAIL = [
640
+ 57.15623566586292,
641
+ -59.59796035547549,
642
+ 14.136097974741746,
643
+ -0.4919138160976202,
644
+ 0.00003399464998481189,
645
+ 0.00004652362892704858,
646
+ -0.00009837447530487956,
647
+ 0.0001580887032249125,
648
+ -0.00021026444172410488,
649
+ 0.00021743961811521265,
650
+ -0.0001643181065367639,
651
+ 0.00008441822398385275,
652
+ -0.000026190838401581408,
653
+ 0.0000036899182659531625
654
+ ];
655
+ var LOG_SQRT_TWO_PI = 0.5 * Math.log(2 * Math.PI);
656
+ function lanczosSeries(x) {
657
+ return LANCZOS_LEAD + sum(LANCZOS_TAIL.map((coefficient, index) => coefficient / (x + index)));
658
+ }
659
+ function logGamma(x) {
660
+ if (!(x > 0)) {
661
+ return Number.NaN;
640
662
  }
641
- const width = design[0].length;
642
- if (design.some((row) => row.length !== width)) {
643
- throw new RangeError("every design row needs the same number of columns");
663
+ const shifted = x + LANCZOS_G - 0.5;
664
+ return LOG_SQRT_TWO_PI + (x - 0.5) * Math.log(shifted) - shifted + Math.log(lanczosSeries(x));
665
+ }
666
+ function logBeta(a, b) {
667
+ if (!(a > 0) || !(b > 0)) {
668
+ return Number.NaN;
644
669
  }
645
- if (y.length !== rows) {
646
- throw new RangeError(`the response has ${y.length} values but the design has ${rows} rows`);
670
+ const shiftedSum = a + b + LANCZOS_G - 0.5;
671
+ return LOG_SQRT_TWO_PI - (LANCZOS_G - 0.5) + Math.log(lanczosSeries(a)) + Math.log(lanczosSeries(b)) - Math.log(lanczosSeries(a + b)) + (a - 0.5) * Math.log1p(-b / shiftedSum) + (b - 0.5) * Math.log1p(-a / shiftedSum) - 0.5 * Math.log(shiftedSum);
672
+ }
673
+ var FRACTION_MAX_STEPS = 400;
674
+ var FRACTION_EPSILON = 0.0000000000000003;
675
+ var FRACTION_FLOOR = 0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001;
676
+ function betaContinuedFraction(x, a, b) {
677
+ const total = a + b;
678
+ const aPlus = a + 1;
679
+ const aMinus = a - 1;
680
+ let c = 1;
681
+ let d = 1 - total * x / aPlus;
682
+ if (Math.abs(d) < FRACTION_FLOOR) {
683
+ d = FRACTION_FLOOR;
647
684
  }
648
- if (weights !== undefined) {
649
- if (weights.length !== rows) {
650
- throw new RangeError(`there are ${weights.length} weights but ${rows} rows`);
685
+ d = 1 / d;
686
+ let value = d;
687
+ for (let step = 1;step <= FRACTION_MAX_STEPS; step += 1) {
688
+ const twice = 2 * step;
689
+ const even = step * (b - step) * x / ((aMinus + twice) * (a + twice));
690
+ d = 1 + even * d;
691
+ if (Math.abs(d) < FRACTION_FLOOR) {
692
+ d = FRACTION_FLOOR;
651
693
  }
652
- if (weights.some((weight) => !(weight >= 0))) {
653
- throw new RangeError("weights cannot be negative or missing");
694
+ c = 1 + even / c;
695
+ if (Math.abs(c) < FRACTION_FLOOR) {
696
+ c = FRACTION_FLOOR;
654
697
  }
655
- }
656
- const scale = weights?.map((weight) => Math.sqrt(weight));
657
- const scaled = (value, row) => scale === undefined ? value : value * scale[row];
658
- const columns = Array.from({ length: width }, (_, column) => design.map((row, index) => scaled(row[column], index)));
659
- const projected = y.map(scaled);
660
- const { householders, pivot, rank } = decompose(columns, tolerance, rows);
661
- applyHouseholders(columns, householders, projected, Math.min(rank, rows - 1));
662
- const solved = backSubstitute(columns, projected, rank);
663
- const coefficients = new Array(width).fill(null);
664
- pivot.slice(0, rank).forEach((column, position) => {
665
- coefficients[column] = solved[position];
666
- });
667
- const fitted = design.map((row) => sum(zipWith(row, coefficients, (value, coefficient) => coefficient === null ? 0 : value * coefficient)));
668
- const residuals = zipWith(y, fitted, (value, fit) => value - fit);
669
- return { coefficients, fitted, residuals, rank };
670
- }
671
- function decompose(columns, tolerance, rows) {
672
- const width = columns.length;
673
- const pivot = columns.map((_, column) => column);
674
- const originalNorms = columns.map((column) => norm(column, 0) || 1);
675
- const householders = new Array(width).fill(0);
676
- let live = width;
677
- for (let step = 0;step < Math.min(rows, width); step++) {
678
- while (step < live && norm(columns[step], step) < originalNorms[step] * tolerance) {
679
- cycleToEnd(columns, pivot, originalNorms, step);
680
- live -= 1;
698
+ d = 1 / d;
699
+ value *= d * c;
700
+ const odd = -(a + step) * (total + step) * x / ((a + twice) * (aPlus + twice));
701
+ d = 1 + odd * d;
702
+ if (Math.abs(d) < FRACTION_FLOOR) {
703
+ d = FRACTION_FLOOR;
681
704
  }
682
- if (step === rows - 1) {
683
- continue;
705
+ c = 1 + odd / c;
706
+ if (Math.abs(c) < FRACTION_FLOOR) {
707
+ c = FRACTION_FLOOR;
708
+ }
709
+ d = 1 / d;
710
+ const delta = d * c;
711
+ value *= delta;
712
+ if (Math.abs(delta - 1) < FRACTION_EPSILON) {
713
+ break;
684
714
  }
685
- householders[step] = reflect(columns, step, rows);
686
715
  }
687
- return { householders, pivot, rank: Math.min(live, rows) };
716
+ return value;
688
717
  }
689
- function reflect(columns, step, rows) {
690
- const column = columns[step];
691
- const length = norm(column, step);
692
- if (length === 0) {
718
+ function incompleteBeta(x, a, b) {
719
+ if (Number.isNaN(x)) {
720
+ return Number.NaN;
721
+ }
722
+ if (x <= 0) {
693
723
  return 0;
694
724
  }
695
- const pivotNorm = column[step] < 0 ? -length : length;
696
- for (let row = step;row < rows; row++) {
697
- column[row] = column[row] / pivotNorm;
725
+ if (x >= 1) {
726
+ return 1;
698
727
  }
699
- const leading = 1 + column[step];
700
- column[step] = leading;
701
- for (let index = step + 1;index < columns.length; index++) {
702
- const other = columns[index];
703
- let inner = 0;
704
- for (let row = step;row < rows; row++) {
705
- inner += column[row] * other[row];
706
- }
707
- const factor = -inner / leading;
708
- for (let row = step;row < rows; row++) {
709
- other[row] = other[row] + factor * column[row];
728
+ return incompleteBetaSplit(x, 1 - x, a, b);
729
+ }
730
+ function incompleteBetaSplit(x, complement, a, b) {
731
+ if (Number.isNaN(x) || Number.isNaN(complement) || !(a > 0) || !(b > 0)) {
732
+ return Number.NaN;
733
+ }
734
+ if (x <= 0) {
735
+ return 0;
736
+ }
737
+ if (complement <= 0) {
738
+ return 1;
739
+ }
740
+ const logX = complement < 0.5 ? Math.log1p(-complement) : Math.log(x);
741
+ const logComplement = x < 0.5 ? Math.log1p(-x) : Math.log(complement);
742
+ const front = Math.exp(a * logX + b * logComplement - logBeta(a, b));
743
+ if (x < (a + 1) / (a + b + 2)) {
744
+ return front * betaContinuedFraction(x, a, b) / a;
745
+ }
746
+ return 1 - front * betaContinuedFraction(complement, b, a) / b;
747
+ }
748
+ var GAMMA_MAX_STEPS = 1000;
749
+ var GAMMA_EPSILON = 0.0000000000000003;
750
+ function lowerGammaSeries(a, x) {
751
+ let term = 1 / a;
752
+ let total = term;
753
+ for (let step = 1;step <= GAMMA_MAX_STEPS; step += 1) {
754
+ term *= x / (a + step);
755
+ total += term;
756
+ if (Math.abs(term) < Math.abs(total) * GAMMA_EPSILON) {
757
+ break;
710
758
  }
711
759
  }
712
- column[step] = -pivotNorm;
713
- return leading;
760
+ return total * Math.exp(-x + a * Math.log(x) - logGamma(a));
714
761
  }
715
- function applyHouseholders(columns, householders, response, count) {
716
- const rows = response.length;
717
- for (let step = 0;step < count; step++) {
718
- const leading = householders[step];
719
- if (leading === 0) {
720
- continue;
762
+ function upperGammaFraction(a, x) {
763
+ let b = x + 1 - a;
764
+ let c = 1 / FRACTION_FLOOR;
765
+ let d = 1 / b;
766
+ let value = d;
767
+ for (let step = 1;step <= GAMMA_MAX_STEPS; step += 1) {
768
+ const numerator = -step * (step - a);
769
+ b += 2;
770
+ d = numerator * d + b;
771
+ if (Math.abs(d) < FRACTION_FLOOR) {
772
+ d = FRACTION_FLOOR;
721
773
  }
722
- const column = columns[step];
723
- let inner = leading * response[step];
724
- for (let row = step + 1;row < rows; row++) {
725
- inner += column[row] * response[row];
774
+ c = b + numerator / c;
775
+ if (Math.abs(c) < FRACTION_FLOOR) {
776
+ c = FRACTION_FLOOR;
726
777
  }
727
- const factor = -inner / leading;
728
- response[step] = response[step] + factor * leading;
729
- for (let row = step + 1;row < rows; row++) {
730
- response[row] = response[row] + factor * column[row];
778
+ d = 1 / d;
779
+ const delta = d * c;
780
+ value *= delta;
781
+ if (Math.abs(delta - 1) < GAMMA_EPSILON) {
782
+ break;
731
783
  }
732
784
  }
785
+ return value * Math.exp(-x + a * Math.log(x) - logGamma(a));
733
786
  }
734
- function backSubstitute(columns, response, rank) {
735
- const solved = new Array(rank).fill(0);
736
- for (let row = rank - 1;row >= 0; row--) {
737
- let value = response[row];
738
- for (let column = row + 1;column < rank; column++) {
739
- value -= columns[column][row] * solved[column];
740
- }
741
- solved[row] = value / columns[row][row];
787
+ function upperGamma(a, x) {
788
+ if (x <= 0) {
789
+ return 1;
742
790
  }
743
- return solved;
744
- }
745
- function cycleToEnd(columns, pivot, originalNorms, step) {
746
- moveToEnd(columns, step);
747
- moveToEnd(pivot, step);
748
- moveToEnd(originalNorms, step);
791
+ if (!Number.isFinite(x)) {
792
+ return 0;
793
+ }
794
+ return x < a + 1 ? 1 - lowerGammaSeries(a, x) : upperGammaFraction(a, x);
749
795
  }
750
- function moveToEnd(track, from) {
751
- const [moved] = track.splice(from, 1);
752
- track.push(moved);
796
+ function normalCdf(z) {
797
+ if (Number.isNaN(z)) {
798
+ return Number.NaN;
799
+ }
800
+ const lower = 0.5 * upperGamma(0.5, 0.5 * z * z);
801
+ return z > 0 ? 1 - lower : lower;
753
802
  }
754
- function norm(column, from) {
755
- return Math.hypot(...column.slice(from));
803
+ var BELOW_ONE = 1 - Number.EPSILON / 2;
804
+ var INVERSE_MAX_STEPS = 200;
805
+ function inverseGuess(p, a, b) {
806
+ if (a >= 1 && b >= 1) {
807
+ const tail = p < 0.5 ? p : 1 - p;
808
+ const t = Math.sqrt(-2 * Math.log(tail));
809
+ const normal = (p < 0.5 ? -1 : 1) * ((2.30753 + t * 0.27061) / (1 + t * (0.99229 + t * 0.04481)) - t);
810
+ const scale = (normal * normal - 3) / 6;
811
+ const harmonic = 2 / (1 / (2 * a - 1) + 1 / (2 * b - 1));
812
+ const w = normal * Math.sqrt(scale + harmonic) / harmonic - (1 / (2 * b - 1) - 1 / (2 * a - 1)) * (scale + 5 / 6 - 2 / (3 * harmonic));
813
+ return a / (a + b * Math.exp(2 * w));
814
+ }
815
+ const lower = Math.exp(a * Math.log(a / (a + b))) / a;
816
+ const upper = Math.exp(b * Math.log(b / (a + b))) / b;
817
+ const total = lower + upper;
818
+ if (p < lower / total) {
819
+ return Math.pow(a * total * p, 1 / a);
820
+ }
821
+ return 1 - Math.pow(b * total * (1 - p), 1 / b);
756
822
  }
757
-
758
- // src/core/moderation.ts
759
- var GRID_STEPS = 15;
760
- function moderationSurface(data, options) {
761
- const { outcome, iv, mod, interaction = true, controls = [] } = options;
762
- if (iv === mod) {
763
- throw new RangeError(`\`iv\` and \`mod\` must name different columns, both name "${iv}"`);
823
+ function inverseIncompleteBeta(p, a, b) {
824
+ if (Number.isNaN(p) || !(a > 0) || !(b > 0)) {
825
+ return Number.NaN;
764
826
  }
765
- const named = new Set([outcome, iv, mod]);
766
- controls.forEach((control) => {
767
- if (named.has(control)) {
768
- throw new RangeError(`control "${control}" already names the outcome, the iv, the mod, ` + "or another control");
769
- }
770
- named.add(control);
771
- });
772
- const rows = frameRows(data);
773
- const y = requireNumericColumn(data, outcome, "outcome");
774
- const ivColumn = requireNumericColumn(data, iv, "iv");
775
- const modColumn = requireNumericColumn(data, mod, "mod");
776
- const controlColumns = controls.map((control) => requireNumericColumn(data, control, "controls"));
777
- const modelColumns = [y, ivColumn, modColumn, ...controlColumns];
778
- const completeRows = y.map((_, row) => row).filter((row) => modelColumns.every((column) => Number.isFinite(column[row])));
779
- if (completeRows.length === 0) {
780
- throw new RangeError("the model has no complete rows: every row is missing a value in " + "the outcome, the IV, the moderator, or a control");
827
+ if (p <= 0) {
828
+ return 0;
781
829
  }
782
- const designColumns = [
783
- { name: "(Intercept)", values: new Array(rows).fill(1) },
784
- { name: iv, values: ivColumn },
785
- { name: mod, values: modColumn },
786
- ...controls.map((control, index) => ({
787
- name: control,
788
- values: controlColumns[index]
789
- })),
790
- ...interaction ? [
791
- {
792
- name: `${iv}:${mod}`,
793
- values: zipWith(ivColumn, modColumn, (a, b) => a * b)
794
- }
795
- ] : []
796
- ];
797
- const design = completeRows.map((row) => designColumns.map((column) => column.values[row]));
798
- const fit = leastSquares(design, completeRows.map((row) => y[row]));
799
- const coefficients = designColumns.map((column, index) => ({
800
- name: column.name,
801
- value: fit.coefficients[index] ?? null
802
- }));
803
- const fitted = new Array(rows).fill(Number.NaN);
804
- const residuals = new Array(rows).fill(Number.NaN);
805
- completeRows.forEach((row, survivor) => {
806
- fitted[row] = fit.fitted[survivor];
807
- residuals[row] = fit.residuals[survivor];
808
- });
809
- const ivValues = rSeq(...extent(ivColumn), GRID_STEPS);
810
- const modValues = rSeq(...extent(modColumn), GRID_STEPS);
811
- const holds = Object.fromEntries(controls.map((control, index) => [
812
- control,
813
- mean(controlColumns[index].filter(Number.isFinite))
814
- ]));
815
- const weights = coefficients.map((term) => term.value ?? 0);
816
- const predictions = modValues.flatMap((modValue) => ivValues.map((ivValue) => {
817
- const gridRow = [
818
- 1,
819
- ivValue,
820
- modValue,
821
- ...controls.map((control) => holds[control]),
822
- ...interaction ? [ivValue * modValue] : []
823
- ];
824
- return sum(zipWith(gridRow, weights, (value, weight) => value * weight));
825
- }));
826
- const [dataLow, dataHigh] = extent(y);
827
- const [surfaceLow, surfaceHigh] = extent(predictions);
828
- return {
829
- coefficients,
830
- fitted,
831
- residuals,
832
- ivValues,
833
- modValues,
834
- predictions,
835
- zlim: [Math.min(dataLow, surfaceLow), Math.max(dataHigh, surfaceHigh)],
836
- holds
837
- };
838
- }
839
- function rSeq(from, to, length) {
840
- if (from === to) {
841
- return new Array(length).fill(from);
830
+ if (p >= 1) {
831
+ return 1;
842
832
  }
843
- const by = (to - from) / (length - 1);
844
- return Array.from({ length }, (_, index) => index === 0 ? from : index === length - 1 ? to : from + index * by);
845
- }
846
- // src/core/regression.ts
847
- function completePointRows(points) {
848
- return points.flatMap((point, row) => Number.isFinite(point.x) && Number.isFinite(point.y) ? [row] : []);
849
- }
850
- function linearRegression(points) {
851
- const rows = completePointRows(points);
852
- if (rows.length === 0) {
853
- return null;
833
+ const logBetaValue = logBeta(a, b);
834
+ let lower = 0;
835
+ let upper = 1;
836
+ let x = inverseGuess(p, a, b);
837
+ if (!(x > 0) || !(x < 1)) {
838
+ x = 0.5;
854
839
  }
855
- const xs = rows.map((row) => points[row].x);
856
- const ys = rows.map((row) => points[row].y);
857
- const meanX = mean(xs);
858
- const meanY = mean(ys);
859
- const devX = xs.map((x) => x - meanX);
860
- const devY = ys.map((y) => y - meanY);
861
- const sumSquaresX = sum(devX.map((d) => d * d));
862
- const sumSquaresY = sum(devY.map((d) => d * d));
863
- const sumProducts = sum(zipWith(devX, devY, (dx, dy) => dx * dy));
864
- const slope = sumSquaresX === 0 ? null : sumProducts / sumSquaresX;
865
- const intercept = slope === null ? meanY : meanY - slope * meanX;
866
- const correlation = sumSquaresX === 0 || sumSquaresY === 0 ? null : sumProducts / Math.sqrt(sumSquaresX * sumSquaresY);
867
- const fittedComplete = xs.map((x) => slope === null ? intercept : intercept + slope * x);
868
- const ssr = sum(fittedComplete.map((f) => (f - meanY) * (f - meanY)));
869
- const sse = sum(zipWith(ys, fittedComplete, (y, f) => (y - f) * (y - f)));
870
- const sst = sumSquaresY;
871
- const rSquared = sst === 0 ? null : ssr / sst;
872
- const fitted = new Array(points.length).fill(Number.NaN);
873
- rows.forEach((row, survivor) => {
874
- fitted[row] = fittedComplete[survivor];
875
- });
876
- return {
877
- intercept,
878
- slope,
879
- correlation,
880
- ssr,
881
- sse,
882
- sst,
883
- rSquared,
884
- fitted
885
- };
840
+ for (let step = 0;step < INVERSE_MAX_STEPS; step += 1) {
841
+ const residual = incompleteBeta(x, a, b) - p;
842
+ if (residual < 0) {
843
+ lower = x;
844
+ } else {
845
+ upper = x;
846
+ }
847
+ const density = Math.exp((a - 1) * Math.log(x) + (b - 1) * Math.log1p(-x) - logBetaValue);
848
+ let next = density > 0 && Number.isFinite(density) ? x - residual / density : Number.NaN;
849
+ if (!(next > lower) || !(next < upper)) {
850
+ next = 0.5 * (lower + upper);
851
+ }
852
+ if (next === x) {
853
+ break;
854
+ }
855
+ const moved = Math.abs(next - x);
856
+ x = next;
857
+ if (moved <= Number.EPSILON * x) {
858
+ break;
859
+ }
860
+ }
861
+ return Math.min(x, BELOW_ONE);
886
862
  }
887
- // src/core/matrix.ts
888
- var SMALLEST_NORMAL = 0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022250738585072014;
889
- function determinant(matrix) {
890
- return determinantOf(factorize(matrix));
863
+
864
+ // src/core/tdist.ts
865
+ function isNonCentral(ncp) {
866
+ return ncp !== undefined && ncp !== 0;
891
867
  }
892
- function invertMatrix(matrix) {
893
- const factorization = factorize(matrix);
894
- const determinant2 = determinantOf(factorization);
895
- const zeroPivot = zeroPivotOf(factorization);
896
- if (zeroPivot !== null) {
897
- return {
898
- determinant: determinant2,
899
- inverse: null,
900
- singularity: "exact",
901
- rcond: 0,
902
- zeroPivot
903
- };
868
+ function isBadArgument(value, df, ncp) {
869
+ return Number.isNaN(value) || !(df > 0) || ncp !== undefined && Number.isNaN(ncp);
870
+ }
871
+ function dt(x, df, ncp) {
872
+ if (isBadArgument(x, df, ncp)) {
873
+ return Number.NaN;
904
874
  }
905
- const inverse = solveForIdentity(factorization);
906
- const rcond = 1 / (oneNorm(matrix) * oneNorm(inverse));
907
- if (rcond < Number.EPSILON) {
908
- return {
909
- determinant: determinant2,
910
- inverse: null,
911
- singularity: "computational",
912
- rcond,
913
- zeroPivot: null
914
- };
875
+ return isNonCentral(ncp) ? nonCentralDensity(x, df, ncp) : centralDensity(x, df);
876
+ }
877
+ function pt(x, df, ncp) {
878
+ if (isBadArgument(x, df, ncp)) {
879
+ return Number.NaN;
915
880
  }
916
- return {
917
- determinant: determinant2,
918
- inverse,
919
- singularity: null,
920
- rcond,
921
- zeroPivot: null
922
- };
881
+ return isNonCentral(ncp) ? nonCentralProbability(x, df, ncp) : centralProbability(x, df);
923
882
  }
924
- function factorize(matrix) {
925
- const interchanged = Math.abs(matrix.y1) > Math.abs(matrix.x1);
926
- const leading = interchanged ? matrix.y1 : matrix.x1;
927
- const upperRight = interchanged ? matrix.y2 : matrix.x2;
928
- const below = interchanged ? matrix.x1 : matrix.y1;
929
- const belowRight = interchanged ? matrix.x2 : matrix.y2;
930
- const multiplier = Math.abs(leading) >= SMALLEST_NORMAL ? below * (1 / leading) : below / leading;
931
- const trailing = fusedMultiplyAdd(-multiplier, upperRight, belowRight);
932
- return {
933
- interchanged,
934
- multiplier,
935
- pivots: [leading, trailing],
936
- upperRight
937
- };
883
+ function qt(p, df, ncp) {
884
+ if (isBadArgument(p, df, ncp) || p < 0 || p > 1) {
885
+ return Number.NaN;
886
+ }
887
+ return isNonCentral(ncp) ? nonCentralQuantile(p, df, ncp) : centralQuantile(p, df);
938
888
  }
939
- function determinantOf(factorization) {
940
- const { interchanged, pivots } = factorization;
941
- if (zeroPivotOf(factorization) !== null) {
889
+ function centralDensity(x, df) {
890
+ if (!Number.isFinite(x)) {
942
891
  return 0;
943
892
  }
944
- const modulus = sum(pivots.map((pivot) => Math.log(Math.abs(pivot))));
945
- const sign = pivots.reduce((carried, pivot) => pivot < 0 ? -carried : carried, interchanged ? -1 : 1);
946
- return sign * Math.exp(modulus);
893
+ const logDensity = -0.5 * Math.log(df) - logBeta(0.5, df / 2) - (df + 1) / 2 * Math.log1p(x * x / df);
894
+ return Math.exp(logDensity);
947
895
  }
948
- function zeroPivotOf(factorization) {
949
- const [leading, trailing] = factorization.pivots;
950
- if (leading === 0) {
896
+ function centralProbability(x, df) {
897
+ if (x === 0) {
898
+ return 0.5;
899
+ }
900
+ if (x === Number.POSITIVE_INFINITY) {
951
901
  return 1;
952
902
  }
953
- return trailing === 0 ? 2 : null;
903
+ if (x === Number.NEGATIVE_INFINITY) {
904
+ return 0;
905
+ }
906
+ const tail = upperTail(Math.abs(x), df);
907
+ return x < 0 ? tail : 1 - tail;
954
908
  }
955
- function solveForIdentity(factorization) {
956
- const { interchanged } = factorization;
957
- const [firstTop, firstBottom] = interchanged ? [0, 1] : [1, 0];
958
- const [secondTop, secondBottom] = interchanged ? [1, 0] : [0, 1];
959
- const [x1, y1] = solveColumn(factorization, firstTop, firstBottom);
960
- const [x2, y2] = solveColumn(factorization, secondTop, secondBottom);
961
- return {
962
- x1: withoutNegativeZero(x1),
963
- y1: withoutNegativeZero(y1),
964
- x2: withoutNegativeZero(x2),
965
- y2: withoutNegativeZero(y2)
966
- };
909
+ function upperTail(t, df) {
910
+ const squared = t * t;
911
+ if (!Number.isFinite(squared)) {
912
+ return 0;
913
+ }
914
+ const total = df + squared;
915
+ return 0.5 * incompleteBetaSplit(df / total, squared / total, df / 2, 0.5);
967
916
  }
968
- function solveColumn(factorization, top, bottom) {
969
- const { multiplier, pivots, upperRight } = factorization;
970
- const lower = fusedMultiplyAdd(-multiplier, top, bottom) / pivots[1];
971
- const upper = fusedMultiplyAdd(-upperRight, lower, top) / pivots[0];
972
- return [upper, lower];
917
+ var POLISH_MAX_STEPS = 4;
918
+ function centralQuantile(p, df) {
919
+ if (p === 0.5) {
920
+ return 0;
921
+ }
922
+ if (p <= 0) {
923
+ return Number.NEGATIVE_INFINITY;
924
+ }
925
+ if (p >= 1) {
926
+ return Number.POSITIVE_INFINITY;
927
+ }
928
+ const tail = p < 0.5 ? p : 1 - p;
929
+ const sign = p < 0.5 ? -1 : 1;
930
+ const twoSided = 2 * tail;
931
+ let squared;
932
+ if (twoSided > 0.5) {
933
+ const near = inverseIncompleteBeta(1 - twoSided, 0.5, df / 2);
934
+ squared = df * near / (1 - near);
935
+ } else {
936
+ const far = inverseIncompleteBeta(twoSided, df / 2, 0.5);
937
+ squared = df * (1 - far) / far;
938
+ }
939
+ return sign * polish(Math.sqrt(squared), tail, df);
973
940
  }
974
- function oneNorm(matrix) {
975
- return Math.max(Math.abs(matrix.x1) + Math.abs(matrix.y1), Math.abs(matrix.x2) + Math.abs(matrix.y2));
941
+ function polish(start, tail, df) {
942
+ let t = start;
943
+ for (let step = 0;step < POLISH_MAX_STEPS; step += 1) {
944
+ const density = centralDensity(t, df);
945
+ if (!(density > 0) || !Number.isFinite(t)) {
946
+ break;
947
+ }
948
+ const move = (upperTail(t, df) - tail) / density;
949
+ const next = t + move;
950
+ if (!(next > 0) || !Number.isFinite(next) || Math.abs(move) > 0.25 * t) {
951
+ break;
952
+ }
953
+ if (next === t) {
954
+ break;
955
+ }
956
+ t = next;
957
+ if (Math.abs(move) <= Number.EPSILON * t) {
958
+ break;
959
+ }
960
+ }
961
+ return t;
976
962
  }
977
- // src/core/pca.ts
978
- function principalComponents(points) {
979
- const rows = completePointRows(points);
980
- if (rows.length === 0) {
981
- return null;
963
+ var SERIES_MAX_STEPS = 1000;
964
+ var SERIES_ERROR_MAX = 0.000000000001;
965
+ var SERIES_NCP_LIMIT_SQUARED = 2 * Math.LN2 * 1022;
966
+ var SERIES_DF_LIMIT = 400000;
967
+ var SQRT_TWO_OVER_PI = Math.sqrt(2 / Math.PI);
968
+ function nonCentralProbability(x, df, ncp) {
969
+ if (x === Number.POSITIVE_INFINITY) {
970
+ return 1;
982
971
  }
983
- const complete = rows.map((row) => points[row]);
984
- const centerX = mean(complete.map((point) => point.x));
985
- const centerY = mean(complete.map((point) => point.y));
986
- const devX = complete.map((point) => point.x - centerX);
987
- const devY = complete.map((point) => point.y - centerY);
988
- const divisor = Math.max(1, complete.length - 1);
989
- const varX = sum(devX.map((d) => d * d)) / divisor;
990
- const varY = sum(devY.map((d) => d * d)) / divisor;
991
- const covariance = sum(zipWith(devX, devY, (dx, dy) => dx * dy)) / divisor;
992
- const [first, second] = componentsOf(varX, varY, covariance);
993
- const scores = points.map(() => ({ x: Number.NaN, y: Number.NaN }));
994
- rows.forEach((row, survivor) => {
995
- const dx = devX[survivor];
996
- const dy = devY[survivor];
997
- scores[row] = {
998
- x: dx * first.loadings[0] + dy * first.loadings[1],
999
- y: dx * second.loadings[0] + dy * second.loadings[1]
1000
- };
1001
- });
1002
- return {
1003
- sdev: [sdevOf(first.variance), sdevOf(second.variance)],
1004
- rotation: [first.loadings, second.loadings],
1005
- center: { x: centerX, y: centerY },
1006
- scores
1007
- };
972
+ if (x === Number.NEGATIVE_INFINITY) {
973
+ return 0;
974
+ }
975
+ const reflected = x < 0;
976
+ const t = reflected ? -x : x;
977
+ const delta = reflected ? -ncp : ncp;
978
+ const lower = df > SERIES_DF_LIMIT || delta * delta > SERIES_NCP_LIMIT_SQUARED ? normalApproximation(t, df, delta) : lenthSeries(t, df, delta);
979
+ return reflected ? 1 - lower : lower;
1008
980
  }
1009
- function componentsOf(varX, varY, covariance) {
1010
- if (covariance === 0) {
1011
- const alongX = { variance: varX, loadings: [1, 0] };
1012
- const alongY = { variance: varY, loadings: [0, 1] };
1013
- return varX >= varY ? [alongX, alongY] : [alongY, alongX];
981
+ function normalApproximation(t, df, delta) {
982
+ const shrink = 1 / (4 * df);
983
+ const spread = Math.sqrt(1 + t * t * 2 * shrink);
984
+ return normalCdf((t * (1 - shrink) - delta) / spread);
985
+ }
986
+ function lenthSeries(t, df, delta) {
987
+ const squared = t * t;
988
+ const total = df + squared;
989
+ const x = squared / total;
990
+ const complement = df / total;
991
+ let sum2 = 0;
992
+ if (x > 0) {
993
+ const lambda = delta * delta;
994
+ let oddWeight = 0.5 * Math.exp(-0.5 * lambda);
995
+ let evenWeight = SQRT_TWO_OVER_PI * oddWeight * delta;
996
+ let remaining = 0.5 - oddWeight;
997
+ if (remaining < 0.0000001) {
998
+ remaining = -0.5 * Math.expm1(-0.5 * lambda);
999
+ }
1000
+ let a = 0.5;
1001
+ const b = 0.5 * df;
1002
+ const powered = Math.pow(complement, b);
1003
+ const logBetaValue = logBeta(0.5, b);
1004
+ let oddTerm = incompleteBetaSplit(x, complement, a, b);
1005
+ let oddStep = 2 * powered * Math.exp(a * Math.log(x) - logBetaValue);
1006
+ let evenTerm = 1 - powered;
1007
+ let evenStep = b * x * powered;
1008
+ sum2 = oddWeight * oddTerm + evenWeight * evenTerm;
1009
+ for (let step = 1;step <= SERIES_MAX_STEPS; step += 1) {
1010
+ a += 1;
1011
+ oddTerm -= oddStep;
1012
+ evenTerm -= evenStep;
1013
+ oddStep *= x * (a + b - 1) / a;
1014
+ evenStep *= x * (a + b - 0.5) / (a + 0.5);
1015
+ oddWeight *= lambda / (2 * step);
1016
+ evenWeight *= lambda / (2 * step + 1);
1017
+ remaining -= oddWeight;
1018
+ if (remaining <= 0) {
1019
+ break;
1020
+ }
1021
+ sum2 += oddWeight * oddTerm + evenWeight * evenTerm;
1022
+ if (Math.abs(2 * remaining * (oddTerm - oddStep)) < SERIES_ERROR_MAX) {
1023
+ break;
1024
+ }
1025
+ }
1014
1026
  }
1015
- const middle = (varX + varY) / 2;
1016
- const spread = Math.hypot((varX - varY) / 2, covariance);
1017
- const larger = middle + spread;
1018
- const smaller = middle - spread;
1019
- const fromRowX = larger - varX;
1020
- const fromRowY = larger - varY;
1021
- const direction = fromRowX >= fromRowY ? [covariance, fromRowX] : [fromRowY, covariance];
1022
- const leading = signed(unit(direction));
1023
- const trailing = signed([negated(leading[1]), leading[0]]);
1024
- return [
1025
- { variance: larger, loadings: leading },
1026
- { variance: smaller, loadings: trailing }
1027
- ];
1028
- }
1029
- function unit(vector) {
1030
- const length = Math.hypot(vector[0], vector[1]);
1031
- return [vector[0] / length, vector[1] / length];
1032
- }
1033
- function signed(vector) {
1034
- const dominant = Math.abs(vector[0]) >= Math.abs(vector[1]) ? vector[0] : vector[1];
1035
- return dominant >= 0 ? vector : [negated(vector[0]), negated(vector[1])];
1036
- }
1037
- function negated(value) {
1038
- return withoutNegativeZero(-value);
1027
+ return Math.min(Math.max(sum2 + normalCdf(-delta), 0), 1);
1039
1028
  }
1040
- function sdevOf(variance) {
1041
- return Math.sqrt(Math.max(0, variance));
1029
+ function nonCentralDensity(x, df, ncp) {
1030
+ if (!Number.isFinite(x)) {
1031
+ return 0;
1032
+ }
1033
+ if (Math.abs(x) > Math.sqrt(df * Number.EPSILON)) {
1034
+ const stepped = x * Math.sqrt((df + 2) / df);
1035
+ const difference = nonCentralProbability(stepped, df + 2, ncp) - nonCentralProbability(x, df, ncp);
1036
+ return df / Math.abs(x) * Math.abs(difference);
1037
+ }
1038
+ return Math.exp(-0.5 * Math.log(df) - logBeta(0.5, df / 2) - 0.5 * ncp * ncp);
1042
1039
  }
1043
- // src/core/logit.ts
1044
- var DEFAULT_LOGIT_EPSILON = 0.00000001;
1045
- var DEFAULT_LOGIT_MAX_ITERATIONS = 25;
1046
- var LINK_THRESHOLD = 30;
1047
- var BOUND = 10 * Number.EPSILON;
1048
- function logisticRegression(points, options = {}) {
1049
- const {
1050
- epsilon = DEFAULT_LOGIT_EPSILON,
1051
- maxIterations = DEFAULT_LOGIT_MAX_ITERATIONS
1052
- } = options;
1053
- const rows = completePointRows(points);
1054
- if (rows.length === 0) {
1055
- return null;
1040
+ var QUANTILE_MAX_STEPS = 200;
1041
+ function nonCentralQuantile(p, df, ncp) {
1042
+ if (p <= 0) {
1043
+ return Number.NEGATIVE_INFINITY;
1056
1044
  }
1057
- const complete = rows.map((row) => points[row]);
1058
- if (complete.some((point) => point.y !== 0 && point.y !== 1)) {
1059
- throw new RangeError("every outcome must be 0 or 1");
1045
+ if (p >= 1) {
1046
+ return Number.POSITIVE_INFINITY;
1060
1047
  }
1061
- const outcomes = complete.map((point) => point.y);
1062
- const design = complete.map((point) => [1, point.x]);
1063
- let fitted = outcomes.map((outcome) => (outcome + 0.5) / 2);
1064
- let predictors = fitted.map(logit);
1065
- let previousDeviance = totalDeviance(outcomes, fitted);
1066
- let deviance = previousDeviance;
1067
- let coefficients = [Number.NaN, null];
1068
- let rank = 0;
1069
- let iterations = 0;
1070
- let converged = false;
1071
- for (let iteration = 1;iteration <= maxIterations; iteration++) {
1072
- iterations = iteration;
1073
- const working = predictors.map((predictor, row) => {
1074
- const probability = fitted[row];
1075
- const slope = linkSlope(predictor);
1076
- return {
1077
- weight: slope * slope / (probability * (1 - probability)),
1078
- response: predictor + (outcomes[row] - probability) / slope
1079
- };
1080
- });
1081
- const step = leastSquares(design, working.map((row) => row.response), {
1082
- weights: working.map((row) => row.weight),
1083
- tolerance: Math.min(0.0000001, epsilon / 1000)
1084
- });
1085
- if (step.coefficients.some((coefficient) => coefficient !== null && !Number.isFinite(coefficient))) {
1048
+ let upper = Math.max(1, ncp);
1049
+ while (Number.isFinite(upper) && nonCentralProbability(upper, df, ncp) < p) {
1050
+ upper *= 2;
1051
+ }
1052
+ let lower = Math.min(-1, -ncp);
1053
+ while (Number.isFinite(lower) && nonCentralProbability(lower, df, ncp) > p) {
1054
+ lower *= 2;
1055
+ }
1056
+ let t = 0.5 * (lower + upper);
1057
+ for (let step = 0;step < QUANTILE_MAX_STEPS; step += 1) {
1058
+ const residual = nonCentralProbability(t, df, ncp) - p;
1059
+ if (residual < 0) {
1060
+ lower = t;
1061
+ } else {
1062
+ upper = t;
1063
+ }
1064
+ const density = nonCentralDensity(t, df, ncp);
1065
+ let next = density > 0 && Number.isFinite(density) ? t - residual / density : Number.NaN;
1066
+ if (!(next > lower) || !(next < upper)) {
1067
+ next = 0.5 * (lower + upper);
1068
+ }
1069
+ if (next === t) {
1086
1070
  break;
1087
1071
  }
1088
- coefficients = step.coefficients;
1089
- rank = step.rank;
1090
- predictors = design.map((row) => sum(zipWith(row, coefficients, (value, coefficient) => coefficient === null ? 0 : value * coefficient)));
1091
- fitted = predictors.map(linkInverse);
1092
- deviance = totalDeviance(outcomes, fitted);
1093
- if (Math.abs(deviance - previousDeviance) / (0.1 + Math.abs(deviance)) < epsilon) {
1094
- converged = true;
1072
+ const moved = Math.abs(next - t);
1073
+ t = next;
1074
+ if (moved <= Number.EPSILON * Math.abs(t)) {
1095
1075
  break;
1096
1076
  }
1097
- previousDeviance = deviance;
1098
1077
  }
1099
- const wholeMean = mean(outcomes);
1100
- const nullDeviance = totalDeviance(outcomes, outcomes.map(() => wholeMean));
1101
- const paddedFitted = new Array(points.length).fill(Number.NaN);
1102
- const paddedPredictors = new Array(points.length).fill(Number.NaN);
1103
- rows.forEach((row, survivor) => {
1104
- paddedFitted[row] = fitted[survivor];
1105
- paddedPredictors[row] = predictors[survivor];
1106
- });
1107
- return {
1108
- intercept: coefficients[0] ?? Number.NaN,
1109
- slope: coefficients[1] ?? null,
1110
- fitted: paddedFitted,
1111
- linearPredictors: paddedPredictors,
1112
- deviance,
1113
- nullDeviance,
1114
- aic: deviance + 2 * rank,
1115
- rank,
1116
- iterations,
1117
- converged,
1118
- saturated: fitted.some((probability) => probability > 1 - BOUND || probability < BOUND)
1119
- };
1120
- }
1121
- function predictLogit(fit, x) {
1122
- return linkInverse(fit.intercept + (fit.slope ?? 0) * x);
1123
- }
1124
- function logit(probability) {
1125
- return Math.log(probability / (1 - probability));
1078
+ return t;
1126
1079
  }
1127
- function linkInverse(predictor) {
1128
- const odds = predictor < -LINK_THRESHOLD ? Number.EPSILON : predictor > LINK_THRESHOLD ? 1 / Number.EPSILON : Math.exp(predictor);
1129
- return odds / (1 + odds);
1080
+
1081
+ // src/core/linalg/matrix.ts
1082
+ function matrix(values, options) {
1083
+ const { byrow = false, dimnames } = options;
1084
+ const [nrow, ncol] = extents(values.length, options);
1085
+ const dense = Float64Array.from(values);
1086
+ const data = new Float64Array(nrow * ncol);
1087
+ if (dense.length === 1 && data.length > 1) {
1088
+ data.fill(dense[0]);
1089
+ } else if (byrow) {
1090
+ dense.forEach((value, index) => {
1091
+ const i = Math.floor(index / ncol);
1092
+ const j = index % ncol;
1093
+ data[j * nrow + i] = value;
1094
+ });
1095
+ } else {
1096
+ data.set(dense);
1097
+ }
1098
+ return make(nrow, ncol, data, dimnames ?? null);
1130
1099
  }
1131
- function linkSlope(predictor) {
1132
- if (predictor > LINK_THRESHOLD || predictor < -LINK_THRESHOLD) {
1133
- return Number.EPSILON;
1100
+ function extents(length, { nrow, ncol }) {
1101
+ if (nrow === undefined && ncol === undefined) {
1102
+ throw new RangeError("matrix() needs nrow or ncol");
1134
1103
  }
1135
- const odds = Math.exp(predictor);
1136
- return odds / ((1 + odds) * (1 + odds));
1104
+ if (nrow !== undefined) {
1105
+ requireExtent(nrow, "nrow");
1106
+ }
1107
+ if (ncol !== undefined) {
1108
+ requireExtent(ncol, "ncol");
1109
+ }
1110
+ const scalar = length === 1;
1111
+ if (nrow !== undefined && ncol !== undefined) {
1112
+ if (!scalar && nrow * ncol !== length) {
1113
+ throw new RangeError(length > nrow * ncol ? "data is too long" : `data length [${length}] is not nrow * ncol [${nrow} * ${ncol}]`);
1114
+ }
1115
+ return [nrow, ncol];
1116
+ }
1117
+ const given = nrow ?? ncol;
1118
+ const name = nrow !== undefined ? "rows" : "columns";
1119
+ if (given === 0) {
1120
+ if (length !== 0) {
1121
+ throw new RangeError("data is too long");
1122
+ }
1123
+ return [0, 0];
1124
+ }
1125
+ const other = scalar ? 1 : length / given;
1126
+ if (!scalar && length % given !== 0) {
1127
+ throw new RangeError(`data length [${length}] is not a multiple of the number of ${name} [${given}]`);
1128
+ }
1129
+ return nrow !== undefined ? [nrow, other] : [other, given];
1137
1130
  }
1138
- function totalDeviance(outcomes, probabilities) {
1139
- return sum(zipWith(outcomes, probabilities, (outcome, probability) => 2 * (surprise(outcome, probability) + surprise(1 - outcome, 1 - probability))));
1131
+ function requireExtent(value, name) {
1132
+ if (!Number.isSafeInteger(value) || value < 0) {
1133
+ throw new RangeError(`${name} must be a non-negative integer, got ${value}`);
1134
+ }
1140
1135
  }
1141
- function surprise(outcome, probability) {
1142
- return outcome !== 0 ? outcome * Math.log(outcome / probability) : 0;
1136
+ function make(nrow, ncol, data, dimnames) {
1137
+ if (data.length !== nrow * ncol) {
1138
+ throw new RangeError(`data length [${data.length}] is not nrow * ncol [${nrow} * ${ncol}]`);
1139
+ }
1140
+ if (dimnames !== null) {
1141
+ const [rows, columns] = dimnames;
1142
+ if (rows !== null && rows.length !== nrow) {
1143
+ throw new RangeError(`length of dimnames [1] (${rows.length}) not equal to array extent (${nrow})`);
1144
+ }
1145
+ if (columns !== null && columns.length !== ncol) {
1146
+ throw new RangeError(`length of dimnames [2] (${columns.length}) not equal to array extent (${ncol})`);
1147
+ }
1148
+ dimnames = rows === null && columns === null ? null : [rows === null ? null : [...rows], columns === null ? null : [...columns]];
1149
+ }
1150
+ return { nrow, ncol, data, dimnames };
1143
1151
  }
1144
- // src/core/special.ts
1145
- var LANCZOS_G = 607 / 128;
1146
- var LANCZOS_LEAD = 0.9999999999999971;
1147
- var LANCZOS_TAIL = [
1148
- 57.15623566586292,
1149
- -59.59796035547549,
1150
- 14.136097974741746,
1151
- -0.4919138160976202,
1152
- 0.00003399464998481189,
1153
- 0.00004652362892704858,
1154
- -0.00009837447530487956,
1155
- 0.0001580887032249125,
1156
- -0.00021026444172410488,
1157
- 0.00021743961811521265,
1158
- -0.0001643181065367639,
1159
- 0.00008441822398385275,
1160
- -0.000026190838401581408,
1161
- 0.0000036899182659531625
1162
- ];
1163
- var LOG_SQRT_TWO_PI = 0.5 * Math.log(2 * Math.PI);
1164
- function lanczosSeries(x) {
1165
- return LANCZOS_LEAD + sum(LANCZOS_TAIL.map((coefficient, index) => coefficient / (x + index)));
1152
+ function fromRows(rows) {
1153
+ const nrow = rows.length;
1154
+ const first = rows[0];
1155
+ if (first === undefined || first.length === 0) {
1156
+ throw new RangeError("fromRows() needs at least one row with one value");
1157
+ }
1158
+ const ncol = first.length;
1159
+ const ragged = rows.findIndex((row) => row.length !== ncol);
1160
+ if (ragged !== -1) {
1161
+ throw new RangeError(`every row needs ${ncol} values; row ${ragged} has ${rows[ragged].length}`);
1162
+ }
1163
+ const data = new Float64Array(nrow * ncol);
1164
+ rows.forEach((row, i) => {
1165
+ Float64Array.from(row).forEach((value, j) => {
1166
+ data[j * nrow + i] = value;
1167
+ });
1168
+ });
1169
+ return make(nrow, ncol, data, null);
1170
+ }
1171
+ function fromColumns(columns) {
1172
+ const ncol = columns.length;
1173
+ const first = columns[0];
1174
+ if (first === undefined || first.length === 0) {
1175
+ throw new RangeError("fromColumns() needs at least one column with one value");
1176
+ }
1177
+ const nrow = first.length;
1178
+ const ragged = columns.findIndex((column) => column.length !== nrow);
1179
+ if (ragged !== -1) {
1180
+ throw new RangeError(`every column needs ${nrow} values; column ${ragged} has ${columns[ragged].length}`);
1181
+ }
1182
+ const data = new Float64Array(nrow * ncol);
1183
+ columns.forEach((column, j) => {
1184
+ data.set(column, j * nrow);
1185
+ });
1186
+ return make(nrow, ncol, data, null);
1166
1187
  }
1167
- function logGamma(x) {
1168
- if (!(x > 0)) {
1169
- return Number.NaN;
1188
+ function at(m, i, j) {
1189
+ if (!Number.isInteger(i) || i < 0 || i >= m.nrow) {
1190
+ throw new RangeError(`row index ${i} is outside 0..${m.nrow - 1}`);
1170
1191
  }
1171
- const shifted = x + LANCZOS_G - 0.5;
1172
- return LOG_SQRT_TWO_PI + (x - 0.5) * Math.log(shifted) - shifted + Math.log(lanczosSeries(x));
1192
+ if (!Number.isInteger(j) || j < 0 || j >= m.ncol) {
1193
+ throw new RangeError(`column index ${j} is outside 0..${m.ncol - 1}`);
1194
+ }
1195
+ return m.data[j * m.nrow + i];
1173
1196
  }
1174
- function logBeta(a, b) {
1175
- if (!(a > 0) || !(b > 0)) {
1176
- return Number.NaN;
1197
+ function row(m, i) {
1198
+ if (!Number.isInteger(i) || i < 0 || i >= m.nrow) {
1199
+ throw new RangeError(`row index ${i} is outside 0..${m.nrow - 1}`);
1177
1200
  }
1178
- const shiftedSum = a + b + LANCZOS_G - 0.5;
1179
- return LOG_SQRT_TWO_PI - (LANCZOS_G - 0.5) + Math.log(lanczosSeries(a)) + Math.log(lanczosSeries(b)) - Math.log(lanczosSeries(a + b)) + (a - 0.5) * Math.log1p(-b / shiftedSum) + (b - 0.5) * Math.log1p(-a / shiftedSum) - 0.5 * Math.log(shiftedSum);
1201
+ return Array.from({ length: m.ncol }, (_, j) => m.data[j * m.nrow + i]);
1180
1202
  }
1181
- var FRACTION_MAX_STEPS = 400;
1182
- var FRACTION_EPSILON = 0.0000000000000003;
1183
- var FRACTION_FLOOR = 0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001;
1184
- function betaContinuedFraction(x, a, b) {
1185
- const total = a + b;
1186
- const aPlus = a + 1;
1187
- const aMinus = a - 1;
1188
- let c = 1;
1189
- let d = 1 - total * x / aPlus;
1190
- if (Math.abs(d) < FRACTION_FLOOR) {
1191
- d = FRACTION_FLOOR;
1203
+ function column(m, j) {
1204
+ if (!Number.isInteger(j) || j < 0 || j >= m.ncol) {
1205
+ throw new RangeError(`column index ${j} is outside 0..${m.ncol - 1}`);
1192
1206
  }
1193
- d = 1 / d;
1194
- let value = d;
1195
- for (let step = 1;step <= FRACTION_MAX_STEPS; step += 1) {
1196
- const twice = 2 * step;
1197
- const even = step * (b - step) * x / ((aMinus + twice) * (a + twice));
1198
- d = 1 + even * d;
1199
- if (Math.abs(d) < FRACTION_FLOOR) {
1200
- d = FRACTION_FLOOR;
1207
+ return Array.from(m.data.subarray(j * m.nrow, (j + 1) * m.nrow));
1208
+ }
1209
+ function toRows(m) {
1210
+ return Array.from({ length: m.nrow }, (_, i) => row(m, i));
1211
+ }
1212
+ function toColumns(m) {
1213
+ return Array.from({ length: m.ncol }, (_, j) => column(m, j));
1214
+ }
1215
+ function fromFrame(data, columns) {
1216
+ const nrow = frameRows(data);
1217
+ const names = columns ?? numericColumns(data);
1218
+ const buffer = new Float64Array(nrow * names.length);
1219
+ names.forEach((name, j) => {
1220
+ buffer.set(requireNumericColumn(data, name, "columns"), j * nrow);
1221
+ });
1222
+ return make(nrow, names.length, buffer, names.length === 0 ? null : [null, [...names]]);
1223
+ }
1224
+
1225
+ // src/core/linalg/modelMatrix.ts
1226
+ function modelMatrix(data, spec) {
1227
+ const { outcome, intercept = true } = spec;
1228
+ const rowCount = frameRows(data);
1229
+ const terms = orderTerms(spec.terms);
1230
+ const columns = new Map;
1231
+ const read = (name, role) => {
1232
+ const held = columns.get(name);
1233
+ if (held !== undefined) {
1234
+ return held;
1201
1235
  }
1202
- c = 1 + even / c;
1203
- if (Math.abs(c) < FRACTION_FLOOR) {
1204
- c = FRACTION_FLOOR;
1236
+ const column2 = requireNumericColumn(data, name, role);
1237
+ columns.set(name, column2);
1238
+ return column2;
1239
+ };
1240
+ if (outcome !== undefined) {
1241
+ read(outcome, "outcome");
1242
+ }
1243
+ terms.forEach((factors) => {
1244
+ factors.forEach((name) => read(name, "terms"));
1245
+ });
1246
+ const involved = [...columns.values()];
1247
+ const rows = Array.from({ length: rowCount }, (_, row2) => row2).filter((row2) => involved.every((column2) => Number.isFinite(column2[row2])));
1248
+ const termColumns = terms.map((factors) => rows.map((row2) => factors.reduce((product, name) => product * columns.get(name)[row2], 1)));
1249
+ const design = intercept ? [rows.map(() => 1), ...termColumns] : termColumns;
1250
+ const names = [
1251
+ ...intercept ? ["(Intercept)"] : [],
1252
+ ...terms.map((factors) => factors.join(":"))
1253
+ ];
1254
+ const assign = [
1255
+ ...intercept ? [0] : [],
1256
+ ...terms.map((_, index) => index + 1)
1257
+ ];
1258
+ const n = rows.length;
1259
+ const p = design.length;
1260
+ const buffer = new Float64Array(n * p);
1261
+ design.forEach((column2, j) => {
1262
+ buffer.set(column2, j * n);
1263
+ });
1264
+ return {
1265
+ matrix: make(n, p, buffer, p === 0 ? null : [null, names]),
1266
+ rows,
1267
+ assign,
1268
+ termLabels: names.slice(intercept ? 1 : 0)
1269
+ };
1270
+ }
1271
+ function orderTerms(terms) {
1272
+ const seen = new Set;
1273
+ const unique = [];
1274
+ terms.forEach((term) => {
1275
+ const factors = typeof term === "string" ? [term] : term;
1276
+ if (factors.length === 0) {
1277
+ throw new RangeError("an interaction term needs at least one column name");
1205
1278
  }
1206
- d = 1 / d;
1207
- value *= d * c;
1208
- const odd = -(a + step) * (total + step) * x / ((a + twice) * (aPlus + twice));
1209
- d = 1 + odd * d;
1210
- if (Math.abs(d) < FRACTION_FLOOR) {
1211
- d = FRACTION_FLOOR;
1279
+ const key = [...factors].sort().join("\x00");
1280
+ if (!seen.has(key)) {
1281
+ seen.add(key);
1282
+ unique.push(factors);
1212
1283
  }
1213
- c = 1 + odd / c;
1214
- if (Math.abs(c) < FRACTION_FLOOR) {
1215
- c = FRACTION_FLOOR;
1284
+ });
1285
+ return unique.map((factors, index) => ({ factors, index })).sort((a, b) => a.factors.length - b.factors.length || a.index - b.index).map(({ factors }) => factors);
1286
+ }
1287
+
1288
+ // src/core/linalg/namedVector.ts
1289
+ function namedVector(names, values) {
1290
+ if (names.length !== values.length) {
1291
+ throw new RangeError(`a named vector needs one name per value: ${names.length} names, ${values.length} values`);
1292
+ }
1293
+ return { names: [...names], values: [...values] };
1294
+ }
1295
+ function lookup(v, name) {
1296
+ const index = v.names.indexOf(name);
1297
+ return index === -1 ? undefined : v.values[index];
1298
+ }
1299
+
1300
+ // src/core/linalg/ops.ts
1301
+ function t(m) {
1302
+ const { nrow, ncol } = m;
1303
+ const data = new Float64Array(nrow * ncol);
1304
+ for (let j = 0;j < ncol; j++) {
1305
+ for (let i = 0;i < nrow; i++) {
1306
+ data[i * ncol + j] = m.data[j * nrow + i];
1216
1307
  }
1217
- d = 1 / d;
1218
- const delta = d * c;
1219
- value *= delta;
1220
- if (Math.abs(delta - 1) < FRACTION_EPSILON) {
1221
- break;
1308
+ }
1309
+ const dimnames = m.dimnames === null ? null : [m.dimnames[1], m.dimnames[0]];
1310
+ return make(ncol, nrow, data, dimnames);
1311
+ }
1312
+ var transpose = t;
1313
+ function matmul(x, y) {
1314
+ const [left, right] = conformProduct(x, y);
1315
+ if (left.ncol !== right.nrow) {
1316
+ throw new RangeError(`non-conformable arguments: ${left.nrow} x ${left.ncol} %*% ${right.nrow} x ${right.ncol}`);
1317
+ }
1318
+ const data = product(left, right);
1319
+ return make(left.nrow, right.ncol, data, productDimnames(left, right));
1320
+ }
1321
+ function conformProduct(x, y) {
1322
+ if (isMatrix(x)) {
1323
+ if (isMatrix(y)) {
1324
+ return [x, y];
1325
+ }
1326
+ return [x, y.length === x.ncol ? asColumn(y) : asRow(y)];
1327
+ }
1328
+ if (isMatrix(y)) {
1329
+ return [x.length === y.nrow ? asRow(x) : asColumn(x), y];
1330
+ }
1331
+ return [asRow(x), x.length === 1 ? asRow(y) : asColumn(y)];
1332
+ }
1333
+ function crossprod(x, y = x) {
1334
+ const left = asColumn(x);
1335
+ const right = isMatrix(y) ? y : y.length === left.nrow ? asColumn(y) : asRow(y);
1336
+ if (left.nrow !== right.nrow) {
1337
+ throw new RangeError(`non-conformable arguments: crossprod of ${left.nrow} x ${left.ncol} and ${right.nrow} x ${right.ncol}`);
1338
+ }
1339
+ return matmul(t(left), right);
1340
+ }
1341
+ function tcrossprod(x, y = x) {
1342
+ const bothVectors = !isMatrix(x) && !isMatrix(y);
1343
+ const left = isMatrix(x) ? x : isMatrix(y) && y.ncol === x.length ? asRow(x) : asColumn(x);
1344
+ const right = isMatrix(y) ? y : !bothVectors && left.nrow === 1 ? asRow(y) : asColumn(y);
1345
+ if (left.ncol !== right.ncol) {
1346
+ throw new RangeError(`non-conformable arguments: tcrossprod of ${left.nrow} x ${left.ncol} and ${right.nrow} x ${right.ncol}`);
1347
+ }
1348
+ return matmul(left, t(right));
1349
+ }
1350
+ function product(x, y) {
1351
+ const { nrow, ncol: inner } = x;
1352
+ const { ncol } = y;
1353
+ const data = new Float64Array(nrow * ncol);
1354
+ for (let j = 0;j < ncol; j++) {
1355
+ for (let k = 0;k < inner; k++) {
1356
+ const factor = y.data[j * y.nrow + k];
1357
+ for (let i = 0;i < nrow; i++) {
1358
+ data[j * nrow + i] = fusedMultiplyAdd(x.data[k * nrow + i], factor, data[j * nrow + i]);
1359
+ }
1222
1360
  }
1223
1361
  }
1224
- return value;
1362
+ return data;
1225
1363
  }
1226
- function incompleteBeta(x, a, b) {
1227
- if (Number.isNaN(x)) {
1228
- return Number.NaN;
1364
+ function productDimnames(x, y) {
1365
+ const rows = x.dimnames?.[0] ?? null;
1366
+ const columns = y.dimnames?.[1] ?? null;
1367
+ return rows === null && columns === null ? null : [rows, columns];
1368
+ }
1369
+ function asColumn(value) {
1370
+ if (isMatrix(value)) {
1371
+ return value;
1229
1372
  }
1230
- if (x <= 0) {
1231
- return 0;
1373
+ return make(value.length, 1, Float64Array.from(value), null);
1374
+ }
1375
+ function asRow(value) {
1376
+ return make(1, value.length, Float64Array.from(value), null);
1377
+ }
1378
+ function isMatrix(value) {
1379
+ if (Array.isArray(value)) {
1380
+ return false;
1232
1381
  }
1233
- if (x >= 1) {
1234
- return 1;
1382
+ if (typeof value === "object" && value !== null && "nrow" in value && "ncol" in value && "data" in value) {
1383
+ return true;
1235
1384
  }
1236
- return incompleteBetaSplit(x, 1 - x, a, b);
1385
+ throw new TypeError("expected a Matrix or an array of numbers");
1237
1386
  }
1238
- function incompleteBetaSplit(x, complement, a, b) {
1239
- if (Number.isNaN(x) || Number.isNaN(complement) || !(a > 0) || !(b > 0)) {
1240
- return Number.NaN;
1387
+ function cbind(...parts) {
1388
+ const matrices = parts.map(asColumn);
1389
+ const first = matrices[0];
1390
+ if (first === undefined) {
1391
+ throw new RangeError("cbind() needs at least one argument");
1241
1392
  }
1242
- if (x <= 0) {
1243
- return 0;
1393
+ const nrow = first.nrow;
1394
+ matrices.forEach((m, index) => {
1395
+ if (m.nrow !== nrow) {
1396
+ throw new RangeError(isMatrix(parts[index]) ? `number of rows of matrices must match (see arg ${index + 1})` : `number of rows of result is not a multiple of vector length (arg ${index + 1})`);
1397
+ }
1398
+ });
1399
+ const ncol = matrices.reduce((total, m) => total + m.ncol, 0);
1400
+ const data = new Float64Array(nrow * ncol);
1401
+ let offset = 0;
1402
+ matrices.forEach((m) => {
1403
+ data.set(m.data, offset);
1404
+ offset += m.data.length;
1405
+ });
1406
+ const rows = matrices.find((m) => m.dimnames?.[0])?.dimnames?.[0] ?? null;
1407
+ const columns = boundNames(matrices.map((m) => ({ names: m.dimnames?.[1] ?? null, count: m.ncol })));
1408
+ return make(nrow, ncol, data, rows === null && columns === null ? null : [rows, columns]);
1409
+ }
1410
+ function rbind(...parts) {
1411
+ if (parts.length === 0) {
1412
+ throw new RangeError("rbind() needs at least one argument");
1413
+ }
1414
+ const transposed = parts.map((part) => isMatrix(part) ? t(part) : part);
1415
+ try {
1416
+ return t(cbind(...transposed));
1417
+ } catch (error) {
1418
+ if (error instanceof RangeError) {
1419
+ throw new RangeError(error.message.replace("number of rows", "number of columns"));
1420
+ }
1421
+ throw error;
1244
1422
  }
1245
- if (complement <= 0) {
1246
- return 1;
1423
+ }
1424
+ function boundNames(parts) {
1425
+ if (parts.every((part) => part.names === null)) {
1426
+ return null;
1247
1427
  }
1248
- const logX = complement < 0.5 ? Math.log1p(-complement) : Math.log(x);
1249
- const logComplement = x < 0.5 ? Math.log1p(-x) : Math.log(complement);
1250
- const front = Math.exp(a * logX + b * logComplement - logBeta(a, b));
1251
- if (x < (a + 1) / (a + b + 2)) {
1252
- return front * betaContinuedFraction(x, a, b) / a;
1428
+ return parts.flatMap((part) => part.names ?? new Array(part.count).fill(""));
1429
+ }
1430
+ function diag(arg) {
1431
+ if (typeof arg === "number") {
1432
+ return identity(arg);
1253
1433
  }
1254
- return 1 - front * betaContinuedFraction(complement, b, a) / b;
1434
+ if (Array.isArray(arg)) {
1435
+ const values = arg;
1436
+ const n2 = values.length;
1437
+ const data = new Float64Array(n2 * n2);
1438
+ values.forEach((value, i) => {
1439
+ data[i * n2 + i] = value;
1440
+ });
1441
+ return make(n2, n2, data, null);
1442
+ }
1443
+ const m = arg;
1444
+ const n = Math.min(m.nrow, m.ncol);
1445
+ return Array.from({ length: n }, (_, i) => m.data[i * m.nrow + i]);
1255
1446
  }
1256
- var GAMMA_MAX_STEPS = 1000;
1257
- var GAMMA_EPSILON = 0.0000000000000003;
1258
- function lowerGammaSeries(a, x) {
1259
- let term = 1 / a;
1260
- let total = term;
1261
- for (let step = 1;step <= GAMMA_MAX_STEPS; step += 1) {
1262
- term *= x / (a + step);
1263
- total += term;
1264
- if (Math.abs(term) < Math.abs(total) * GAMMA_EPSILON) {
1265
- break;
1447
+ function identity(order) {
1448
+ if (!Number.isInteger(order) || order < 0) {
1449
+ throw new RangeError(`order must be a non-negative integer, got ${order}`);
1450
+ }
1451
+ const data = new Float64Array(order * order);
1452
+ for (let i = 0;i < order; i++) {
1453
+ data[i * order + i] = 1;
1454
+ }
1455
+ return make(order, order, data, null);
1456
+ }
1457
+
1458
+ // src/core/linalg/qr.ts
1459
+ var DEFAULT_QR_TOLERANCE = 0.0000001;
1460
+ function qr(x, options = {}) {
1461
+ const { tolerance = DEFAULT_QR_TOLERANCE } = options;
1462
+ if (!(tolerance >= 0)) {
1463
+ throw new RangeError(`tolerance must be a non-negative number, got ${tolerance}`);
1464
+ }
1465
+ if (!isMatrix(x)) {
1466
+ throw new TypeError("expected a Matrix");
1467
+ }
1468
+ if (!x.data.every(Number.isFinite)) {
1469
+ throw new RangeError("NA/NaN/Inf in foreign function call (arg 1)");
1470
+ }
1471
+ const { nrow, ncol } = x;
1472
+ const columns = Array.from({ length: ncol }, (_, j) => Array.from(x.data.subarray(j * nrow, (j + 1) * nrow)));
1473
+ const { householders, pivot, rank } = decompose(columns, tolerance, nrow);
1474
+ const data = new Float64Array(nrow * ncol);
1475
+ columns.forEach((column2, j) => {
1476
+ data.set(column2, j * nrow);
1477
+ });
1478
+ const rows = x.dimnames?.[0] ?? null;
1479
+ const names = x.dimnames?.[1] ?? null;
1480
+ const dimnames = rows === null && names === null ? null : [rows, names === null ? null : pivot.map((from) => names[from])];
1481
+ return { qr: make(nrow, ncol, data, dimnames), qraux: householders, pivot, rank };
1482
+ }
1483
+ function qrCoef(q, y) {
1484
+ if (isMatrix(y)) {
1485
+ requireRows(q, y.nrow);
1486
+ const width = q.qr.ncol;
1487
+ const data = new Float64Array(width * y.ncol);
1488
+ for (let j = 0;j < y.ncol; j++) {
1489
+ const solved = coefficientsOf(q, Array.from(y.data.subarray(j * y.nrow, (j + 1) * y.nrow)));
1490
+ solved.forEach((value, i) => {
1491
+ data[j * width + i] = value ?? Number.NaN;
1492
+ });
1266
1493
  }
1494
+ return make(width, y.ncol, data, readerDimnames(originalColumnNames(q), y));
1267
1495
  }
1268
- return total * Math.exp(-x + a * Math.log(x) - logGamma(a));
1496
+ requireRows(q, y.length);
1497
+ return coefficientsOf(q, y);
1269
1498
  }
1270
- function upperGammaFraction(a, x) {
1271
- let b = x + 1 - a;
1272
- let c = 1 / FRACTION_FLOOR;
1273
- let d = 1 / b;
1274
- let value = d;
1275
- for (let step = 1;step <= GAMMA_MAX_STEPS; step += 1) {
1276
- const numerator = -step * (step - a);
1277
- b += 2;
1278
- d = numerator * d + b;
1279
- if (Math.abs(d) < FRACTION_FLOOR) {
1280
- d = FRACTION_FLOOR;
1499
+ function coefficientsOf(q, y) {
1500
+ const qty = transformed(q, y, true);
1501
+ const solved = backSubstitute(q.qr, qty, q.rank);
1502
+ const coefficients = new Array(q.qr.ncol).fill(null);
1503
+ q.pivot.slice(0, q.rank).forEach((column2, position) => {
1504
+ coefficients[column2] = solved[position];
1505
+ });
1506
+ return coefficients;
1507
+ }
1508
+ function originalColumnNames(q) {
1509
+ const pivoted = q.qr.dimnames?.[1] ?? null;
1510
+ if (pivoted === null) {
1511
+ return null;
1512
+ }
1513
+ const names = new Array(pivoted.length);
1514
+ q.pivot.forEach((original, position) => {
1515
+ names[original] = pivoted[position];
1516
+ });
1517
+ return names;
1518
+ }
1519
+ function qrFitted(q, y) {
1520
+ return perColumn(q, y, (column2) => transformed(q, transformed(q, column2, true).map((value, index) => index < q.rank ? value : 0), false));
1521
+ }
1522
+ function qrResid(q, y) {
1523
+ return perColumn(q, y, (column2) => transformed(q, transformed(q, column2, true).map((value, index) => index < q.rank ? 0 : value), false));
1524
+ }
1525
+ function qrQty(q, y) {
1526
+ return perColumn(q, y, (column2) => transformed(q, column2, true));
1527
+ }
1528
+ function qrQy(q, y) {
1529
+ return perColumn(q, y, (column2) => transformed(q, column2, false));
1530
+ }
1531
+ function perColumn(q, y, read) {
1532
+ if (isMatrix(y)) {
1533
+ requireRows(q, y.nrow);
1534
+ const data = new Float64Array(y.nrow * y.ncol);
1535
+ for (let j = 0;j < y.ncol; j++) {
1536
+ data.set(read(Array.from(y.data.subarray(j * y.nrow, (j + 1) * y.nrow))), j * y.nrow);
1281
1537
  }
1282
- c = b + numerator / c;
1283
- if (Math.abs(c) < FRACTION_FLOOR) {
1284
- c = FRACTION_FLOOR;
1538
+ return make(y.nrow, y.ncol, data, readerDimnames(null, y));
1539
+ }
1540
+ requireRows(q, y.length);
1541
+ return read(y);
1542
+ }
1543
+ function readerDimnames(rows, y) {
1544
+ const columns = y.dimnames?.[1] ?? null;
1545
+ return rows === null && columns === null ? null : [rows, columns];
1546
+ }
1547
+ function transformed(q, y, transpose2) {
1548
+ const result = [...y];
1549
+ const count = reflectorCount(q);
1550
+ if (transpose2) {
1551
+ for (let step = 0;step < count; step++) {
1552
+ applyReflector(q, step, result);
1285
1553
  }
1286
- d = 1 / d;
1287
- const delta = d * c;
1288
- value *= delta;
1289
- if (Math.abs(delta - 1) < GAMMA_EPSILON) {
1290
- break;
1554
+ } else {
1555
+ for (let step = count - 1;step >= 0; step--) {
1556
+ applyReflector(q, step, result);
1291
1557
  }
1292
1558
  }
1293
- return value * Math.exp(-x + a * Math.log(x) - logGamma(a));
1559
+ return result;
1294
1560
  }
1295
- function upperGamma(a, x) {
1296
- if (x <= 0) {
1297
- return 1;
1298
- }
1299
- if (!Number.isFinite(x)) {
1300
- return 0;
1561
+ function qrQ(q) {
1562
+ const { nrow, ncol } = q.qr;
1563
+ const width = Math.min(nrow, ncol);
1564
+ const data = new Float64Array(nrow * width);
1565
+ for (let j = 0;j < width; j++) {
1566
+ const unit = new Array(nrow).fill(0);
1567
+ unit[j] = 1;
1568
+ data.set(transformed(q, unit, false), j * nrow);
1569
+ }
1570
+ return make(nrow, width, data, null);
1571
+ }
1572
+ function qrR(q) {
1573
+ const { nrow, ncol } = q.qr;
1574
+ const height = Math.min(nrow, ncol);
1575
+ const data = new Float64Array(height * ncol);
1576
+ for (let j = 0;j < ncol; j++) {
1577
+ for (let i = 0;i <= Math.min(j, height - 1); i++) {
1578
+ data[j * height + i] = q.qr.data[j * nrow + i];
1579
+ }
1301
1580
  }
1302
- return x < a + 1 ? 1 - lowerGammaSeries(a, x) : upperGammaFraction(a, x);
1581
+ const rows = q.qr.dimnames?.[0]?.slice(0, height) ?? null;
1582
+ const columns = q.qr.dimnames?.[1] ?? null;
1583
+ return make(height, ncol, data, rows === null && columns === null ? null : [rows, columns]);
1303
1584
  }
1304
- function normalCdf(z) {
1305
- if (Number.isNaN(z)) {
1306
- return Number.NaN;
1585
+ function requireRows(q, rows) {
1586
+ if (rows !== q.qr.nrow) {
1587
+ throw new RangeError("'qr' and 'y' must have the same number of rows");
1307
1588
  }
1308
- const lower = 0.5 * upperGamma(0.5, 0.5 * z * z);
1309
- return z > 0 ? 1 - lower : lower;
1310
1589
  }
1311
- var BELOW_ONE = 1 - Number.EPSILON / 2;
1312
- var INVERSE_MAX_STEPS = 200;
1313
- function inverseGuess(p, a, b) {
1314
- if (a >= 1 && b >= 1) {
1315
- const tail = p < 0.5 ? p : 1 - p;
1316
- const t = Math.sqrt(-2 * Math.log(tail));
1317
- const normal = (p < 0.5 ? -1 : 1) * ((2.30753 + t * 0.27061) / (1 + t * (0.99229 + t * 0.04481)) - t);
1318
- const scale = (normal * normal - 3) / 6;
1319
- const harmonic = 2 / (1 / (2 * a - 1) + 1 / (2 * b - 1));
1320
- const w = normal * Math.sqrt(scale + harmonic) / harmonic - (1 / (2 * b - 1) - 1 / (2 * a - 1)) * (scale + 5 / 6 - 2 / (3 * harmonic));
1321
- return a / (a + b * Math.exp(2 * w));
1590
+ function reflectorCount(q) {
1591
+ return Math.min(q.rank, q.qr.nrow - 1);
1592
+ }
1593
+ function applyReflector(q, step, vector) {
1594
+ const leading = q.qraux[step];
1595
+ if (leading === 0) {
1596
+ return;
1322
1597
  }
1323
- const lower = Math.exp(a * Math.log(a / (a + b))) / a;
1324
- const upper = Math.exp(b * Math.log(b / (a + b))) / b;
1325
- const total = lower + upper;
1326
- if (p < lower / total) {
1327
- return Math.pow(a * total * p, 1 / a);
1598
+ const { nrow } = q.qr;
1599
+ const column2 = q.qr.data.subarray(step * nrow, (step + 1) * nrow);
1600
+ let inner = leading * vector[step];
1601
+ for (let row2 = step + 1;row2 < nrow; row2++) {
1602
+ inner = fusedMultiplyAdd(column2[row2], vector[row2], inner);
1328
1603
  }
1329
- return 1 - Math.pow(b * total * (1 - p), 1 / b);
1330
- }
1331
- function inverseIncompleteBeta(p, a, b) {
1332
- if (Number.isNaN(p) || !(a > 0) || !(b > 0)) {
1333
- return Number.NaN;
1604
+ const factor = -inner / leading;
1605
+ vector[step] = fusedMultiplyAdd(factor, leading, vector[step]);
1606
+ for (let row2 = step + 1;row2 < nrow; row2++) {
1607
+ vector[row2] = fusedMultiplyAdd(factor, column2[row2], vector[row2]);
1334
1608
  }
1335
- if (p <= 0) {
1336
- return 0;
1609
+ }
1610
+ function decompose(columns, tolerance, rows) {
1611
+ const width = columns.length;
1612
+ const pivot = columns.map((_, column2) => column2);
1613
+ const qraux = columns.map((column2) => norm(column2, 0));
1614
+ const lastNorms = [...qraux];
1615
+ const originalNorms = qraux.map((value) => value || 1);
1616
+ let k = width + 1;
1617
+ for (let step = 0;step < Math.min(rows, width); step++) {
1618
+ while (step + 1 < k && qraux[step] < originalNorms[step] * tolerance) {
1619
+ moveToEnd(columns, step);
1620
+ moveToEnd(pivot, step);
1621
+ moveToEnd(qraux, step);
1622
+ moveToEnd(lastNorms, step);
1623
+ moveToEnd(originalNorms, step);
1624
+ k -= 1;
1625
+ }
1626
+ if (step === rows - 1) {
1627
+ continue;
1628
+ }
1629
+ reflect(columns, qraux, lastNorms, step, rows);
1337
1630
  }
1338
- if (p >= 1) {
1339
- return 1;
1631
+ return { householders: qraux, pivot, rank: Math.min(k - 1, rows) };
1632
+ }
1633
+ function reflect(columns, qraux, lastNorms, step, rows) {
1634
+ const column2 = columns[step];
1635
+ const length = norm(column2, step);
1636
+ if (length === 0) {
1637
+ return;
1340
1638
  }
1341
- const logBetaValue = logBeta(a, b);
1342
- let lower = 0;
1343
- let upper = 1;
1344
- let x = inverseGuess(p, a, b);
1345
- if (!(x > 0) || !(x < 1)) {
1346
- x = 0.5;
1639
+ const pivotNorm = column2[step] < 0 ? -length : length;
1640
+ const reciprocal = 1 / pivotNorm;
1641
+ for (let row2 = step;row2 < rows; row2++) {
1642
+ column2[row2] = column2[row2] * reciprocal;
1347
1643
  }
1348
- for (let step = 0;step < INVERSE_MAX_STEPS; step += 1) {
1349
- const residual = incompleteBeta(x, a, b) - p;
1350
- if (residual < 0) {
1351
- lower = x;
1352
- } else {
1353
- upper = x;
1644
+ const leading = 1 + column2[step];
1645
+ column2[step] = leading;
1646
+ for (let index = step + 1;index < columns.length; index++) {
1647
+ const other = columns[index];
1648
+ let inner = 0;
1649
+ for (let row2 = step;row2 < rows; row2++) {
1650
+ inner = fusedMultiplyAdd(column2[row2], other[row2], inner);
1354
1651
  }
1355
- const density = Math.exp((a - 1) * Math.log(x) + (b - 1) * Math.log1p(-x) - logBetaValue);
1356
- let next = density > 0 && Number.isFinite(density) ? x - residual / density : Number.NaN;
1357
- if (!(next > lower) || !(next < upper)) {
1358
- next = 0.5 * (lower + upper);
1652
+ const factor = -inner / leading;
1653
+ for (let row2 = step;row2 < rows; row2++) {
1654
+ other[row2] = fusedMultiplyAdd(factor, column2[row2], other[row2]);
1359
1655
  }
1360
- if (next === x) {
1361
- break;
1656
+ const running = qraux[index];
1657
+ if (running !== 0) {
1658
+ const ratio = Math.abs(other[step]) / running;
1659
+ const remaining = Math.max(1 - ratio * ratio, 0);
1660
+ if (Math.abs(remaining) < 0.000001) {
1661
+ qraux[index] = norm(other, step + 1);
1662
+ lastNorms[index] = qraux[index];
1663
+ } else {
1664
+ qraux[index] = running * Math.sqrt(remaining);
1665
+ }
1362
1666
  }
1363
- const moved = Math.abs(next - x);
1364
- x = next;
1365
- if (moved <= Number.EPSILON * x) {
1366
- break;
1667
+ }
1668
+ qraux[step] = leading;
1669
+ column2[step] = -pivotNorm;
1670
+ }
1671
+ function backSubstitute(compact, response, rank) {
1672
+ const { nrow } = compact;
1673
+ const entry = (i, j) => compact.data[j * nrow + i];
1674
+ const solved = response.slice(0, rank);
1675
+ for (let column2 = rank - 1;column2 >= 0; column2--) {
1676
+ const value = solved[column2] / entry(column2, column2);
1677
+ solved[column2] = value;
1678
+ for (let row2 = 0;row2 < column2; row2++) {
1679
+ solved[row2] = fusedMultiplyAdd(-value, entry(row2, column2), solved[row2]);
1367
1680
  }
1368
1681
  }
1369
- return Math.min(x, BELOW_ONE);
1682
+ return solved;
1683
+ }
1684
+ function moveToEnd(track, from) {
1685
+ const [moved] = track.splice(from, 1);
1686
+ track.push(moved);
1687
+ }
1688
+ function norm(column2, from) {
1689
+ let squares = 0;
1690
+ for (let row2 = from;row2 < column2.length; row2++) {
1691
+ const value = column2[row2];
1692
+ squares = fusedMultiplyAdd(value, value, squares);
1693
+ }
1694
+ return Math.sqrt(squares);
1370
1695
  }
1371
1696
 
1372
- // src/core/tdist.ts
1373
- function isNonCentral(ncp) {
1374
- return ncp !== undefined && ncp !== 0;
1697
+ // src/core/linalg/lm.ts
1698
+ function lm(data, options) {
1699
+ const { outcome, intercept = true, tolerance = DEFAULT_QR_TOLERANCE } = options;
1700
+ const design = modelMatrix(data, options);
1701
+ const { rows } = design;
1702
+ const n = rows.length;
1703
+ if (n === 0) {
1704
+ throw new RangeError("0 (non-NA) cases");
1705
+ }
1706
+ const outcomeColumn = data[outcome];
1707
+ const y = rows.map((row2) => outcomeColumn[row2]);
1708
+ const factored = qr(design.matrix, { tolerance });
1709
+ const coefficients = qrCoef(factored, y);
1710
+ const residuals = qrResid(factored, y);
1711
+ const fitted = zipWith(y, residuals, (value, residual) => value - residual);
1712
+ const names = design.matrix.dimnames?.[1] ?? [];
1713
+ const { rank } = factored;
1714
+ const dfResidual = n - rank;
1715
+ const interceptCount = intercept ? 1 : 0;
1716
+ const rss = sum(residuals.map((r) => r * r));
1717
+ const centered = intercept ? mean(fitted) : 0;
1718
+ const mss = sum(fitted.map((f) => (f - centered) * (f - centered)));
1719
+ const resvar = rss / dfResidual;
1720
+ const numdf = rank - interceptCount;
1721
+ const rSquared = numdf > 0 ? mss / (mss + rss) : 0;
1722
+ const adjRSquared = numdf > 0 ? 1 - (1 - rSquared) * ((n - interceptCount) / dfResidual) : 0;
1723
+ const standardErrors = standardErrorsOf(factored, resvar, names.length);
1724
+ const tValues = zipWith(coefficients, standardErrors, (b, se) => b === null || se === null ? null : b / se);
1725
+ const pValues = tValues.map((tv) => tv === null ? null : 2 * pt(-Math.abs(tv), dfResidual));
1726
+ return {
1727
+ coefficients: namedVector(names, coefficients),
1728
+ standardErrors: namedVector(names, standardErrors),
1729
+ tValues: namedVector(names, tValues),
1730
+ pValues: namedVector(names, pValues),
1731
+ fitted: padded(fitted, rows, outcomeColumn.length),
1732
+ residuals: padded(residuals, rows, outcomeColumn.length),
1733
+ rank,
1734
+ dfResidual,
1735
+ rSquared,
1736
+ adjRSquared,
1737
+ sigma: Math.sqrt(resvar),
1738
+ fStatistic: numdf > 0 ? { value: mss / numdf / resvar, numdf, dendf: dfResidual } : null,
1739
+ rows,
1740
+ termLabels: design.termLabels
1741
+ };
1375
1742
  }
1376
- function isBadArgument(value, df, ncp) {
1377
- return Number.isNaN(value) || !(df > 0) || ncp !== undefined && Number.isNaN(ncp);
1743
+ function standardErrorsOf(factored, resvar, width) {
1744
+ const { rank, pivot } = factored;
1745
+ const { nrow } = factored.qr;
1746
+ const r = (i, j) => factored.qr.data[j * nrow + i];
1747
+ const inverse = Array.from({ length: rank }, (_, k) => {
1748
+ const x = new Array(rank).fill(0);
1749
+ x[k] = 1 / r(k, k);
1750
+ for (let i = k - 1;i >= 0; i--) {
1751
+ let total = 0;
1752
+ for (let j = i + 1;j <= k; j++) {
1753
+ total += r(i, j) * x[j];
1754
+ }
1755
+ x[i] = -total / r(i, i);
1756
+ }
1757
+ return x;
1758
+ });
1759
+ const diagonal = Array.from({ length: rank }, (_, i) => sum(inverse.map((columnOfInverse) => columnOfInverse[i] ** 2)));
1760
+ const errors = new Array(width).fill(null);
1761
+ pivot.slice(0, rank).forEach((original, position) => {
1762
+ errors[original] = Math.sqrt(diagonal[position] * resvar);
1763
+ });
1764
+ return errors;
1765
+ }
1766
+ function padded(values, rows, length) {
1767
+ const out = new Array(length).fill(Number.NaN);
1768
+ rows.forEach((row2, index) => {
1769
+ out[row2] = values[index];
1770
+ });
1771
+ return out;
1772
+ }
1773
+
1774
+ // src/core/moderation.ts
1775
+ var GRID_STEPS = 15;
1776
+ function moderationSurface(data, options) {
1777
+ const { outcome, iv, mod, interaction = true, controls = [] } = options;
1778
+ if (iv === mod) {
1779
+ throw new RangeError(`\`iv\` and \`mod\` must name different columns, both name "${iv}"`);
1780
+ }
1781
+ const named = new Set([outcome, iv, mod]);
1782
+ controls.forEach((control) => {
1783
+ if (named.has(control)) {
1784
+ throw new RangeError(`control "${control}" already names the outcome, the iv, the mod, ` + "or another control");
1785
+ }
1786
+ named.add(control);
1787
+ });
1788
+ frameRows(data);
1789
+ const y = requireNumericColumn(data, outcome, "outcome");
1790
+ const ivColumn = requireNumericColumn(data, iv, "iv");
1791
+ const modColumn = requireNumericColumn(data, mod, "mod");
1792
+ const controlColumns = controls.map((control) => requireNumericColumn(data, control, "controls"));
1793
+ const modelColumns = [y, ivColumn, modColumn, ...controlColumns];
1794
+ const anyComplete = y.some((_, row2) => modelColumns.every((column2) => Number.isFinite(column2[row2])));
1795
+ if (!anyComplete) {
1796
+ throw new RangeError("the model has no complete rows: every row is missing a value in " + "the outcome, the IV, the moderator, or a control");
1797
+ }
1798
+ const fit = lm(data, {
1799
+ outcome,
1800
+ terms: [iv, mod, ...controls, ...interaction ? [[iv, mod]] : []]
1801
+ });
1802
+ const { fitted, residuals } = fit;
1803
+ const coefficients = fit.coefficients.names.map((name, index) => ({
1804
+ name,
1805
+ value: fit.coefficients.values[index] ?? null
1806
+ }));
1807
+ const ivValues = rSeq(...extent(ivColumn), GRID_STEPS);
1808
+ const modValues = rSeq(...extent(modColumn), GRID_STEPS);
1809
+ const holds = Object.fromEntries(controls.map((control, index) => [
1810
+ control,
1811
+ mean(controlColumns[index].filter(Number.isFinite))
1812
+ ]));
1813
+ const weights = coefficients.map((term) => term.value ?? 0);
1814
+ const predictions = modValues.flatMap((modValue) => ivValues.map((ivValue) => {
1815
+ const gridRow = [
1816
+ 1,
1817
+ ivValue,
1818
+ modValue,
1819
+ ...controls.map((control) => holds[control]),
1820
+ ...interaction ? [ivValue * modValue] : []
1821
+ ];
1822
+ return sum(zipWith(gridRow, weights, (value, weight) => value * weight));
1823
+ }));
1824
+ const [dataLow, dataHigh] = extent(y);
1825
+ const [surfaceLow, surfaceHigh] = extent(predictions);
1826
+ return {
1827
+ coefficients,
1828
+ fitted,
1829
+ residuals,
1830
+ ivValues,
1831
+ modValues,
1832
+ predictions,
1833
+ zlim: [Math.min(dataLow, surfaceLow), Math.max(dataHigh, surfaceHigh)],
1834
+ holds
1835
+ };
1378
1836
  }
1379
- function dt(x, df, ncp) {
1380
- if (isBadArgument(x, df, ncp)) {
1381
- return Number.NaN;
1837
+ function rSeq(from, to, length) {
1838
+ if (from === to) {
1839
+ return new Array(length).fill(from);
1382
1840
  }
1383
- return isNonCentral(ncp) ? nonCentralDensity(x, df, ncp) : centralDensity(x, df);
1841
+ const by = (to - from) / (length - 1);
1842
+ return Array.from({ length }, (_, index) => index === 0 ? from : index === length - 1 ? to : from + index * by);
1384
1843
  }
1385
- function pt(x, df, ncp) {
1386
- if (isBadArgument(x, df, ncp)) {
1387
- return Number.NaN;
1388
- }
1389
- return isNonCentral(ncp) ? nonCentralProbability(x, df, ncp) : centralProbability(x, df);
1844
+ // src/core/regression.ts
1845
+ function completePointRows(points) {
1846
+ return points.flatMap((point, row2) => Number.isFinite(point.x) && Number.isFinite(point.y) ? [row2] : []);
1390
1847
  }
1391
- function qt(p, df, ncp) {
1392
- if (isBadArgument(p, df, ncp) || p < 0 || p > 1) {
1393
- return Number.NaN;
1848
+ function linearRegression(points) {
1849
+ const rows = completePointRows(points);
1850
+ if (rows.length === 0) {
1851
+ return null;
1394
1852
  }
1395
- return isNonCentral(ncp) ? nonCentralQuantile(p, df, ncp) : centralQuantile(p, df);
1853
+ const xs = rows.map((row2) => points[row2].x);
1854
+ const ys = rows.map((row2) => points[row2].y);
1855
+ const meanX = mean(xs);
1856
+ const meanY = mean(ys);
1857
+ const devX = xs.map((x) => x - meanX);
1858
+ const devY = ys.map((y) => y - meanY);
1859
+ const sumSquaresX = sum(devX.map((d) => d * d));
1860
+ const sumSquaresY = sum(devY.map((d) => d * d));
1861
+ const sumProducts = sum(zipWith(devX, devY, (dx, dy) => dx * dy));
1862
+ const slope = sumSquaresX === 0 ? null : sumProducts / sumSquaresX;
1863
+ const intercept = slope === null ? meanY : meanY - slope * meanX;
1864
+ const correlation = sumSquaresX === 0 || sumSquaresY === 0 ? null : sumProducts / Math.sqrt(sumSquaresX * sumSquaresY);
1865
+ const fittedComplete = xs.map((x) => slope === null ? intercept : intercept + slope * x);
1866
+ const ssr = sum(fittedComplete.map((f) => (f - meanY) * (f - meanY)));
1867
+ const sse = sum(zipWith(ys, fittedComplete, (y, f) => (y - f) * (y - f)));
1868
+ const sst = sumSquaresY;
1869
+ const rSquared = sst === 0 ? null : ssr / sst;
1870
+ const fitted = new Array(points.length).fill(Number.NaN);
1871
+ rows.forEach((row2, survivor) => {
1872
+ fitted[row2] = fittedComplete[survivor];
1873
+ });
1874
+ return {
1875
+ intercept,
1876
+ slope,
1877
+ correlation,
1878
+ ssr,
1879
+ sse,
1880
+ sst,
1881
+ rSquared,
1882
+ fitted
1883
+ };
1396
1884
  }
1397
- function centralDensity(x, df) {
1398
- if (!Number.isFinite(x)) {
1399
- return 0;
1400
- }
1401
- const logDensity = -0.5 * Math.log(df) - logBeta(0.5, df / 2) - (df + 1) / 2 * Math.log1p(x * x / df);
1402
- return Math.exp(logDensity);
1885
+ // src/core/matrix.ts
1886
+ var SMALLEST_NORMAL = 0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022250738585072014;
1887
+ function determinant(matrix2) {
1888
+ return determinantOf(factorize(matrix2));
1403
1889
  }
1404
- function centralProbability(x, df) {
1405
- if (x === 0) {
1406
- return 0.5;
1407
- }
1408
- if (x === Number.POSITIVE_INFINITY) {
1409
- return 1;
1890
+ function invertMatrix(matrix2) {
1891
+ const factorization = factorize(matrix2);
1892
+ const determinant2 = determinantOf(factorization);
1893
+ const zeroPivot = zeroPivotOf(factorization);
1894
+ if (zeroPivot !== null) {
1895
+ return {
1896
+ determinant: determinant2,
1897
+ inverse: null,
1898
+ singularity: "exact",
1899
+ rcond: 0,
1900
+ zeroPivot
1901
+ };
1410
1902
  }
1411
- if (x === Number.NEGATIVE_INFINITY) {
1412
- return 0;
1903
+ const inverse = solveForIdentity(factorization);
1904
+ const rcond = 1 / (oneNorm(matrix2) * oneNorm(inverse));
1905
+ if (rcond < Number.EPSILON) {
1906
+ return {
1907
+ determinant: determinant2,
1908
+ inverse: null,
1909
+ singularity: "computational",
1910
+ rcond,
1911
+ zeroPivot: null
1912
+ };
1413
1913
  }
1414
- const tail = upperTail(Math.abs(x), df);
1415
- return x < 0 ? tail : 1 - tail;
1914
+ return {
1915
+ determinant: determinant2,
1916
+ inverse,
1917
+ singularity: null,
1918
+ rcond,
1919
+ zeroPivot: null
1920
+ };
1416
1921
  }
1417
- function upperTail(t, df) {
1418
- const squared = t * t;
1419
- if (!Number.isFinite(squared)) {
1420
- return 0;
1421
- }
1422
- const total = df + squared;
1423
- return 0.5 * incompleteBetaSplit(df / total, squared / total, df / 2, 0.5);
1922
+ function factorize(matrix2) {
1923
+ const interchanged = Math.abs(matrix2.y1) > Math.abs(matrix2.x1);
1924
+ const leading = interchanged ? matrix2.y1 : matrix2.x1;
1925
+ const upperRight = interchanged ? matrix2.y2 : matrix2.x2;
1926
+ const below = interchanged ? matrix2.x1 : matrix2.y1;
1927
+ const belowRight = interchanged ? matrix2.x2 : matrix2.y2;
1928
+ const multiplier = Math.abs(leading) >= SMALLEST_NORMAL ? below * (1 / leading) : below / leading;
1929
+ const trailing = fusedMultiplyAdd(-multiplier, upperRight, belowRight);
1930
+ return {
1931
+ interchanged,
1932
+ multiplier,
1933
+ pivots: [leading, trailing],
1934
+ upperRight
1935
+ };
1424
1936
  }
1425
- var POLISH_MAX_STEPS = 4;
1426
- function centralQuantile(p, df) {
1427
- if (p === 0.5) {
1937
+ function determinantOf(factorization) {
1938
+ const { interchanged, pivots } = factorization;
1939
+ if (zeroPivotOf(factorization) !== null) {
1428
1940
  return 0;
1429
1941
  }
1430
- if (p <= 0) {
1431
- return Number.NEGATIVE_INFINITY;
1432
- }
1433
- if (p >= 1) {
1434
- return Number.POSITIVE_INFINITY;
1435
- }
1436
- const tail = p < 0.5 ? p : 1 - p;
1437
- const sign = p < 0.5 ? -1 : 1;
1438
- const twoSided = 2 * tail;
1439
- let squared;
1440
- if (twoSided > 0.5) {
1441
- const near = inverseIncompleteBeta(1 - twoSided, 0.5, df / 2);
1442
- squared = df * near / (1 - near);
1443
- } else {
1444
- const far = inverseIncompleteBeta(twoSided, df / 2, 0.5);
1445
- squared = df * (1 - far) / far;
1446
- }
1447
- return sign * polish(Math.sqrt(squared), tail, df);
1448
- }
1449
- function polish(start, tail, df) {
1450
- let t = start;
1451
- for (let step = 0;step < POLISH_MAX_STEPS; step += 1) {
1452
- const density = centralDensity(t, df);
1453
- if (!(density > 0) || !Number.isFinite(t)) {
1454
- break;
1455
- }
1456
- const move = (upperTail(t, df) - tail) / density;
1457
- const next = t + move;
1458
- if (!(next > 0) || !Number.isFinite(next) || Math.abs(move) > 0.25 * t) {
1459
- break;
1460
- }
1461
- if (next === t) {
1462
- break;
1463
- }
1464
- t = next;
1465
- if (Math.abs(move) <= Number.EPSILON * t) {
1466
- break;
1467
- }
1468
- }
1469
- return t;
1942
+ const modulus = sum(pivots.map((pivot) => Math.log(Math.abs(pivot))));
1943
+ const sign = pivots.reduce((carried, pivot) => pivot < 0 ? -carried : carried, interchanged ? -1 : 1);
1944
+ return sign * Math.exp(modulus);
1470
1945
  }
1471
- var SERIES_MAX_STEPS = 1000;
1472
- var SERIES_ERROR_MAX = 0.000000000001;
1473
- var SERIES_NCP_LIMIT_SQUARED = 2 * Math.LN2 * 1022;
1474
- var SERIES_DF_LIMIT = 400000;
1475
- var SQRT_TWO_OVER_PI = Math.sqrt(2 / Math.PI);
1476
- function nonCentralProbability(x, df, ncp) {
1477
- if (x === Number.POSITIVE_INFINITY) {
1946
+ function zeroPivotOf(factorization) {
1947
+ const [leading, trailing] = factorization.pivots;
1948
+ if (leading === 0) {
1478
1949
  return 1;
1479
1950
  }
1480
- if (x === Number.NEGATIVE_INFINITY) {
1481
- return 0;
1482
- }
1483
- const reflected = x < 0;
1484
- const t = reflected ? -x : x;
1485
- const delta = reflected ? -ncp : ncp;
1486
- const lower = df > SERIES_DF_LIMIT || delta * delta > SERIES_NCP_LIMIT_SQUARED ? normalApproximation(t, df, delta) : lenthSeries(t, df, delta);
1487
- return reflected ? 1 - lower : lower;
1951
+ return trailing === 0 ? 2 : null;
1488
1952
  }
1489
- function normalApproximation(t, df, delta) {
1490
- const shrink = 1 / (4 * df);
1491
- const spread = Math.sqrt(1 + t * t * 2 * shrink);
1492
- return normalCdf((t * (1 - shrink) - delta) / spread);
1953
+ function solveForIdentity(factorization) {
1954
+ const { interchanged } = factorization;
1955
+ const [firstTop, firstBottom] = interchanged ? [0, 1] : [1, 0];
1956
+ const [secondTop, secondBottom] = interchanged ? [1, 0] : [0, 1];
1957
+ const [x1, y1] = solveColumn(factorization, firstTop, firstBottom);
1958
+ const [x2, y2] = solveColumn(factorization, secondTop, secondBottom);
1959
+ return {
1960
+ x1: withoutNegativeZero(x1),
1961
+ y1: withoutNegativeZero(y1),
1962
+ x2: withoutNegativeZero(x2),
1963
+ y2: withoutNegativeZero(y2)
1964
+ };
1493
1965
  }
1494
- function lenthSeries(t, df, delta) {
1495
- const squared = t * t;
1496
- const total = df + squared;
1497
- const x = squared / total;
1498
- const complement = df / total;
1499
- let sum2 = 0;
1500
- if (x > 0) {
1501
- const lambda = delta * delta;
1502
- let oddWeight = 0.5 * Math.exp(-0.5 * lambda);
1503
- let evenWeight = SQRT_TWO_OVER_PI * oddWeight * delta;
1504
- let remaining = 0.5 - oddWeight;
1505
- if (remaining < 0.0000001) {
1506
- remaining = -0.5 * Math.expm1(-0.5 * lambda);
1507
- }
1508
- let a = 0.5;
1509
- const b = 0.5 * df;
1510
- const powered = Math.pow(complement, b);
1511
- const logBetaValue = logBeta(0.5, b);
1512
- let oddTerm = incompleteBetaSplit(x, complement, a, b);
1513
- let oddStep = 2 * powered * Math.exp(a * Math.log(x) - logBetaValue);
1514
- let evenTerm = 1 - powered;
1515
- let evenStep = b * x * powered;
1516
- sum2 = oddWeight * oddTerm + evenWeight * evenTerm;
1517
- for (let step = 1;step <= SERIES_MAX_STEPS; step += 1) {
1518
- a += 1;
1519
- oddTerm -= oddStep;
1520
- evenTerm -= evenStep;
1521
- oddStep *= x * (a + b - 1) / a;
1522
- evenStep *= x * (a + b - 0.5) / (a + 0.5);
1523
- oddWeight *= lambda / (2 * step);
1524
- evenWeight *= lambda / (2 * step + 1);
1525
- remaining -= oddWeight;
1526
- if (remaining <= 0) {
1527
- break;
1528
- }
1529
- sum2 += oddWeight * oddTerm + evenWeight * evenTerm;
1530
- if (Math.abs(2 * remaining * (oddTerm - oddStep)) < SERIES_ERROR_MAX) {
1531
- break;
1532
- }
1533
- }
1966
+ function solveColumn(factorization, top, bottom) {
1967
+ const { multiplier, pivots, upperRight } = factorization;
1968
+ const lower = fusedMultiplyAdd(-multiplier, top, bottom) / pivots[1];
1969
+ const upper = fusedMultiplyAdd(-upperRight, lower, top) / pivots[0];
1970
+ return [upper, lower];
1971
+ }
1972
+ function oneNorm(matrix2) {
1973
+ return Math.max(Math.abs(matrix2.x1) + Math.abs(matrix2.y1), Math.abs(matrix2.x2) + Math.abs(matrix2.y2));
1974
+ }
1975
+ // src/core/pca.ts
1976
+ function principalComponents(points) {
1977
+ const rows = completePointRows(points);
1978
+ if (rows.length === 0) {
1979
+ return null;
1534
1980
  }
1535
- return Math.min(Math.max(sum2 + normalCdf(-delta), 0), 1);
1981
+ const complete = rows.map((row2) => points[row2]);
1982
+ const centerX = mean(complete.map((point) => point.x));
1983
+ const centerY = mean(complete.map((point) => point.y));
1984
+ const devX = complete.map((point) => point.x - centerX);
1985
+ const devY = complete.map((point) => point.y - centerY);
1986
+ const divisor = Math.max(1, complete.length - 1);
1987
+ const varX = sum(devX.map((d) => d * d)) / divisor;
1988
+ const varY = sum(devY.map((d) => d * d)) / divisor;
1989
+ const covariance = sum(zipWith(devX, devY, (dx, dy) => dx * dy)) / divisor;
1990
+ const [first, second] = componentsOf(varX, varY, covariance);
1991
+ const scores = points.map(() => ({ x: Number.NaN, y: Number.NaN }));
1992
+ rows.forEach((row2, survivor) => {
1993
+ const dx = devX[survivor];
1994
+ const dy = devY[survivor];
1995
+ scores[row2] = {
1996
+ x: dx * first.loadings[0] + dy * first.loadings[1],
1997
+ y: dx * second.loadings[0] + dy * second.loadings[1]
1998
+ };
1999
+ });
2000
+ return {
2001
+ sdev: [sdevOf(first.variance), sdevOf(second.variance)],
2002
+ rotation: [first.loadings, second.loadings],
2003
+ center: { x: centerX, y: centerY },
2004
+ scores
2005
+ };
1536
2006
  }
1537
- function nonCentralDensity(x, df, ncp) {
1538
- if (!Number.isFinite(x)) {
1539
- return 0;
1540
- }
1541
- if (Math.abs(x) > Math.sqrt(df * Number.EPSILON)) {
1542
- const stepped = x * Math.sqrt((df + 2) / df);
1543
- const difference = nonCentralProbability(stepped, df + 2, ncp) - nonCentralProbability(x, df, ncp);
1544
- return df / Math.abs(x) * Math.abs(difference);
2007
+ function componentsOf(varX, varY, covariance) {
2008
+ if (covariance === 0) {
2009
+ const alongX = { variance: varX, loadings: [1, 0] };
2010
+ const alongY = { variance: varY, loadings: [0, 1] };
2011
+ return varX >= varY ? [alongX, alongY] : [alongY, alongX];
1545
2012
  }
1546
- return Math.exp(-0.5 * Math.log(df) - logBeta(0.5, df / 2) - 0.5 * ncp * ncp);
2013
+ const middle = (varX + varY) / 2;
2014
+ const spread = Math.hypot((varX - varY) / 2, covariance);
2015
+ const larger = middle + spread;
2016
+ const smaller = middle - spread;
2017
+ const fromRowX = larger - varX;
2018
+ const fromRowY = larger - varY;
2019
+ const direction = fromRowX >= fromRowY ? [covariance, fromRowX] : [fromRowY, covariance];
2020
+ const leading = signed(unit(direction));
2021
+ const trailing = signed([negated(leading[1]), leading[0]]);
2022
+ return [
2023
+ { variance: larger, loadings: leading },
2024
+ { variance: smaller, loadings: trailing }
2025
+ ];
1547
2026
  }
1548
- var QUANTILE_MAX_STEPS = 200;
1549
- function nonCentralQuantile(p, df, ncp) {
1550
- if (p <= 0) {
1551
- return Number.NEGATIVE_INFINITY;
1552
- }
1553
- if (p >= 1) {
1554
- return Number.POSITIVE_INFINITY;
2027
+ function unit(vector) {
2028
+ const length = Math.hypot(vector[0], vector[1]);
2029
+ return [vector[0] / length, vector[1] / length];
2030
+ }
2031
+ function signed(vector) {
2032
+ const dominant = Math.abs(vector[0]) >= Math.abs(vector[1]) ? vector[0] : vector[1];
2033
+ return dominant >= 0 ? vector : [negated(vector[0]), negated(vector[1])];
2034
+ }
2035
+ function negated(value) {
2036
+ return withoutNegativeZero(-value);
2037
+ }
2038
+ function sdevOf(variance) {
2039
+ return Math.sqrt(Math.max(0, variance));
2040
+ }
2041
+ // src/core/ols.ts
2042
+ var DEFAULT_LEAST_SQUARES_TOLERANCE = 0.0000001;
2043
+ function leastSquares(design, y, options = {}) {
2044
+ const { weights, tolerance = DEFAULT_LEAST_SQUARES_TOLERANCE } = options;
2045
+ const rows = design.length;
2046
+ if (rows === 0) {
2047
+ throw new RangeError("least squares needs at least one row");
1555
2048
  }
1556
- let upper = Math.max(1, ncp);
1557
- while (Number.isFinite(upper) && nonCentralProbability(upper, df, ncp) < p) {
1558
- upper *= 2;
2049
+ const width = design[0].length;
2050
+ if (design.some((row2) => row2.length !== width)) {
2051
+ throw new RangeError("every design row needs the same number of columns");
1559
2052
  }
1560
- let lower = Math.min(-1, -ncp);
1561
- while (Number.isFinite(lower) && nonCentralProbability(lower, df, ncp) > p) {
1562
- lower *= 2;
2053
+ if (y.length !== rows) {
2054
+ throw new RangeError(`the response has ${y.length} values but the design has ${rows} rows`);
1563
2055
  }
1564
- let t = 0.5 * (lower + upper);
1565
- for (let step = 0;step < QUANTILE_MAX_STEPS; step += 1) {
1566
- const residual = nonCentralProbability(t, df, ncp) - p;
1567
- if (residual < 0) {
1568
- lower = t;
1569
- } else {
1570
- upper = t;
2056
+ if (weights !== undefined) {
2057
+ if (weights.length !== rows) {
2058
+ throw new RangeError(`there are ${weights.length} weights but ${rows} rows`);
1571
2059
  }
1572
- const density = nonCentralDensity(t, df, ncp);
1573
- let next = density > 0 && Number.isFinite(density) ? t - residual / density : Number.NaN;
1574
- if (!(next > lower) || !(next < upper)) {
1575
- next = 0.5 * (lower + upper);
2060
+ if (weights.some((weight) => !(weight >= 0))) {
2061
+ throw new RangeError("weights cannot be negative or missing");
1576
2062
  }
1577
- if (next === t) {
2063
+ }
2064
+ const scale = weights?.map((weight) => Math.sqrt(weight));
2065
+ const scaled = (value, row2) => scale === undefined ? value : value * scale[row2];
2066
+ const buffer = new Float64Array(rows * width);
2067
+ design.forEach((row2, index) => {
2068
+ for (let column2 = 0;column2 < width; column2++) {
2069
+ buffer[column2 * rows + index] = scaled(row2[column2], index);
2070
+ }
2071
+ });
2072
+ const factored = qr(make(rows, width, buffer, null), { tolerance });
2073
+ const coefficients = qrCoef(factored, y.map(scaled));
2074
+ const fitted = design.map((row2) => sum(zipWith(row2, coefficients, (value, coefficient) => coefficient === null ? 0 : value * coefficient)));
2075
+ const residuals = zipWith(y, fitted, (value, fit) => value - fit);
2076
+ return { coefficients, fitted, residuals, rank: factored.rank };
2077
+ }
2078
+
2079
+ // src/core/logit.ts
2080
+ var DEFAULT_LOGIT_EPSILON = 0.00000001;
2081
+ var DEFAULT_LOGIT_MAX_ITERATIONS = 25;
2082
+ var LINK_THRESHOLD = 30;
2083
+ var BOUND = 10 * Number.EPSILON;
2084
+ function logisticRegression(points, options = {}) {
2085
+ const {
2086
+ epsilon = DEFAULT_LOGIT_EPSILON,
2087
+ maxIterations = DEFAULT_LOGIT_MAX_ITERATIONS
2088
+ } = options;
2089
+ const rows = completePointRows(points);
2090
+ if (rows.length === 0) {
2091
+ return null;
2092
+ }
2093
+ const complete = rows.map((row2) => points[row2]);
2094
+ if (complete.some((point) => point.y !== 0 && point.y !== 1)) {
2095
+ throw new RangeError("every outcome must be 0 or 1");
2096
+ }
2097
+ const outcomes = complete.map((point) => point.y);
2098
+ const design = complete.map((point) => [1, point.x]);
2099
+ let fitted = outcomes.map((outcome) => (outcome + 0.5) / 2);
2100
+ let predictors = fitted.map(logit);
2101
+ let previousDeviance = totalDeviance(outcomes, fitted);
2102
+ let deviance = previousDeviance;
2103
+ let coefficients = [Number.NaN, null];
2104
+ let rank = 0;
2105
+ let iterations = 0;
2106
+ let converged = false;
2107
+ for (let iteration = 1;iteration <= maxIterations; iteration++) {
2108
+ iterations = iteration;
2109
+ const working = predictors.map((predictor, row2) => {
2110
+ const probability = fitted[row2];
2111
+ const slope = linkSlope(predictor);
2112
+ return {
2113
+ weight: slope * slope / (probability * (1 - probability)),
2114
+ response: predictor + (outcomes[row2] - probability) / slope
2115
+ };
2116
+ });
2117
+ const step = leastSquares(design, working.map((row2) => row2.response), {
2118
+ weights: working.map((row2) => row2.weight),
2119
+ tolerance: Math.min(0.0000001, epsilon / 1000)
2120
+ });
2121
+ if (step.coefficients.some((coefficient) => coefficient !== null && !Number.isFinite(coefficient))) {
1578
2122
  break;
1579
2123
  }
1580
- const moved = Math.abs(next - t);
1581
- t = next;
1582
- if (moved <= Number.EPSILON * Math.abs(t)) {
2124
+ coefficients = step.coefficients;
2125
+ rank = step.rank;
2126
+ predictors = design.map((row2) => sum(zipWith(row2, coefficients, (value, coefficient) => coefficient === null ? 0 : value * coefficient)));
2127
+ fitted = predictors.map(linkInverse);
2128
+ deviance = totalDeviance(outcomes, fitted);
2129
+ if (Math.abs(deviance - previousDeviance) / (0.1 + Math.abs(deviance)) < epsilon) {
2130
+ converged = true;
1583
2131
  break;
1584
2132
  }
2133
+ previousDeviance = deviance;
1585
2134
  }
1586
- return t;
2135
+ const wholeMean = mean(outcomes);
2136
+ const nullDeviance = totalDeviance(outcomes, outcomes.map(() => wholeMean));
2137
+ const paddedFitted = new Array(points.length).fill(Number.NaN);
2138
+ const paddedPredictors = new Array(points.length).fill(Number.NaN);
2139
+ rows.forEach((row2, survivor) => {
2140
+ paddedFitted[row2] = fitted[survivor];
2141
+ paddedPredictors[row2] = predictors[survivor];
2142
+ });
2143
+ return {
2144
+ intercept: coefficients[0] ?? Number.NaN,
2145
+ slope: coefficients[1] ?? null,
2146
+ fitted: paddedFitted,
2147
+ linearPredictors: paddedPredictors,
2148
+ deviance,
2149
+ nullDeviance,
2150
+ aic: deviance + 2 * rank,
2151
+ rank,
2152
+ iterations,
2153
+ converged,
2154
+ saturated: fitted.some((probability) => probability > 1 - BOUND || probability < BOUND)
2155
+ };
2156
+ }
2157
+ function predictLogit(fit, x) {
2158
+ return linkInverse(fit.intercept + (fit.slope ?? 0) * x);
2159
+ }
2160
+ function logit(probability) {
2161
+ return Math.log(probability / (1 - probability));
2162
+ }
2163
+ function linkInverse(predictor) {
2164
+ const odds = predictor < -LINK_THRESHOLD ? Number.EPSILON : predictor > LINK_THRESHOLD ? 1 / Number.EPSILON : Math.exp(predictor);
2165
+ return odds / (1 + odds);
2166
+ }
2167
+ function linkSlope(predictor) {
2168
+ if (predictor > LINK_THRESHOLD || predictor < -LINK_THRESHOLD) {
2169
+ return Number.EPSILON;
2170
+ }
2171
+ const odds = Math.exp(predictor);
2172
+ return odds / ((1 + odds) * (1 + odds));
2173
+ }
2174
+ function totalDeviance(outcomes, probabilities) {
2175
+ return sum(zipWith(outcomes, probabilities, (outcome, probability) => 2 * (surprise(outcome, probability) + surprise(1 - outcome, 1 - probability))));
2176
+ }
2177
+ function surprise(outcome, probability) {
2178
+ return outcome !== 0 ? outcome * Math.log(outcome / probability) : 0;
1587
2179
  }
1588
2180
  // src/core/ttest.ts
1589
2181
  var DEFAULT_T_TEST_OPTIONS = {
@@ -1596,27 +2188,27 @@ var FILL_UPPER_QUANTILE = 0.999;
1596
2188
  function tTestStats(options = {}) {
1597
2189
  const { diff, sd: sd2, n, alpha } = { ...DEFAULT_T_TEST_OPTIONS, ...options };
1598
2190
  const df = n - 1;
1599
- const t = diff / (sd2 / Math.sqrt(n));
2191
+ const t2 = diff / (sd2 / Math.sqrt(n));
1600
2192
  const criticalValue = qt(1 - alpha, df);
1601
- const beta = pt(criticalValue, df, t);
2193
+ const beta = pt(criticalValue, df, t2);
1602
2194
  const power = 1 - beta;
1603
- const altMedian = qt(0.5, df, t);
1604
- const altFillFrom = qt(beta, df, t);
2195
+ const altMedian = qt(0.5, df, t2);
2196
+ const altFillFrom = qt(beta, df, t2);
1605
2197
  return {
1606
2198
  diff,
1607
2199
  sd: sd2,
1608
2200
  n,
1609
2201
  alpha,
1610
2202
  df,
1611
- t,
2203
+ t: t2,
1612
2204
  criticalValue,
1613
2205
  beta,
1614
2206
  power,
1615
2207
  altMedian,
1616
- altMedianDensity: dt(altMedian, df, t),
2208
+ altMedianDensity: dt(altMedian, df, t2),
1617
2209
  altFill: {
1618
2210
  from: altFillFrom,
1619
- to: qt(FILL_UPPER_QUANTILE, df, t)
2211
+ to: qt(FILL_UPPER_QUANTILE, df, t2)
1620
2212
  },
1621
2213
  nullFill: {
1622
2214
  from: criticalValue,
@@ -2701,8 +3293,8 @@ var HEAD_LENGTH = 24;
2701
3293
  function matrixInverseScale(width, height) {
2702
3294
  return createScale({ width, height, x: WORLD, y: WORLD });
2703
3295
  }
2704
- function plotMatrixInverse(target, matrix) {
2705
- const inversion = invertMatrix(matrix);
3296
+ function plotMatrixInverse(target, matrix2) {
3297
+ const inversion = invertMatrix(matrix2);
2706
3298
  const { ctx, width, height } = resolveTarget(target);
2707
3299
  if (inversion.inverse === null) {
2708
3300
  return inversion;
@@ -2713,22 +3305,22 @@ function plotMatrixInverse(target, matrix) {
2713
3305
  const { area } = scale;
2714
3306
  ctx.save();
2715
3307
  clipToArea(ctx, area);
2716
- drawMatrix(ctx, scale, matrix, MATRIX_FILL);
3308
+ drawMatrix(ctx, scale, matrix2, MATRIX_FILL);
2717
3309
  drawMatrix(ctx, scale, inversion.inverse, INVERSE_FILL);
2718
3310
  ctx.restore();
2719
3311
  return inversion;
2720
3312
  }
2721
- function drawMatrix(ctx, scale, matrix, fill) {
2722
- drawParallelogram(ctx, scale, matrix, fill);
2723
- drawColumnArrow(ctx, scale, matrix.x1, matrix.y1);
2724
- drawColumnArrow(ctx, scale, matrix.x2, matrix.y2);
3313
+ function drawMatrix(ctx, scale, matrix2, fill) {
3314
+ drawParallelogram(ctx, scale, matrix2, fill);
3315
+ drawColumnArrow(ctx, scale, matrix2.x1, matrix2.y1);
3316
+ drawColumnArrow(ctx, scale, matrix2.x2, matrix2.y2);
2725
3317
  }
2726
- function drawParallelogram(ctx, scale, matrix, fill) {
3318
+ function drawParallelogram(ctx, scale, matrix2, fill) {
2727
3319
  const corners = [
2728
3320
  [0, 0],
2729
- [matrix.x1, matrix.y1],
2730
- [matrix.x1 + matrix.x2, matrix.y1 + matrix.y2],
2731
- [matrix.x2, matrix.y2],
3321
+ [matrix2.x1, matrix2.y1],
3322
+ [matrix2.x1 + matrix2.x2, matrix2.y1 + matrix2.y2],
3323
+ [matrix2.x2, matrix2.y2],
2732
3324
  [0, 0]
2733
3325
  ];
2734
3326
  ctx.save();
@@ -2932,29 +3524,29 @@ function drawRows(ctx, scale, rows, colors) {
2932
3524
  ctx.setLineDash([]);
2933
3525
  ctx.lineWidth = SPAN_WIDTH;
2934
3526
  ctx.strokeStyle = colors.span99;
2935
- for (const { interval, row } of rows) {
2936
- drawSpan(ctx, scale, interval.ci99.low, interval.ci99.high, row);
3527
+ for (const { interval, row: row2 } of rows) {
3528
+ drawSpan(ctx, scale, interval.ci99.low, interval.ci99.high, row2);
2937
3529
  }
2938
3530
  ctx.strokeStyle = colors.span95;
2939
- for (const { interval, row } of rows) {
2940
- drawSpan(ctx, scale, interval.ci95.low, interval.ci95.high, row);
3531
+ for (const { interval, row: row2 } of rows) {
3532
+ drawSpan(ctx, scale, interval.ci95.low, interval.ci95.high, row2);
2941
3533
  }
2942
3534
  ctx.fillStyle = colors.point;
2943
- for (const { interval, row } of rows) {
2944
- drawDiamond(ctx, scale, interval.mean, row);
3535
+ for (const { interval, row: row2 } of rows) {
3536
+ drawDiamond(ctx, scale, interval.mean, row2);
2945
3537
  }
2946
3538
  ctx.restore();
2947
3539
  }
2948
- function drawSpan(ctx, scale, low, high, row) {
2949
- const y = scale.toPixelY(row);
3540
+ function drawSpan(ctx, scale, low, high, row2) {
3541
+ const y = scale.toPixelY(row2);
2950
3542
  ctx.beginPath();
2951
3543
  ctx.moveTo(scale.toPixelX(low), y);
2952
3544
  ctx.lineTo(scale.toPixelX(high), y);
2953
3545
  ctx.stroke();
2954
3546
  }
2955
- function drawDiamond(ctx, scale, at, row) {
2956
- const x = scale.toPixelX(at);
2957
- const y = scale.toPixelY(row);
3547
+ function drawDiamond(ctx, scale, at2, row2) {
3548
+ const x = scale.toPixelX(at2);
3549
+ const y = scale.toPixelY(row2);
2958
3550
  ctx.beginPath();
2959
3551
  ctx.moveTo(x, y - POINT_RADIUS2);
2960
3552
  ctx.lineTo(x + POINT_RADIUS2, y);
@@ -2962,12 +3554,12 @@ function drawDiamond(ctx, scale, at, row) {
2962
3554
  ctx.lineTo(x - POINT_RADIUS2, y);
2963
3555
  ctx.fill();
2964
3556
  }
2965
- function drawPopulationMean(ctx, scale, at) {
2966
- if (!Number.isFinite(at)) {
3557
+ function drawPopulationMean(ctx, scale, at2) {
3558
+ if (!Number.isFinite(at2)) {
2967
3559
  return;
2968
3560
  }
2969
3561
  const { area } = scale;
2970
- const x = scale.toPixelX(at);
3562
+ const x = scale.toPixelX(at2);
2971
3563
  ctx.save();
2972
3564
  ctx.setLineDash([]);
2973
3565
  ctx.strokeStyle = MEAN_LINE_COLOR;
@@ -2989,6 +3581,12 @@ var SAMPLE_CURVE_COLOR = "rgba(179, 179, 179, 0.5)";
2989
3581
  var BAR_COLOR = "lightgray";
2990
3582
  var CURVE_WIDTH2 = 2;
2991
3583
  var SAMPLE_CURVE_WIDTH = 1;
3584
+ var MARK_COLOR = "cornflowerblue";
3585
+ var SAMPLE_MARK_COLOR = "rgba(100, 149, 237, 0.5)";
3586
+ var MARK_WIDTH = 2;
3587
+ var SAMPLE_MARK_WIDTH = 1;
3588
+ var MARK_TICK = 4;
3589
+ var CARET_SIZE = 6;
2992
3590
  var LABEL_FONT = "bold 11px sans-serif";
2993
3591
  var COUNT_FONT = "11px sans-serif";
2994
3592
  var LABEL_LINE_HEIGHT = 13;
@@ -3017,27 +3615,75 @@ function plotSampling(target, population, options = {}) {
3017
3615
  theta = mean,
3018
3616
  rng = seededRng(Math.floor(Math.random() * 4294967296)),
3019
3617
  state = null,
3020
- densityWindow = "data"
3618
+ densityWindow = "data",
3619
+ mark
3021
3620
  } = options;
3022
3621
  const window = frozenWindow(population, state);
3023
3622
  const grid = densityWindow === "frozen" ? { from: window.min, to: window.max } : {};
3024
3623
  const drawable = population.length >= 2;
3025
3624
  const draw = drawable ? drawSamples(rng, population, { sampleSize, reps, theta }) : { samples: [], thetas: [] };
3026
3625
  const sampleTheta = [...state?.sampleTheta ?? [], ...draw.thetas];
3027
- clearSurface(ctx, width, height);
3028
- drawDensityPanel(ctx, width, height, "population", window, drawable ? kernelDensity(population, grid) : null, [], "Population Distribution");
3029
3626
  const pooled = draw.samples.flat();
3030
- drawDensityPanel(ctx, width, height, "samples", window, pooled.length >= 2 ? kernelDensity(pooled, grid) : null, draw.samples.filter((sample) => sample.length >= 2).map((sample) => kernelDensity(sample, grid)), "Sample Distribution");
3627
+ const marks = markNumbers(mark, theta, pooled, sampleTheta);
3628
+ clearSurface(ctx, width, height);
3629
+ drawDensityPanel(ctx, width, height, "population", window, drawable ? kernelDensity(population, grid) : null, [], "Population Distribution", populationMarks(mark), mark !== undefined && mark.populationValue === null ? `no true ${mark.label}` : null);
3630
+ drawDensityPanel(ctx, width, height, "samples", window, pooled.length >= 2 ? kernelDensity(pooled, grid) : null, draw.samples.filter((sample) => sample.length >= 2).map((sample) => kernelDensity(sample, grid)), "Sample Distribution", sampleMarks(mark, draw.samples, draw.thetas, pooled, marks), null);
3031
3631
  const countable = sampleTheta.filter((theta2) => theta2 >= window.min && theta2 <= window.max);
3032
3632
  const counted = countable.length > 0 ? histogram(countable) : null;
3033
- drawStatisticPanel(ctx, width, height, window, counted, sampleTheta.length);
3633
+ drawStatisticPanel(ctx, width, height, window, counted, sampleTheta.length, marks === null || marks.pile === null ? [] : [{ kind: "location", value: marks.pile, center: 0, faint: false }]);
3034
3634
  return {
3035
3635
  state: { xMin: window.min, xMax: window.max, sampleTheta },
3036
3636
  samples: draw.samples,
3037
3637
  thetas: draw.thetas,
3038
- histogram: counted
3638
+ histogram: counted,
3639
+ marks
3640
+ };
3641
+ }
3642
+ function markNumbers(mark, theta, pooled, sampleTheta) {
3643
+ if (mark === undefined) {
3644
+ return null;
3645
+ }
3646
+ return {
3647
+ population: mark.populationValue,
3648
+ sample: pooled.length > 0 ? theta(pooled) : null,
3649
+ pile: sampleTheta.length > 0 ? mean(sampleTheta) : null
3039
3650
  };
3040
3651
  }
3652
+ function populationMarks(mark) {
3653
+ if (mark === undefined || mark.populationValue === null) {
3654
+ return [];
3655
+ }
3656
+ if (mark.kind === "location") {
3657
+ return [
3658
+ { kind: "location", value: mark.populationValue, center: 0, faint: false }
3659
+ ];
3660
+ }
3661
+ const center = mark.populationCenter;
3662
+ if (center === null || center === undefined) {
3663
+ return [];
3664
+ }
3665
+ return [{ kind: "spread", value: mark.populationValue, center, faint: false }];
3666
+ }
3667
+ function sampleMarks(mark, samples, thetas, pooled, marks) {
3668
+ if (mark === undefined || marks === null || marks.sample === null) {
3669
+ return [];
3670
+ }
3671
+ const perSample = samples.map((sample, index) => ({
3672
+ kind: mark.kind,
3673
+ value: thetas[index],
3674
+ center: sample.length > 0 ? mean(sample) : 0,
3675
+ faint: true
3676
+ }));
3677
+ return [
3678
+ ...perSample,
3679
+ {
3680
+ kind: mark.kind,
3681
+ value: marks.sample,
3682
+ center: pooled.length > 0 ? mean(pooled) : 0,
3683
+ faint: false
3684
+ }
3685
+ ];
3686
+ }
3041
3687
  function frozenWindow(population, state) {
3042
3688
  if (state !== null) {
3043
3689
  return { min: state.xMin, max: state.xMax };
@@ -3048,29 +3694,37 @@ function frozenWindow(population, state) {
3048
3694
  const [min, max] = extent(population);
3049
3695
  return { min, max };
3050
3696
  }
3051
- function drawDensityPanel(ctx, width, height, panel, window, main, perSample, label) {
3697
+ function drawDensityPanel(ctx, width, height, panel, window, main, perSample, label, marks, note) {
3052
3698
  const peak = main === null ? 1 : highestOf(main.y);
3053
3699
  const scale = samplingScale(width, height, panel, window, peak);
3054
3700
  drawAxes(ctx, scale, { frame: false, yAxis: false });
3055
3701
  drawLabel(ctx, scale, [label], peak / 2, LABEL_FONT);
3056
- if (main === null) {
3057
- return;
3702
+ if (note !== null) {
3703
+ drawMarkNote(ctx, scale, note, peak / 2);
3058
3704
  }
3059
- drawCurve2(ctx, scale, main, CURVE_COLOR2, CURVE_WIDTH2, panel === "population");
3060
- for (const estimate of perSample) {
3061
- drawCurve2(ctx, scale, estimate, SAMPLE_CURVE_COLOR, SAMPLE_CURVE_WIDTH, false);
3705
+ if (main !== null) {
3706
+ drawCurve2(ctx, scale, main, CURVE_COLOR2, CURVE_WIDTH2, panel === "population");
3707
+ for (const estimate of perSample) {
3708
+ drawCurve2(ctx, scale, estimate, SAMPLE_CURVE_COLOR, SAMPLE_CURVE_WIDTH, false);
3709
+ }
3710
+ if (panel === "samples") {
3711
+ drawCurve2(ctx, scale, main, CURVE_COLOR2, CURVE_WIDTH2, false);
3712
+ }
3062
3713
  }
3063
- if (panel === "samples") {
3064
- drawCurve2(ctx, scale, main, CURVE_COLOR2, CURVE_WIDTH2, false);
3714
+ for (const mark of marks) {
3715
+ drawMark(ctx, scale, window, mark);
3065
3716
  }
3066
3717
  }
3067
- function drawStatisticPanel(ctx, width, height, window, counted, drawnSoFar) {
3718
+ function drawStatisticPanel(ctx, width, height, window, counted, drawnSoFar, marks) {
3068
3719
  const tallest = counted === null ? 1 : highestOf(counted.counts);
3069
3720
  const scale = samplingScale(width, height, "statistic", window, tallest);
3070
3721
  drawAxes(ctx, scale, { frame: false, yAxis: false });
3071
3722
  if (counted !== null) {
3072
3723
  drawBars(ctx, scale, counted);
3073
3724
  }
3725
+ for (const mark of marks) {
3726
+ drawMark(ctx, scale, window, mark);
3727
+ }
3074
3728
  drawLabel(ctx, scale, ["Sampling Statistic ", `( ${drawnSoFar} )`], tallest / 2, COUNT_FONT);
3075
3729
  }
3076
3730
  function drawCurve2(ctx, scale, estimate, color, lineWidth, dotted) {
@@ -3108,7 +3762,91 @@ function drawBars(ctx, scale, counted) {
3108
3762
  });
3109
3763
  ctx.restore();
3110
3764
  }
3111
- function drawLabel(ctx, scale, lines, at, font) {
3765
+ function drawMark(ctx, scale, window, mark) {
3766
+ const color = mark.faint ? SAMPLE_MARK_COLOR : MARK_COLOR;
3767
+ const lineWidth = mark.faint ? SAMPLE_MARK_WIDTH : MARK_WIDTH;
3768
+ if (mark.kind === "location") {
3769
+ if (isInside(window, mark.value)) {
3770
+ drawMarkLine(ctx, scale, mark.value, color, lineWidth);
3771
+ } else {
3772
+ drawCaret(ctx, scale, mark.value > window.max, color);
3773
+ }
3774
+ return;
3775
+ }
3776
+ const low = mark.center - mark.value;
3777
+ const high = mark.center + mark.value;
3778
+ drawMarkSpan(ctx, scale, low, high, color, lineWidth);
3779
+ if (!isInside(window, low)) {
3780
+ drawCaret(ctx, scale, false, color);
3781
+ }
3782
+ if (!isInside(window, high)) {
3783
+ drawCaret(ctx, scale, true, color);
3784
+ }
3785
+ }
3786
+ function isInside(window, value) {
3787
+ return value >= window.min && value <= window.max;
3788
+ }
3789
+ function drawMarkLine(ctx, scale, value, color, lineWidth) {
3790
+ const { area } = scale;
3791
+ const px = scale.toPixelX(value);
3792
+ ctx.save();
3793
+ clipToArea(ctx, area);
3794
+ ctx.setLineDash([]);
3795
+ ctx.strokeStyle = color;
3796
+ ctx.lineWidth = lineWidth;
3797
+ ctx.beginPath();
3798
+ ctx.moveTo(px, area.top);
3799
+ ctx.lineTo(px, area.bottom);
3800
+ ctx.stroke();
3801
+ ctx.restore();
3802
+ }
3803
+ function drawMarkSpan(ctx, scale, low, high, color, lineWidth) {
3804
+ const { area } = scale;
3805
+ const y = (area.top + area.bottom) / 2;
3806
+ const left = scale.toPixelX(low);
3807
+ const right = scale.toPixelX(high);
3808
+ ctx.save();
3809
+ clipToArea(ctx, area);
3810
+ ctx.setLineDash([]);
3811
+ ctx.strokeStyle = color;
3812
+ ctx.lineWidth = lineWidth;
3813
+ ctx.beginPath();
3814
+ ctx.moveTo(left, y);
3815
+ ctx.lineTo(right, y);
3816
+ ctx.moveTo(left, y - MARK_TICK);
3817
+ ctx.lineTo(left, y + MARK_TICK);
3818
+ ctx.moveTo(right, y - MARK_TICK);
3819
+ ctx.lineTo(right, y + MARK_TICK);
3820
+ ctx.stroke();
3821
+ ctx.restore();
3822
+ }
3823
+ function drawCaret(ctx, scale, atRight, color) {
3824
+ const { area } = scale;
3825
+ const y = (area.top + area.bottom) / 2;
3826
+ const tip = atRight ? area.right : area.left;
3827
+ const base = atRight ? tip - CARET_SIZE : tip + CARET_SIZE;
3828
+ ctx.save();
3829
+ clipToArea(ctx, area);
3830
+ ctx.setLineDash([]);
3831
+ ctx.fillStyle = color;
3832
+ ctx.beginPath();
3833
+ ctx.moveTo(tip, y);
3834
+ ctx.lineTo(base, y - CARET_SIZE / 2);
3835
+ ctx.lineTo(base, y + CARET_SIZE / 2);
3836
+ ctx.fill();
3837
+ ctx.restore();
3838
+ }
3839
+ function drawMarkNote(ctx, scale, note, at2) {
3840
+ ctx.save();
3841
+ ctx.setLineDash([]);
3842
+ ctx.fillStyle = MARK_COLOR;
3843
+ ctx.font = COUNT_FONT;
3844
+ ctx.textAlign = "left";
3845
+ ctx.textBaseline = "middle";
3846
+ ctx.fillText(note, scale.area.left, scale.toPixelY(at2) + LABEL_LINE_HEIGHT);
3847
+ ctx.restore();
3848
+ }
3849
+ function drawLabel(ctx, scale, lines, at2, font) {
3112
3850
  ctx.save();
3113
3851
  ctx.setLineDash([]);
3114
3852
  ctx.fillStyle = TEXT_COLOR2;
@@ -3116,7 +3854,7 @@ function drawLabel(ctx, scale, lines, at, font) {
3116
3854
  ctx.textAlign = "left";
3117
3855
  ctx.textBaseline = "middle";
3118
3856
  lines.forEach((line, index) => {
3119
- ctx.fillText(line, scale.area.left, scale.toPixelY(at) + index * LABEL_LINE_HEIGHT);
3857
+ ctx.fillText(line, scale.area.left, scale.toPixelY(at2) + index * LABEL_LINE_HEIGHT);
3120
3858
  });
3121
3859
  ctx.restore();
3122
3860
  }
@@ -4191,5 +4929,5 @@ export {
4191
4929
  DEFAULT_LEAST_SQUARES_TOLERANCE
4192
4930
  };
4193
4931
 
4194
- //# debugId=2FC10F4594685E1B64756E2164756E21
4932
+ //# debugId=6C77280FC4EBBC8264756E2164756E21
4195
4933
  //# sourceMappingURL=index.js.map