@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/3d.js CHANGED
@@ -660,6 +660,9 @@ function meanAbsoluteDeviation(values) {
660
660
  return mean(values.map((value) => Math.abs(value - center)));
661
661
  }
662
662
  function fusedMultiplyAdd(a, b, c) {
663
+ if (a * b === 0) {
664
+ return c + a * b;
665
+ }
663
666
  const [product, productError] = twoProduct(a, b);
664
667
  const [sum2, sumError] = twoSum(c, product);
665
668
  const rounded = sum2 + (sumError + productError);
@@ -711,129 +714,1142 @@ function type7(sorted, p) {
711
714
  return low;
712
715
  }
713
716
 
714
- // src/core/ols.ts
715
- var DEFAULT_LEAST_SQUARES_TOLERANCE = 0.0000001;
716
- function leastSquares(design, y, options = {}) {
717
- const { weights, tolerance = DEFAULT_LEAST_SQUARES_TOLERANCE } = options;
718
- const rows = design.length;
719
- if (rows === 0) {
720
- throw new RangeError("least squares needs at least one row");
721
- }
722
- const width = design[0].length;
723
- if (design.some((row) => row.length !== width)) {
724
- throw new RangeError("every design row needs the same number of columns");
725
- }
726
- if (y.length !== rows) {
727
- throw new RangeError(`the response has ${y.length} values but the design has ${rows} rows`);
728
- }
729
- if (weights !== undefined) {
730
- if (weights.length !== rows) {
731
- throw new RangeError(`there are ${weights.length} weights but ${rows} rows`);
732
- }
733
- if (weights.some((weight) => !(weight >= 0))) {
734
- throw new RangeError("weights cannot be negative or missing");
735
- }
736
- }
737
- const scale = weights?.map((weight) => Math.sqrt(weight));
738
- const scaled = (value, row) => scale === undefined ? value : value * scale[row];
739
- const columns = Array.from({ length: width }, (_, column) => design.map((row, index) => scaled(row[column], index)));
740
- const projected = y.map(scaled);
741
- const { householders, pivot, rank } = decompose(columns, tolerance, rows);
742
- applyHouseholders(columns, householders, projected, Math.min(rank, rows - 1));
743
- const solved = backSubstitute(columns, projected, rank);
744
- const coefficients = new Array(width).fill(null);
745
- pivot.slice(0, rank).forEach((column, position) => {
746
- coefficients[column] = solved[position];
717
+ // src/core/special.ts
718
+ var LANCZOS_G = 607 / 128;
719
+ var LANCZOS_LEAD = 0.9999999999999971;
720
+ var LANCZOS_TAIL = [
721
+ 57.15623566586292,
722
+ -59.59796035547549,
723
+ 14.136097974741746,
724
+ -0.4919138160976202,
725
+ 0.00003399464998481189,
726
+ 0.00004652362892704858,
727
+ -0.00009837447530487956,
728
+ 0.0001580887032249125,
729
+ -0.00021026444172410488,
730
+ 0.00021743961811521265,
731
+ -0.0001643181065367639,
732
+ 0.00008441822398385275,
733
+ -0.000026190838401581408,
734
+ 0.0000036899182659531625
735
+ ];
736
+ var LOG_SQRT_TWO_PI = 0.5 * Math.log(2 * Math.PI);
737
+ function lanczosSeries(x) {
738
+ return LANCZOS_LEAD + sum(LANCZOS_TAIL.map((coefficient, index) => coefficient / (x + index)));
739
+ }
740
+ function logGamma(x) {
741
+ if (!(x > 0)) {
742
+ return Number.NaN;
743
+ }
744
+ const shifted = x + LANCZOS_G - 0.5;
745
+ return LOG_SQRT_TWO_PI + (x - 0.5) * Math.log(shifted) - shifted + Math.log(lanczosSeries(x));
746
+ }
747
+ function logBeta(a, b) {
748
+ if (!(a > 0) || !(b > 0)) {
749
+ return Number.NaN;
750
+ }
751
+ const shiftedSum = a + b + LANCZOS_G - 0.5;
752
+ 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);
753
+ }
754
+ var FRACTION_MAX_STEPS = 400;
755
+ var FRACTION_EPSILON = 0.0000000000000003;
756
+ var FRACTION_FLOOR = 0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001;
757
+ function betaContinuedFraction(x, a, b) {
758
+ const total = a + b;
759
+ const aPlus = a + 1;
760
+ const aMinus = a - 1;
761
+ let c = 1;
762
+ let d = 1 - total * x / aPlus;
763
+ if (Math.abs(d) < FRACTION_FLOOR) {
764
+ d = FRACTION_FLOOR;
765
+ }
766
+ d = 1 / d;
767
+ let value = d;
768
+ for (let step = 1;step <= FRACTION_MAX_STEPS; step += 1) {
769
+ const twice = 2 * step;
770
+ const even = step * (b - step) * x / ((aMinus + twice) * (a + twice));
771
+ d = 1 + even * d;
772
+ if (Math.abs(d) < FRACTION_FLOOR) {
773
+ d = FRACTION_FLOOR;
774
+ }
775
+ c = 1 + even / c;
776
+ if (Math.abs(c) < FRACTION_FLOOR) {
777
+ c = FRACTION_FLOOR;
778
+ }
779
+ d = 1 / d;
780
+ value *= d * c;
781
+ const odd = -(a + step) * (total + step) * x / ((a + twice) * (aPlus + twice));
782
+ d = 1 + odd * d;
783
+ if (Math.abs(d) < FRACTION_FLOOR) {
784
+ d = FRACTION_FLOOR;
785
+ }
786
+ c = 1 + odd / c;
787
+ if (Math.abs(c) < FRACTION_FLOOR) {
788
+ c = FRACTION_FLOOR;
789
+ }
790
+ d = 1 / d;
791
+ const delta = d * c;
792
+ value *= delta;
793
+ if (Math.abs(delta - 1) < FRACTION_EPSILON) {
794
+ break;
795
+ }
796
+ }
797
+ return value;
798
+ }
799
+ function incompleteBeta(x, a, b) {
800
+ if (Number.isNaN(x)) {
801
+ return Number.NaN;
802
+ }
803
+ if (x <= 0) {
804
+ return 0;
805
+ }
806
+ if (x >= 1) {
807
+ return 1;
808
+ }
809
+ return incompleteBetaSplit(x, 1 - x, a, b);
810
+ }
811
+ function incompleteBetaSplit(x, complement, a, b) {
812
+ if (Number.isNaN(x) || Number.isNaN(complement) || !(a > 0) || !(b > 0)) {
813
+ return Number.NaN;
814
+ }
815
+ if (x <= 0) {
816
+ return 0;
817
+ }
818
+ if (complement <= 0) {
819
+ return 1;
820
+ }
821
+ const logX = complement < 0.5 ? Math.log1p(-complement) : Math.log(x);
822
+ const logComplement = x < 0.5 ? Math.log1p(-x) : Math.log(complement);
823
+ const front = Math.exp(a * logX + b * logComplement - logBeta(a, b));
824
+ if (x < (a + 1) / (a + b + 2)) {
825
+ return front * betaContinuedFraction(x, a, b) / a;
826
+ }
827
+ return 1 - front * betaContinuedFraction(complement, b, a) / b;
828
+ }
829
+ var GAMMA_MAX_STEPS = 1000;
830
+ var GAMMA_EPSILON = 0.0000000000000003;
831
+ function lowerGammaSeries(a, x) {
832
+ let term = 1 / a;
833
+ let total = term;
834
+ for (let step = 1;step <= GAMMA_MAX_STEPS; step += 1) {
835
+ term *= x / (a + step);
836
+ total += term;
837
+ if (Math.abs(term) < Math.abs(total) * GAMMA_EPSILON) {
838
+ break;
839
+ }
840
+ }
841
+ return total * Math.exp(-x + a * Math.log(x) - logGamma(a));
842
+ }
843
+ function upperGammaFraction(a, x) {
844
+ let b = x + 1 - a;
845
+ let c = 1 / FRACTION_FLOOR;
846
+ let d = 1 / b;
847
+ let value = d;
848
+ for (let step = 1;step <= GAMMA_MAX_STEPS; step += 1) {
849
+ const numerator = -step * (step - a);
850
+ b += 2;
851
+ d = numerator * d + b;
852
+ if (Math.abs(d) < FRACTION_FLOOR) {
853
+ d = FRACTION_FLOOR;
854
+ }
855
+ c = b + numerator / c;
856
+ if (Math.abs(c) < FRACTION_FLOOR) {
857
+ c = FRACTION_FLOOR;
858
+ }
859
+ d = 1 / d;
860
+ const delta = d * c;
861
+ value *= delta;
862
+ if (Math.abs(delta - 1) < GAMMA_EPSILON) {
863
+ break;
864
+ }
865
+ }
866
+ return value * Math.exp(-x + a * Math.log(x) - logGamma(a));
867
+ }
868
+ function upperGamma(a, x) {
869
+ if (x <= 0) {
870
+ return 1;
871
+ }
872
+ if (!Number.isFinite(x)) {
873
+ return 0;
874
+ }
875
+ return x < a + 1 ? 1 - lowerGammaSeries(a, x) : upperGammaFraction(a, x);
876
+ }
877
+ function normalCdf(z) {
878
+ if (Number.isNaN(z)) {
879
+ return Number.NaN;
880
+ }
881
+ const lower = 0.5 * upperGamma(0.5, 0.5 * z * z);
882
+ return z > 0 ? 1 - lower : lower;
883
+ }
884
+ var BELOW_ONE = 1 - Number.EPSILON / 2;
885
+ var INVERSE_MAX_STEPS = 200;
886
+ function inverseGuess(p, a, b) {
887
+ if (a >= 1 && b >= 1) {
888
+ const tail = p < 0.5 ? p : 1 - p;
889
+ const t = Math.sqrt(-2 * Math.log(tail));
890
+ const normal = (p < 0.5 ? -1 : 1) * ((2.30753 + t * 0.27061) / (1 + t * (0.99229 + t * 0.04481)) - t);
891
+ const scale = (normal * normal - 3) / 6;
892
+ const harmonic = 2 / (1 / (2 * a - 1) + 1 / (2 * b - 1));
893
+ const w = normal * Math.sqrt(scale + harmonic) / harmonic - (1 / (2 * b - 1) - 1 / (2 * a - 1)) * (scale + 5 / 6 - 2 / (3 * harmonic));
894
+ return a / (a + b * Math.exp(2 * w));
895
+ }
896
+ const lower = Math.exp(a * Math.log(a / (a + b))) / a;
897
+ const upper = Math.exp(b * Math.log(b / (a + b))) / b;
898
+ const total = lower + upper;
899
+ if (p < lower / total) {
900
+ return Math.pow(a * total * p, 1 / a);
901
+ }
902
+ return 1 - Math.pow(b * total * (1 - p), 1 / b);
903
+ }
904
+ function inverseIncompleteBeta(p, a, b) {
905
+ if (Number.isNaN(p) || !(a > 0) || !(b > 0)) {
906
+ return Number.NaN;
907
+ }
908
+ if (p <= 0) {
909
+ return 0;
910
+ }
911
+ if (p >= 1) {
912
+ return 1;
913
+ }
914
+ const logBetaValue = logBeta(a, b);
915
+ let lower = 0;
916
+ let upper = 1;
917
+ let x = inverseGuess(p, a, b);
918
+ if (!(x > 0) || !(x < 1)) {
919
+ x = 0.5;
920
+ }
921
+ for (let step = 0;step < INVERSE_MAX_STEPS; step += 1) {
922
+ const residual = incompleteBeta(x, a, b) - p;
923
+ if (residual < 0) {
924
+ lower = x;
925
+ } else {
926
+ upper = x;
927
+ }
928
+ const density = Math.exp((a - 1) * Math.log(x) + (b - 1) * Math.log1p(-x) - logBetaValue);
929
+ let next = density > 0 && Number.isFinite(density) ? x - residual / density : Number.NaN;
930
+ if (!(next > lower) || !(next < upper)) {
931
+ next = 0.5 * (lower + upper);
932
+ }
933
+ if (next === x) {
934
+ break;
935
+ }
936
+ const moved = Math.abs(next - x);
937
+ x = next;
938
+ if (moved <= Number.EPSILON * x) {
939
+ break;
940
+ }
941
+ }
942
+ return Math.min(x, BELOW_ONE);
943
+ }
944
+
945
+ // src/core/tdist.ts
946
+ function isNonCentral(ncp) {
947
+ return ncp !== undefined && ncp !== 0;
948
+ }
949
+ function isBadArgument(value, df, ncp) {
950
+ return Number.isNaN(value) || !(df > 0) || ncp !== undefined && Number.isNaN(ncp);
951
+ }
952
+ function dt(x, df, ncp) {
953
+ if (isBadArgument(x, df, ncp)) {
954
+ return Number.NaN;
955
+ }
956
+ return isNonCentral(ncp) ? nonCentralDensity(x, df, ncp) : centralDensity(x, df);
957
+ }
958
+ function pt(x, df, ncp) {
959
+ if (isBadArgument(x, df, ncp)) {
960
+ return Number.NaN;
961
+ }
962
+ return isNonCentral(ncp) ? nonCentralProbability(x, df, ncp) : centralProbability(x, df);
963
+ }
964
+ function qt(p, df, ncp) {
965
+ if (isBadArgument(p, df, ncp) || p < 0 || p > 1) {
966
+ return Number.NaN;
967
+ }
968
+ return isNonCentral(ncp) ? nonCentralQuantile(p, df, ncp) : centralQuantile(p, df);
969
+ }
970
+ function centralDensity(x, df) {
971
+ if (!Number.isFinite(x)) {
972
+ return 0;
973
+ }
974
+ const logDensity = -0.5 * Math.log(df) - logBeta(0.5, df / 2) - (df + 1) / 2 * Math.log1p(x * x / df);
975
+ return Math.exp(logDensity);
976
+ }
977
+ function centralProbability(x, df) {
978
+ if (x === 0) {
979
+ return 0.5;
980
+ }
981
+ if (x === Number.POSITIVE_INFINITY) {
982
+ return 1;
983
+ }
984
+ if (x === Number.NEGATIVE_INFINITY) {
985
+ return 0;
986
+ }
987
+ const tail = upperTail(Math.abs(x), df);
988
+ return x < 0 ? tail : 1 - tail;
989
+ }
990
+ function upperTail(t, df) {
991
+ const squared = t * t;
992
+ if (!Number.isFinite(squared)) {
993
+ return 0;
994
+ }
995
+ const total = df + squared;
996
+ return 0.5 * incompleteBetaSplit(df / total, squared / total, df / 2, 0.5);
997
+ }
998
+ var POLISH_MAX_STEPS = 4;
999
+ function centralQuantile(p, df) {
1000
+ if (p === 0.5) {
1001
+ return 0;
1002
+ }
1003
+ if (p <= 0) {
1004
+ return Number.NEGATIVE_INFINITY;
1005
+ }
1006
+ if (p >= 1) {
1007
+ return Number.POSITIVE_INFINITY;
1008
+ }
1009
+ const tail = p < 0.5 ? p : 1 - p;
1010
+ const sign = p < 0.5 ? -1 : 1;
1011
+ const twoSided = 2 * tail;
1012
+ let squared;
1013
+ if (twoSided > 0.5) {
1014
+ const near = inverseIncompleteBeta(1 - twoSided, 0.5, df / 2);
1015
+ squared = df * near / (1 - near);
1016
+ } else {
1017
+ const far = inverseIncompleteBeta(twoSided, df / 2, 0.5);
1018
+ squared = df * (1 - far) / far;
1019
+ }
1020
+ return sign * polish(Math.sqrt(squared), tail, df);
1021
+ }
1022
+ function polish(start, tail, df) {
1023
+ let t = start;
1024
+ for (let step = 0;step < POLISH_MAX_STEPS; step += 1) {
1025
+ const density = centralDensity(t, df);
1026
+ if (!(density > 0) || !Number.isFinite(t)) {
1027
+ break;
1028
+ }
1029
+ const move = (upperTail(t, df) - tail) / density;
1030
+ const next = t + move;
1031
+ if (!(next > 0) || !Number.isFinite(next) || Math.abs(move) > 0.25 * t) {
1032
+ break;
1033
+ }
1034
+ if (next === t) {
1035
+ break;
1036
+ }
1037
+ t = next;
1038
+ if (Math.abs(move) <= Number.EPSILON * t) {
1039
+ break;
1040
+ }
1041
+ }
1042
+ return t;
1043
+ }
1044
+ var SERIES_MAX_STEPS = 1000;
1045
+ var SERIES_ERROR_MAX = 0.000000000001;
1046
+ var SERIES_NCP_LIMIT_SQUARED = 2 * Math.LN2 * 1022;
1047
+ var SERIES_DF_LIMIT = 400000;
1048
+ var SQRT_TWO_OVER_PI = Math.sqrt(2 / Math.PI);
1049
+ function nonCentralProbability(x, df, ncp) {
1050
+ if (x === Number.POSITIVE_INFINITY) {
1051
+ return 1;
1052
+ }
1053
+ if (x === Number.NEGATIVE_INFINITY) {
1054
+ return 0;
1055
+ }
1056
+ const reflected = x < 0;
1057
+ const t = reflected ? -x : x;
1058
+ const delta = reflected ? -ncp : ncp;
1059
+ const lower = df > SERIES_DF_LIMIT || delta * delta > SERIES_NCP_LIMIT_SQUARED ? normalApproximation(t, df, delta) : lenthSeries(t, df, delta);
1060
+ return reflected ? 1 - lower : lower;
1061
+ }
1062
+ function normalApproximation(t, df, delta) {
1063
+ const shrink = 1 / (4 * df);
1064
+ const spread = Math.sqrt(1 + t * t * 2 * shrink);
1065
+ return normalCdf((t * (1 - shrink) - delta) / spread);
1066
+ }
1067
+ function lenthSeries(t, df, delta) {
1068
+ const squared = t * t;
1069
+ const total = df + squared;
1070
+ const x = squared / total;
1071
+ const complement = df / total;
1072
+ let sum2 = 0;
1073
+ if (x > 0) {
1074
+ const lambda = delta * delta;
1075
+ let oddWeight = 0.5 * Math.exp(-0.5 * lambda);
1076
+ let evenWeight = SQRT_TWO_OVER_PI * oddWeight * delta;
1077
+ let remaining = 0.5 - oddWeight;
1078
+ if (remaining < 0.0000001) {
1079
+ remaining = -0.5 * Math.expm1(-0.5 * lambda);
1080
+ }
1081
+ let a = 0.5;
1082
+ const b = 0.5 * df;
1083
+ const powered = Math.pow(complement, b);
1084
+ const logBetaValue = logBeta(0.5, b);
1085
+ let oddTerm = incompleteBetaSplit(x, complement, a, b);
1086
+ let oddStep = 2 * powered * Math.exp(a * Math.log(x) - logBetaValue);
1087
+ let evenTerm = 1 - powered;
1088
+ let evenStep = b * x * powered;
1089
+ sum2 = oddWeight * oddTerm + evenWeight * evenTerm;
1090
+ for (let step = 1;step <= SERIES_MAX_STEPS; step += 1) {
1091
+ a += 1;
1092
+ oddTerm -= oddStep;
1093
+ evenTerm -= evenStep;
1094
+ oddStep *= x * (a + b - 1) / a;
1095
+ evenStep *= x * (a + b - 0.5) / (a + 0.5);
1096
+ oddWeight *= lambda / (2 * step);
1097
+ evenWeight *= lambda / (2 * step + 1);
1098
+ remaining -= oddWeight;
1099
+ if (remaining <= 0) {
1100
+ break;
1101
+ }
1102
+ sum2 += oddWeight * oddTerm + evenWeight * evenTerm;
1103
+ if (Math.abs(2 * remaining * (oddTerm - oddStep)) < SERIES_ERROR_MAX) {
1104
+ break;
1105
+ }
1106
+ }
1107
+ }
1108
+ return Math.min(Math.max(sum2 + normalCdf(-delta), 0), 1);
1109
+ }
1110
+ function nonCentralDensity(x, df, ncp) {
1111
+ if (!Number.isFinite(x)) {
1112
+ return 0;
1113
+ }
1114
+ if (Math.abs(x) > Math.sqrt(df * Number.EPSILON)) {
1115
+ const stepped = x * Math.sqrt((df + 2) / df);
1116
+ const difference = nonCentralProbability(stepped, df + 2, ncp) - nonCentralProbability(x, df, ncp);
1117
+ return df / Math.abs(x) * Math.abs(difference);
1118
+ }
1119
+ return Math.exp(-0.5 * Math.log(df) - logBeta(0.5, df / 2) - 0.5 * ncp * ncp);
1120
+ }
1121
+ var QUANTILE_MAX_STEPS = 200;
1122
+ function nonCentralQuantile(p, df, ncp) {
1123
+ if (p <= 0) {
1124
+ return Number.NEGATIVE_INFINITY;
1125
+ }
1126
+ if (p >= 1) {
1127
+ return Number.POSITIVE_INFINITY;
1128
+ }
1129
+ let upper = Math.max(1, ncp);
1130
+ while (Number.isFinite(upper) && nonCentralProbability(upper, df, ncp) < p) {
1131
+ upper *= 2;
1132
+ }
1133
+ let lower = Math.min(-1, -ncp);
1134
+ while (Number.isFinite(lower) && nonCentralProbability(lower, df, ncp) > p) {
1135
+ lower *= 2;
1136
+ }
1137
+ let t = 0.5 * (lower + upper);
1138
+ for (let step = 0;step < QUANTILE_MAX_STEPS; step += 1) {
1139
+ const residual = nonCentralProbability(t, df, ncp) - p;
1140
+ if (residual < 0) {
1141
+ lower = t;
1142
+ } else {
1143
+ upper = t;
1144
+ }
1145
+ const density = nonCentralDensity(t, df, ncp);
1146
+ let next = density > 0 && Number.isFinite(density) ? t - residual / density : Number.NaN;
1147
+ if (!(next > lower) || !(next < upper)) {
1148
+ next = 0.5 * (lower + upper);
1149
+ }
1150
+ if (next === t) {
1151
+ break;
1152
+ }
1153
+ const moved = Math.abs(next - t);
1154
+ t = next;
1155
+ if (moved <= Number.EPSILON * Math.abs(t)) {
1156
+ break;
1157
+ }
1158
+ }
1159
+ return t;
1160
+ }
1161
+
1162
+ // src/core/linalg/matrix.ts
1163
+ function matrix(values, options) {
1164
+ const { byrow = false, dimnames } = options;
1165
+ const [nrow, ncol] = extents(values.length, options);
1166
+ const dense = Float64Array.from(values);
1167
+ const data = new Float64Array(nrow * ncol);
1168
+ if (dense.length === 1 && data.length > 1) {
1169
+ data.fill(dense[0]);
1170
+ } else if (byrow) {
1171
+ dense.forEach((value, index) => {
1172
+ const i = Math.floor(index / ncol);
1173
+ const j = index % ncol;
1174
+ data[j * nrow + i] = value;
1175
+ });
1176
+ } else {
1177
+ data.set(dense);
1178
+ }
1179
+ return make(nrow, ncol, data, dimnames ?? null);
1180
+ }
1181
+ function extents(length, { nrow, ncol }) {
1182
+ if (nrow === undefined && ncol === undefined) {
1183
+ throw new RangeError("matrix() needs nrow or ncol");
1184
+ }
1185
+ if (nrow !== undefined) {
1186
+ requireExtent(nrow, "nrow");
1187
+ }
1188
+ if (ncol !== undefined) {
1189
+ requireExtent(ncol, "ncol");
1190
+ }
1191
+ const scalar = length === 1;
1192
+ if (nrow !== undefined && ncol !== undefined) {
1193
+ if (!scalar && nrow * ncol !== length) {
1194
+ throw new RangeError(length > nrow * ncol ? "data is too long" : `data length [${length}] is not nrow * ncol [${nrow} * ${ncol}]`);
1195
+ }
1196
+ return [nrow, ncol];
1197
+ }
1198
+ const given = nrow ?? ncol;
1199
+ const name = nrow !== undefined ? "rows" : "columns";
1200
+ if (given === 0) {
1201
+ if (length !== 0) {
1202
+ throw new RangeError("data is too long");
1203
+ }
1204
+ return [0, 0];
1205
+ }
1206
+ const other = scalar ? 1 : length / given;
1207
+ if (!scalar && length % given !== 0) {
1208
+ throw new RangeError(`data length [${length}] is not a multiple of the number of ${name} [${given}]`);
1209
+ }
1210
+ return nrow !== undefined ? [nrow, other] : [other, given];
1211
+ }
1212
+ function requireExtent(value, name) {
1213
+ if (!Number.isSafeInteger(value) || value < 0) {
1214
+ throw new RangeError(`${name} must be a non-negative integer, got ${value}`);
1215
+ }
1216
+ }
1217
+ function make(nrow, ncol, data, dimnames) {
1218
+ if (data.length !== nrow * ncol) {
1219
+ throw new RangeError(`data length [${data.length}] is not nrow * ncol [${nrow} * ${ncol}]`);
1220
+ }
1221
+ if (dimnames !== null) {
1222
+ const [rows, columns] = dimnames;
1223
+ if (rows !== null && rows.length !== nrow) {
1224
+ throw new RangeError(`length of dimnames [1] (${rows.length}) not equal to array extent (${nrow})`);
1225
+ }
1226
+ if (columns !== null && columns.length !== ncol) {
1227
+ throw new RangeError(`length of dimnames [2] (${columns.length}) not equal to array extent (${ncol})`);
1228
+ }
1229
+ dimnames = rows === null && columns === null ? null : [rows === null ? null : [...rows], columns === null ? null : [...columns]];
1230
+ }
1231
+ return { nrow, ncol, data, dimnames };
1232
+ }
1233
+ function fromRows(rows) {
1234
+ const nrow = rows.length;
1235
+ const first = rows[0];
1236
+ if (first === undefined || first.length === 0) {
1237
+ throw new RangeError("fromRows() needs at least one row with one value");
1238
+ }
1239
+ const ncol = first.length;
1240
+ const ragged = rows.findIndex((row) => row.length !== ncol);
1241
+ if (ragged !== -1) {
1242
+ throw new RangeError(`every row needs ${ncol} values; row ${ragged} has ${rows[ragged].length}`);
1243
+ }
1244
+ const data = new Float64Array(nrow * ncol);
1245
+ rows.forEach((row, i) => {
1246
+ Float64Array.from(row).forEach((value, j) => {
1247
+ data[j * nrow + i] = value;
1248
+ });
747
1249
  });
748
- const fitted = design.map((row) => sum(zipWith(row, coefficients, (value, coefficient) => coefficient === null ? 0 : value * coefficient)));
749
- const residuals = zipWith(y, fitted, (value, fit) => value - fit);
750
- return { coefficients, fitted, residuals, rank };
1250
+ return make(nrow, ncol, data, null);
1251
+ }
1252
+ function fromColumns(columns) {
1253
+ const ncol = columns.length;
1254
+ const first = columns[0];
1255
+ if (first === undefined || first.length === 0) {
1256
+ throw new RangeError("fromColumns() needs at least one column with one value");
1257
+ }
1258
+ const nrow = first.length;
1259
+ const ragged = columns.findIndex((column) => column.length !== nrow);
1260
+ if (ragged !== -1) {
1261
+ throw new RangeError(`every column needs ${nrow} values; column ${ragged} has ${columns[ragged].length}`);
1262
+ }
1263
+ const data = new Float64Array(nrow * ncol);
1264
+ columns.forEach((column, j) => {
1265
+ data.set(column, j * nrow);
1266
+ });
1267
+ return make(nrow, ncol, data, null);
1268
+ }
1269
+ function at(m, i, j) {
1270
+ if (!Number.isInteger(i) || i < 0 || i >= m.nrow) {
1271
+ throw new RangeError(`row index ${i} is outside 0..${m.nrow - 1}`);
1272
+ }
1273
+ if (!Number.isInteger(j) || j < 0 || j >= m.ncol) {
1274
+ throw new RangeError(`column index ${j} is outside 0..${m.ncol - 1}`);
1275
+ }
1276
+ return m.data[j * m.nrow + i];
1277
+ }
1278
+ function row(m, i) {
1279
+ if (!Number.isInteger(i) || i < 0 || i >= m.nrow) {
1280
+ throw new RangeError(`row index ${i} is outside 0..${m.nrow - 1}`);
1281
+ }
1282
+ return Array.from({ length: m.ncol }, (_, j) => m.data[j * m.nrow + i]);
1283
+ }
1284
+ function column(m, j) {
1285
+ if (!Number.isInteger(j) || j < 0 || j >= m.ncol) {
1286
+ throw new RangeError(`column index ${j} is outside 0..${m.ncol - 1}`);
1287
+ }
1288
+ return Array.from(m.data.subarray(j * m.nrow, (j + 1) * m.nrow));
1289
+ }
1290
+ function toRows(m) {
1291
+ return Array.from({ length: m.nrow }, (_, i) => row(m, i));
1292
+ }
1293
+ function toColumns(m) {
1294
+ return Array.from({ length: m.ncol }, (_, j) => column(m, j));
1295
+ }
1296
+ function fromFrame(data, columns) {
1297
+ const nrow = frameRows(data);
1298
+ const names = columns ?? numericColumns(data);
1299
+ const buffer = new Float64Array(nrow * names.length);
1300
+ names.forEach((name, j) => {
1301
+ buffer.set(requireNumericColumn(data, name, "columns"), j * nrow);
1302
+ });
1303
+ return make(nrow, names.length, buffer, names.length === 0 ? null : [null, [...names]]);
1304
+ }
1305
+
1306
+ // src/core/linalg/modelMatrix.ts
1307
+ function modelMatrix(data, spec) {
1308
+ const { outcome, intercept = true } = spec;
1309
+ const rowCount = frameRows(data);
1310
+ const terms = orderTerms(spec.terms);
1311
+ const columns = new Map;
1312
+ const read = (name, role) => {
1313
+ const held = columns.get(name);
1314
+ if (held !== undefined) {
1315
+ return held;
1316
+ }
1317
+ const column2 = requireNumericColumn(data, name, role);
1318
+ columns.set(name, column2);
1319
+ return column2;
1320
+ };
1321
+ if (outcome !== undefined) {
1322
+ read(outcome, "outcome");
1323
+ }
1324
+ terms.forEach((factors) => {
1325
+ factors.forEach((name) => read(name, "terms"));
1326
+ });
1327
+ const involved = [...columns.values()];
1328
+ const rows = Array.from({ length: rowCount }, (_, row2) => row2).filter((row2) => involved.every((column2) => Number.isFinite(column2[row2])));
1329
+ const termColumns = terms.map((factors) => rows.map((row2) => factors.reduce((product, name) => product * columns.get(name)[row2], 1)));
1330
+ const design = intercept ? [rows.map(() => 1), ...termColumns] : termColumns;
1331
+ const names = [
1332
+ ...intercept ? ["(Intercept)"] : [],
1333
+ ...terms.map((factors) => factors.join(":"))
1334
+ ];
1335
+ const assign = [
1336
+ ...intercept ? [0] : [],
1337
+ ...terms.map((_, index) => index + 1)
1338
+ ];
1339
+ const n = rows.length;
1340
+ const p = design.length;
1341
+ const buffer = new Float64Array(n * p);
1342
+ design.forEach((column2, j) => {
1343
+ buffer.set(column2, j * n);
1344
+ });
1345
+ return {
1346
+ matrix: make(n, p, buffer, p === 0 ? null : [null, names]),
1347
+ rows,
1348
+ assign,
1349
+ termLabels: names.slice(intercept ? 1 : 0)
1350
+ };
1351
+ }
1352
+ function orderTerms(terms) {
1353
+ const seen = new Set;
1354
+ const unique = [];
1355
+ terms.forEach((term) => {
1356
+ const factors = typeof term === "string" ? [term] : term;
1357
+ if (factors.length === 0) {
1358
+ throw new RangeError("an interaction term needs at least one column name");
1359
+ }
1360
+ const key = [...factors].sort().join("\x00");
1361
+ if (!seen.has(key)) {
1362
+ seen.add(key);
1363
+ unique.push(factors);
1364
+ }
1365
+ });
1366
+ return unique.map((factors, index) => ({ factors, index })).sort((a, b) => a.factors.length - b.factors.length || a.index - b.index).map(({ factors }) => factors);
1367
+ }
1368
+
1369
+ // src/core/linalg/namedVector.ts
1370
+ function namedVector(names, values) {
1371
+ if (names.length !== values.length) {
1372
+ throw new RangeError(`a named vector needs one name per value: ${names.length} names, ${values.length} values`);
1373
+ }
1374
+ return { names: [...names], values: [...values] };
1375
+ }
1376
+ function lookup(v, name) {
1377
+ const index = v.names.indexOf(name);
1378
+ return index === -1 ? undefined : v.values[index];
1379
+ }
1380
+
1381
+ // src/core/linalg/ops.ts
1382
+ function t(m) {
1383
+ const { nrow, ncol } = m;
1384
+ const data = new Float64Array(nrow * ncol);
1385
+ for (let j = 0;j < ncol; j++) {
1386
+ for (let i = 0;i < nrow; i++) {
1387
+ data[i * ncol + j] = m.data[j * nrow + i];
1388
+ }
1389
+ }
1390
+ const dimnames = m.dimnames === null ? null : [m.dimnames[1], m.dimnames[0]];
1391
+ return make(ncol, nrow, data, dimnames);
1392
+ }
1393
+ var transpose = t;
1394
+ function matmul(x, y) {
1395
+ const [left, right] = conformProduct(x, y);
1396
+ if (left.ncol !== right.nrow) {
1397
+ throw new RangeError(`non-conformable arguments: ${left.nrow} x ${left.ncol} %*% ${right.nrow} x ${right.ncol}`);
1398
+ }
1399
+ const data = product(left, right);
1400
+ return make(left.nrow, right.ncol, data, productDimnames(left, right));
1401
+ }
1402
+ function conformProduct(x, y) {
1403
+ if (isMatrix(x)) {
1404
+ if (isMatrix(y)) {
1405
+ return [x, y];
1406
+ }
1407
+ return [x, y.length === x.ncol ? asColumn(y) : asRow(y)];
1408
+ }
1409
+ if (isMatrix(y)) {
1410
+ return [x.length === y.nrow ? asRow(x) : asColumn(x), y];
1411
+ }
1412
+ return [asRow(x), x.length === 1 ? asRow(y) : asColumn(y)];
1413
+ }
1414
+ function crossprod(x, y = x) {
1415
+ const left = asColumn(x);
1416
+ const right = isMatrix(y) ? y : y.length === left.nrow ? asColumn(y) : asRow(y);
1417
+ if (left.nrow !== right.nrow) {
1418
+ throw new RangeError(`non-conformable arguments: crossprod of ${left.nrow} x ${left.ncol} and ${right.nrow} x ${right.ncol}`);
1419
+ }
1420
+ return matmul(t(left), right);
1421
+ }
1422
+ function tcrossprod(x, y = x) {
1423
+ const bothVectors = !isMatrix(x) && !isMatrix(y);
1424
+ const left = isMatrix(x) ? x : isMatrix(y) && y.ncol === x.length ? asRow(x) : asColumn(x);
1425
+ const right = isMatrix(y) ? y : !bothVectors && left.nrow === 1 ? asRow(y) : asColumn(y);
1426
+ if (left.ncol !== right.ncol) {
1427
+ throw new RangeError(`non-conformable arguments: tcrossprod of ${left.nrow} x ${left.ncol} and ${right.nrow} x ${right.ncol}`);
1428
+ }
1429
+ return matmul(left, t(right));
1430
+ }
1431
+ function product(x, y) {
1432
+ const { nrow, ncol: inner } = x;
1433
+ const { ncol } = y;
1434
+ const data = new Float64Array(nrow * ncol);
1435
+ for (let j = 0;j < ncol; j++) {
1436
+ for (let k = 0;k < inner; k++) {
1437
+ const factor = y.data[j * y.nrow + k];
1438
+ for (let i = 0;i < nrow; i++) {
1439
+ data[j * nrow + i] = fusedMultiplyAdd(x.data[k * nrow + i], factor, data[j * nrow + i]);
1440
+ }
1441
+ }
1442
+ }
1443
+ return data;
1444
+ }
1445
+ function productDimnames(x, y) {
1446
+ const rows = x.dimnames?.[0] ?? null;
1447
+ const columns = y.dimnames?.[1] ?? null;
1448
+ return rows === null && columns === null ? null : [rows, columns];
1449
+ }
1450
+ function asColumn(value) {
1451
+ if (isMatrix(value)) {
1452
+ return value;
1453
+ }
1454
+ return make(value.length, 1, Float64Array.from(value), null);
1455
+ }
1456
+ function asRow(value) {
1457
+ return make(1, value.length, Float64Array.from(value), null);
1458
+ }
1459
+ function isMatrix(value) {
1460
+ if (Array.isArray(value)) {
1461
+ return false;
1462
+ }
1463
+ if (typeof value === "object" && value !== null && "nrow" in value && "ncol" in value && "data" in value) {
1464
+ return true;
1465
+ }
1466
+ throw new TypeError("expected a Matrix or an array of numbers");
1467
+ }
1468
+ function cbind(...parts) {
1469
+ const matrices = parts.map(asColumn);
1470
+ const first = matrices[0];
1471
+ if (first === undefined) {
1472
+ throw new RangeError("cbind() needs at least one argument");
1473
+ }
1474
+ const nrow = first.nrow;
1475
+ matrices.forEach((m, index) => {
1476
+ if (m.nrow !== nrow) {
1477
+ 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})`);
1478
+ }
1479
+ });
1480
+ const ncol = matrices.reduce((total, m) => total + m.ncol, 0);
1481
+ const data = new Float64Array(nrow * ncol);
1482
+ let offset = 0;
1483
+ matrices.forEach((m) => {
1484
+ data.set(m.data, offset);
1485
+ offset += m.data.length;
1486
+ });
1487
+ const rows = matrices.find((m) => m.dimnames?.[0])?.dimnames?.[0] ?? null;
1488
+ const columns = boundNames(matrices.map((m) => ({ names: m.dimnames?.[1] ?? null, count: m.ncol })));
1489
+ return make(nrow, ncol, data, rows === null && columns === null ? null : [rows, columns]);
1490
+ }
1491
+ function rbind(...parts) {
1492
+ if (parts.length === 0) {
1493
+ throw new RangeError("rbind() needs at least one argument");
1494
+ }
1495
+ const transposed = parts.map((part) => isMatrix(part) ? t(part) : part);
1496
+ try {
1497
+ return t(cbind(...transposed));
1498
+ } catch (error) {
1499
+ if (error instanceof RangeError) {
1500
+ throw new RangeError(error.message.replace("number of rows", "number of columns"));
1501
+ }
1502
+ throw error;
1503
+ }
1504
+ }
1505
+ function boundNames(parts) {
1506
+ if (parts.every((part) => part.names === null)) {
1507
+ return null;
1508
+ }
1509
+ return parts.flatMap((part) => part.names ?? new Array(part.count).fill(""));
1510
+ }
1511
+ function diag(arg) {
1512
+ if (typeof arg === "number") {
1513
+ return identity(arg);
1514
+ }
1515
+ if (Array.isArray(arg)) {
1516
+ const values = arg;
1517
+ const n2 = values.length;
1518
+ const data = new Float64Array(n2 * n2);
1519
+ values.forEach((value, i) => {
1520
+ data[i * n2 + i] = value;
1521
+ });
1522
+ return make(n2, n2, data, null);
1523
+ }
1524
+ const m = arg;
1525
+ const n = Math.min(m.nrow, m.ncol);
1526
+ return Array.from({ length: n }, (_, i) => m.data[i * m.nrow + i]);
1527
+ }
1528
+ function identity(order) {
1529
+ if (!Number.isInteger(order) || order < 0) {
1530
+ throw new RangeError(`order must be a non-negative integer, got ${order}`);
1531
+ }
1532
+ const data = new Float64Array(order * order);
1533
+ for (let i = 0;i < order; i++) {
1534
+ data[i * order + i] = 1;
1535
+ }
1536
+ return make(order, order, data, null);
1537
+ }
1538
+
1539
+ // src/core/linalg/qr.ts
1540
+ var DEFAULT_QR_TOLERANCE = 0.0000001;
1541
+ function qr(x, options = {}) {
1542
+ const { tolerance = DEFAULT_QR_TOLERANCE } = options;
1543
+ if (!(tolerance >= 0)) {
1544
+ throw new RangeError(`tolerance must be a non-negative number, got ${tolerance}`);
1545
+ }
1546
+ if (!isMatrix(x)) {
1547
+ throw new TypeError("expected a Matrix");
1548
+ }
1549
+ if (!x.data.every(Number.isFinite)) {
1550
+ throw new RangeError("NA/NaN/Inf in foreign function call (arg 1)");
1551
+ }
1552
+ const { nrow, ncol } = x;
1553
+ const columns = Array.from({ length: ncol }, (_, j) => Array.from(x.data.subarray(j * nrow, (j + 1) * nrow)));
1554
+ const { householders, pivot, rank } = decompose(columns, tolerance, nrow);
1555
+ const data = new Float64Array(nrow * ncol);
1556
+ columns.forEach((column2, j) => {
1557
+ data.set(column2, j * nrow);
1558
+ });
1559
+ const rows = x.dimnames?.[0] ?? null;
1560
+ const names = x.dimnames?.[1] ?? null;
1561
+ const dimnames = rows === null && names === null ? null : [rows, names === null ? null : pivot.map((from) => names[from])];
1562
+ return { qr: make(nrow, ncol, data, dimnames), qraux: householders, pivot, rank };
1563
+ }
1564
+ function qrCoef(q, y) {
1565
+ if (isMatrix(y)) {
1566
+ requireRows(q, y.nrow);
1567
+ const width = q.qr.ncol;
1568
+ const data = new Float64Array(width * y.ncol);
1569
+ for (let j = 0;j < y.ncol; j++) {
1570
+ const solved = coefficientsOf(q, Array.from(y.data.subarray(j * y.nrow, (j + 1) * y.nrow)));
1571
+ solved.forEach((value, i) => {
1572
+ data[j * width + i] = value ?? Number.NaN;
1573
+ });
1574
+ }
1575
+ return make(width, y.ncol, data, readerDimnames(originalColumnNames(q), y));
1576
+ }
1577
+ requireRows(q, y.length);
1578
+ return coefficientsOf(q, y);
1579
+ }
1580
+ function coefficientsOf(q, y) {
1581
+ const qty = transformed(q, y, true);
1582
+ const solved = backSubstitute(q.qr, qty, q.rank);
1583
+ const coefficients = new Array(q.qr.ncol).fill(null);
1584
+ q.pivot.slice(0, q.rank).forEach((column2, position) => {
1585
+ coefficients[column2] = solved[position];
1586
+ });
1587
+ return coefficients;
1588
+ }
1589
+ function originalColumnNames(q) {
1590
+ const pivoted = q.qr.dimnames?.[1] ?? null;
1591
+ if (pivoted === null) {
1592
+ return null;
1593
+ }
1594
+ const names = new Array(pivoted.length);
1595
+ q.pivot.forEach((original, position) => {
1596
+ names[original] = pivoted[position];
1597
+ });
1598
+ return names;
1599
+ }
1600
+ function qrFitted(q, y) {
1601
+ return perColumn(q, y, (column2) => transformed(q, transformed(q, column2, true).map((value, index) => index < q.rank ? value : 0), false));
1602
+ }
1603
+ function qrResid(q, y) {
1604
+ return perColumn(q, y, (column2) => transformed(q, transformed(q, column2, true).map((value, index) => index < q.rank ? 0 : value), false));
1605
+ }
1606
+ function qrQty(q, y) {
1607
+ return perColumn(q, y, (column2) => transformed(q, column2, true));
1608
+ }
1609
+ function qrQy(q, y) {
1610
+ return perColumn(q, y, (column2) => transformed(q, column2, false));
1611
+ }
1612
+ function perColumn(q, y, read) {
1613
+ if (isMatrix(y)) {
1614
+ requireRows(q, y.nrow);
1615
+ const data = new Float64Array(y.nrow * y.ncol);
1616
+ for (let j = 0;j < y.ncol; j++) {
1617
+ data.set(read(Array.from(y.data.subarray(j * y.nrow, (j + 1) * y.nrow))), j * y.nrow);
1618
+ }
1619
+ return make(y.nrow, y.ncol, data, readerDimnames(null, y));
1620
+ }
1621
+ requireRows(q, y.length);
1622
+ return read(y);
1623
+ }
1624
+ function readerDimnames(rows, y) {
1625
+ const columns = y.dimnames?.[1] ?? null;
1626
+ return rows === null && columns === null ? null : [rows, columns];
1627
+ }
1628
+ function transformed(q, y, transpose2) {
1629
+ const result = [...y];
1630
+ const count = reflectorCount(q);
1631
+ if (transpose2) {
1632
+ for (let step = 0;step < count; step++) {
1633
+ applyReflector(q, step, result);
1634
+ }
1635
+ } else {
1636
+ for (let step = count - 1;step >= 0; step--) {
1637
+ applyReflector(q, step, result);
1638
+ }
1639
+ }
1640
+ return result;
1641
+ }
1642
+ function qrQ(q) {
1643
+ const { nrow, ncol } = q.qr;
1644
+ const width = Math.min(nrow, ncol);
1645
+ const data = new Float64Array(nrow * width);
1646
+ for (let j = 0;j < width; j++) {
1647
+ const unit = new Array(nrow).fill(0);
1648
+ unit[j] = 1;
1649
+ data.set(transformed(q, unit, false), j * nrow);
1650
+ }
1651
+ return make(nrow, width, data, null);
1652
+ }
1653
+ function qrR(q) {
1654
+ const { nrow, ncol } = q.qr;
1655
+ const height = Math.min(nrow, ncol);
1656
+ const data = new Float64Array(height * ncol);
1657
+ for (let j = 0;j < ncol; j++) {
1658
+ for (let i = 0;i <= Math.min(j, height - 1); i++) {
1659
+ data[j * height + i] = q.qr.data[j * nrow + i];
1660
+ }
1661
+ }
1662
+ const rows = q.qr.dimnames?.[0]?.slice(0, height) ?? null;
1663
+ const columns = q.qr.dimnames?.[1] ?? null;
1664
+ return make(height, ncol, data, rows === null && columns === null ? null : [rows, columns]);
1665
+ }
1666
+ function requireRows(q, rows) {
1667
+ if (rows !== q.qr.nrow) {
1668
+ throw new RangeError("'qr' and 'y' must have the same number of rows");
1669
+ }
1670
+ }
1671
+ function reflectorCount(q) {
1672
+ return Math.min(q.rank, q.qr.nrow - 1);
1673
+ }
1674
+ function applyReflector(q, step, vector) {
1675
+ const leading = q.qraux[step];
1676
+ if (leading === 0) {
1677
+ return;
1678
+ }
1679
+ const { nrow } = q.qr;
1680
+ const column2 = q.qr.data.subarray(step * nrow, (step + 1) * nrow);
1681
+ let inner = leading * vector[step];
1682
+ for (let row2 = step + 1;row2 < nrow; row2++) {
1683
+ inner = fusedMultiplyAdd(column2[row2], vector[row2], inner);
1684
+ }
1685
+ const factor = -inner / leading;
1686
+ vector[step] = fusedMultiplyAdd(factor, leading, vector[step]);
1687
+ for (let row2 = step + 1;row2 < nrow; row2++) {
1688
+ vector[row2] = fusedMultiplyAdd(factor, column2[row2], vector[row2]);
1689
+ }
751
1690
  }
752
1691
  function decompose(columns, tolerance, rows) {
753
1692
  const width = columns.length;
754
- const pivot = columns.map((_, column) => column);
755
- const originalNorms = columns.map((column) => norm(column, 0) || 1);
756
- const householders = new Array(width).fill(0);
757
- let live = width;
1693
+ const pivot = columns.map((_, column2) => column2);
1694
+ const qraux = columns.map((column2) => norm(column2, 0));
1695
+ const lastNorms = [...qraux];
1696
+ const originalNorms = qraux.map((value) => value || 1);
1697
+ let k = width + 1;
758
1698
  for (let step = 0;step < Math.min(rows, width); step++) {
759
- while (step < live && norm(columns[step], step) < originalNorms[step] * tolerance) {
760
- cycleToEnd(columns, pivot, originalNorms, step);
761
- live -= 1;
1699
+ while (step + 1 < k && qraux[step] < originalNorms[step] * tolerance) {
1700
+ moveToEnd(columns, step);
1701
+ moveToEnd(pivot, step);
1702
+ moveToEnd(qraux, step);
1703
+ moveToEnd(lastNorms, step);
1704
+ moveToEnd(originalNorms, step);
1705
+ k -= 1;
762
1706
  }
763
1707
  if (step === rows - 1) {
764
1708
  continue;
765
1709
  }
766
- householders[step] = reflect(columns, step, rows);
1710
+ reflect(columns, qraux, lastNorms, step, rows);
767
1711
  }
768
- return { householders, pivot, rank: Math.min(live, rows) };
1712
+ return { householders: qraux, pivot, rank: Math.min(k - 1, rows) };
769
1713
  }
770
- function reflect(columns, step, rows) {
771
- const column = columns[step];
772
- const length = norm(column, step);
1714
+ function reflect(columns, qraux, lastNorms, step, rows) {
1715
+ const column2 = columns[step];
1716
+ const length = norm(column2, step);
773
1717
  if (length === 0) {
774
- return 0;
1718
+ return;
775
1719
  }
776
- const pivotNorm = column[step] < 0 ? -length : length;
777
- for (let row = step;row < rows; row++) {
778
- column[row] = column[row] / pivotNorm;
1720
+ const pivotNorm = column2[step] < 0 ? -length : length;
1721
+ const reciprocal = 1 / pivotNorm;
1722
+ for (let row2 = step;row2 < rows; row2++) {
1723
+ column2[row2] = column2[row2] * reciprocal;
779
1724
  }
780
- const leading = 1 + column[step];
781
- column[step] = leading;
1725
+ const leading = 1 + column2[step];
1726
+ column2[step] = leading;
782
1727
  for (let index = step + 1;index < columns.length; index++) {
783
1728
  const other = columns[index];
784
1729
  let inner = 0;
785
- for (let row = step;row < rows; row++) {
786
- inner += column[row] * other[row];
1730
+ for (let row2 = step;row2 < rows; row2++) {
1731
+ inner = fusedMultiplyAdd(column2[row2], other[row2], inner);
787
1732
  }
788
1733
  const factor = -inner / leading;
789
- for (let row = step;row < rows; row++) {
790
- other[row] = other[row] + factor * column[row];
1734
+ for (let row2 = step;row2 < rows; row2++) {
1735
+ other[row2] = fusedMultiplyAdd(factor, column2[row2], other[row2]);
791
1736
  }
792
- }
793
- column[step] = -pivotNorm;
794
- return leading;
795
- }
796
- function applyHouseholders(columns, householders, response, count) {
797
- const rows = response.length;
798
- for (let step = 0;step < count; step++) {
799
- const leading = householders[step];
800
- if (leading === 0) {
801
- continue;
802
- }
803
- const column = columns[step];
804
- let inner = leading * response[step];
805
- for (let row = step + 1;row < rows; row++) {
806
- inner += column[row] * response[row];
807
- }
808
- const factor = -inner / leading;
809
- response[step] = response[step] + factor * leading;
810
- for (let row = step + 1;row < rows; row++) {
811
- response[row] = response[row] + factor * column[row];
1737
+ const running = qraux[index];
1738
+ if (running !== 0) {
1739
+ const ratio = Math.abs(other[step]) / running;
1740
+ const remaining = Math.max(1 - ratio * ratio, 0);
1741
+ if (Math.abs(remaining) < 0.000001) {
1742
+ qraux[index] = norm(other, step + 1);
1743
+ lastNorms[index] = qraux[index];
1744
+ } else {
1745
+ qraux[index] = running * Math.sqrt(remaining);
1746
+ }
812
1747
  }
813
1748
  }
1749
+ qraux[step] = leading;
1750
+ column2[step] = -pivotNorm;
814
1751
  }
815
- function backSubstitute(columns, response, rank) {
816
- const solved = new Array(rank).fill(0);
817
- for (let row = rank - 1;row >= 0; row--) {
818
- let value = response[row];
819
- for (let column = row + 1;column < rank; column++) {
820
- value -= columns[column][row] * solved[column];
1752
+ function backSubstitute(compact, response, rank) {
1753
+ const { nrow } = compact;
1754
+ const entry = (i, j) => compact.data[j * nrow + i];
1755
+ const solved = response.slice(0, rank);
1756
+ for (let column2 = rank - 1;column2 >= 0; column2--) {
1757
+ const value = solved[column2] / entry(column2, column2);
1758
+ solved[column2] = value;
1759
+ for (let row2 = 0;row2 < column2; row2++) {
1760
+ solved[row2] = fusedMultiplyAdd(-value, entry(row2, column2), solved[row2]);
821
1761
  }
822
- solved[row] = value / columns[row][row];
823
1762
  }
824
1763
  return solved;
825
1764
  }
826
- function cycleToEnd(columns, pivot, originalNorms, step) {
827
- moveToEnd(columns, step);
828
- moveToEnd(pivot, step);
829
- moveToEnd(originalNorms, step);
830
- }
831
1765
  function moveToEnd(track, from) {
832
1766
  const [moved] = track.splice(from, 1);
833
1767
  track.push(moved);
834
1768
  }
835
- function norm(column, from) {
836
- return Math.hypot(...column.slice(from));
1769
+ function norm(column2, from) {
1770
+ let squares = 0;
1771
+ for (let row2 = from;row2 < column2.length; row2++) {
1772
+ const value = column2[row2];
1773
+ squares = fusedMultiplyAdd(value, value, squares);
1774
+ }
1775
+ return Math.sqrt(squares);
1776
+ }
1777
+
1778
+ // src/core/linalg/lm.ts
1779
+ function lm(data, options) {
1780
+ const { outcome, intercept = true, tolerance = DEFAULT_QR_TOLERANCE } = options;
1781
+ const design = modelMatrix(data, options);
1782
+ const { rows } = design;
1783
+ const n = rows.length;
1784
+ if (n === 0) {
1785
+ throw new RangeError("0 (non-NA) cases");
1786
+ }
1787
+ const outcomeColumn = data[outcome];
1788
+ const y = rows.map((row2) => outcomeColumn[row2]);
1789
+ const factored = qr(design.matrix, { tolerance });
1790
+ const coefficients = qrCoef(factored, y);
1791
+ const residuals = qrResid(factored, y);
1792
+ const fitted = zipWith(y, residuals, (value, residual) => value - residual);
1793
+ const names = design.matrix.dimnames?.[1] ?? [];
1794
+ const { rank } = factored;
1795
+ const dfResidual = n - rank;
1796
+ const interceptCount = intercept ? 1 : 0;
1797
+ const rss = sum(residuals.map((r) => r * r));
1798
+ const centered = intercept ? mean(fitted) : 0;
1799
+ const mss = sum(fitted.map((f) => (f - centered) * (f - centered)));
1800
+ const resvar = rss / dfResidual;
1801
+ const numdf = rank - interceptCount;
1802
+ const rSquared = numdf > 0 ? mss / (mss + rss) : 0;
1803
+ const adjRSquared = numdf > 0 ? 1 - (1 - rSquared) * ((n - interceptCount) / dfResidual) : 0;
1804
+ const standardErrors = standardErrorsOf(factored, resvar, names.length);
1805
+ const tValues = zipWith(coefficients, standardErrors, (b, se) => b === null || se === null ? null : b / se);
1806
+ const pValues = tValues.map((tv) => tv === null ? null : 2 * pt(-Math.abs(tv), dfResidual));
1807
+ return {
1808
+ coefficients: namedVector(names, coefficients),
1809
+ standardErrors: namedVector(names, standardErrors),
1810
+ tValues: namedVector(names, tValues),
1811
+ pValues: namedVector(names, pValues),
1812
+ fitted: padded(fitted, rows, outcomeColumn.length),
1813
+ residuals: padded(residuals, rows, outcomeColumn.length),
1814
+ rank,
1815
+ dfResidual,
1816
+ rSquared,
1817
+ adjRSquared,
1818
+ sigma: Math.sqrt(resvar),
1819
+ fStatistic: numdf > 0 ? { value: mss / numdf / resvar, numdf, dendf: dfResidual } : null,
1820
+ rows,
1821
+ termLabels: design.termLabels
1822
+ };
1823
+ }
1824
+ function standardErrorsOf(factored, resvar, width) {
1825
+ const { rank, pivot } = factored;
1826
+ const { nrow } = factored.qr;
1827
+ const r = (i, j) => factored.qr.data[j * nrow + i];
1828
+ const inverse = Array.from({ length: rank }, (_, k) => {
1829
+ const x = new Array(rank).fill(0);
1830
+ x[k] = 1 / r(k, k);
1831
+ for (let i = k - 1;i >= 0; i--) {
1832
+ let total = 0;
1833
+ for (let j = i + 1;j <= k; j++) {
1834
+ total += r(i, j) * x[j];
1835
+ }
1836
+ x[i] = -total / r(i, i);
1837
+ }
1838
+ return x;
1839
+ });
1840
+ const diagonal = Array.from({ length: rank }, (_, i) => sum(inverse.map((columnOfInverse) => columnOfInverse[i] ** 2)));
1841
+ const errors = new Array(width).fill(null);
1842
+ pivot.slice(0, rank).forEach((original, position) => {
1843
+ errors[original] = Math.sqrt(diagonal[position] * resvar);
1844
+ });
1845
+ return errors;
1846
+ }
1847
+ function padded(values, rows, length) {
1848
+ const out = new Array(length).fill(Number.NaN);
1849
+ rows.forEach((row2, index) => {
1850
+ out[row2] = values[index];
1851
+ });
1852
+ return out;
837
1853
  }
838
1854
 
839
1855
  // src/core/moderation.ts
@@ -850,43 +1866,25 @@ function moderationSurface(data, options) {
850
1866
  }
851
1867
  named.add(control);
852
1868
  });
853
- const rows = frameRows(data);
1869
+ frameRows(data);
854
1870
  const y = requireNumericColumn(data, outcome, "outcome");
855
1871
  const ivColumn = requireNumericColumn(data, iv, "iv");
856
1872
  const modColumn = requireNumericColumn(data, mod, "mod");
857
1873
  const controlColumns = controls.map((control) => requireNumericColumn(data, control, "controls"));
858
1874
  const modelColumns = [y, ivColumn, modColumn, ...controlColumns];
859
- const completeRows = y.map((_, row) => row).filter((row) => modelColumns.every((column) => Number.isFinite(column[row])));
860
- if (completeRows.length === 0) {
1875
+ const anyComplete = y.some((_, row2) => modelColumns.every((column2) => Number.isFinite(column2[row2])));
1876
+ if (!anyComplete) {
861
1877
  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");
862
1878
  }
863
- const designColumns = [
864
- { name: "(Intercept)", values: new Array(rows).fill(1) },
865
- { name: iv, values: ivColumn },
866
- { name: mod, values: modColumn },
867
- ...controls.map((control, index) => ({
868
- name: control,
869
- values: controlColumns[index]
870
- })),
871
- ...interaction ? [
872
- {
873
- name: `${iv}:${mod}`,
874
- values: zipWith(ivColumn, modColumn, (a, b) => a * b)
875
- }
876
- ] : []
877
- ];
878
- const design = completeRows.map((row) => designColumns.map((column) => column.values[row]));
879
- const fit = leastSquares(design, completeRows.map((row) => y[row]));
880
- const coefficients = designColumns.map((column, index) => ({
881
- name: column.name,
882
- value: fit.coefficients[index] ?? null
883
- }));
884
- const fitted = new Array(rows).fill(Number.NaN);
885
- const residuals = new Array(rows).fill(Number.NaN);
886
- completeRows.forEach((row, survivor) => {
887
- fitted[row] = fit.fitted[survivor];
888
- residuals[row] = fit.residuals[survivor];
1879
+ const fit = lm(data, {
1880
+ outcome,
1881
+ terms: [iv, mod, ...controls, ...interaction ? [[iv, mod]] : []]
889
1882
  });
1883
+ const { fitted, residuals } = fit;
1884
+ const coefficients = fit.coefficients.names.map((name, index) => ({
1885
+ name,
1886
+ value: fit.coefficients.values[index] ?? null
1887
+ }));
890
1888
  const ivValues = rSeq(...extent(ivColumn), GRID_STEPS);
891
1889
  const modValues = rSeq(...extent(modColumn), GRID_STEPS);
892
1890
  const holds = Object.fromEntries(controls.map((control, index) => [
@@ -1980,5 +2978,5 @@ export {
1980
2978
  DEFAULT_SCATTER3D_STYLE
1981
2979
  };
1982
2980
 
1983
- //# debugId=167ACAEEE5BCC45664756E2164756E21
2981
+ //# debugId=F613BD0490CF40B764756E2164756E21
1984
2982
  //# sourceMappingURL=3d.js.map