@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.
- package/CHANGELOG.md +105 -0
- package/README.md +127 -110
- package/dist/3d.js +1120 -122
- package/dist/3d.js.map +16 -9
- package/dist/core/arith.d.ts.map +1 -1
- package/dist/core/linalg/cov.d.ts +50 -0
- package/dist/core/linalg/cov.d.ts.map +1 -0
- package/dist/core/linalg/eigen.d.ts +53 -0
- package/dist/core/linalg/eigen.d.ts.map +1 -0
- package/dist/core/linalg/lm.d.ts +78 -0
- package/dist/core/linalg/lm.d.ts.map +1 -0
- package/dist/core/linalg/lu.d.ts +154 -0
- package/dist/core/linalg/lu.d.ts.map +1 -0
- package/dist/core/linalg/matrix.d.ts +131 -0
- package/dist/core/linalg/matrix.d.ts.map +1 -0
- package/dist/core/linalg/modelMatrix.d.ts +69 -0
- package/dist/core/linalg/modelMatrix.d.ts.map +1 -0
- package/dist/core/linalg/namedVector.d.ts +37 -0
- package/dist/core/linalg/namedVector.d.ts.map +1 -0
- package/dist/core/linalg/ops.d.ts +120 -0
- package/dist/core/linalg/ops.d.ts.map +1 -0
- package/dist/core/linalg/prcomp.d.ts +66 -0
- package/dist/core/linalg/prcomp.d.ts.map +1 -0
- package/dist/core/linalg/qr.d.ts +134 -0
- package/dist/core/linalg/qr.d.ts.map +1 -0
- package/dist/core/linalg/vector.d.ts +68 -0
- package/dist/core/linalg/vector.d.ts.map +1 -0
- package/dist/core/moderation.d.ts +6 -3
- package/dist/core/moderation.d.ts.map +1 -1
- package/dist/core/ols.d.ts +4 -7
- package/dist/core/ols.d.ts.map +1 -1
- package/dist/data/moderationData.d.ts +2 -2
- package/dist/data/pcaDegenerate.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1601 -863
- package/dist/index.js.map +17 -11
- package/dist/linalg.d.ts +36 -0
- package/dist/linalg.d.ts.map +1 -0
- package/dist/linalg.js +1860 -0
- package/dist/linalg.js.map +24 -0
- package/dist/plot/moderation3d.d.ts +1 -1
- package/dist/plot/sampling.d.ts +45 -0
- package/dist/plot/sampling.d.ts.map +1 -1
- package/dist/plot/scatter3d.d.ts +1 -1
- 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/
|
|
715
|
-
var
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
const
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
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
|
-
|
|
749
|
-
|
|
750
|
-
|
|
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((_,
|
|
755
|
-
const
|
|
756
|
-
const
|
|
757
|
-
|
|
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 <
|
|
760
|
-
|
|
761
|
-
|
|
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
|
-
|
|
1710
|
+
reflect(columns, qraux, lastNorms, step, rows);
|
|
767
1711
|
}
|
|
768
|
-
return { householders, pivot, rank: Math.min(
|
|
1712
|
+
return { householders: qraux, pivot, rank: Math.min(k - 1, rows) };
|
|
769
1713
|
}
|
|
770
|
-
function reflect(columns, step, rows) {
|
|
771
|
-
const
|
|
772
|
-
const length = norm(
|
|
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
|
|
1718
|
+
return;
|
|
775
1719
|
}
|
|
776
|
-
const pivotNorm =
|
|
777
|
-
|
|
778
|
-
|
|
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 +
|
|
781
|
-
|
|
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
|
|
786
|
-
inner
|
|
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
|
|
790
|
-
other[
|
|
1734
|
+
for (let row2 = step;row2 < rows; row2++) {
|
|
1735
|
+
other[row2] = fusedMultiplyAdd(factor, column2[row2], other[row2]);
|
|
791
1736
|
}
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
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(
|
|
816
|
-
const
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
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(
|
|
836
|
-
|
|
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
|
-
|
|
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
|
|
860
|
-
if (
|
|
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
|
|
864
|
-
|
|
865
|
-
|
|
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=
|
|
2981
|
+
//# debugId=F613BD0490CF40B764756E2164756E21
|
|
1984
2982
|
//# sourceMappingURL=3d.js.map
|