Show t-test results based on individual measurements to analysis task page.
authordewei_zhu@apple.com <dewei_zhu@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 23 Aug 2018 23:11:10 +0000 (23:11 +0000)
committerdewei_zhu@apple.com <dewei_zhu@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 23 Aug 2018 23:11:10 +0000 (23:11 +0000)
https://bugs.webkit.org/show_bug.cgi?id=188425

Reviewed by Ryosuke Niwa.

Added comparison for individual iterations in analysis task page.
Added comparison for individual iterations for notification on A/B tests completion.
Refactored t-distribution inverse lookup to any degree of freedom with 5 significant figures.

* public/shared/statistics.js: Refactored t-distribution inverse lookup function and adapted this
change to all invocations.
(Statistics.new.this.supportedConfidenceIntervalProbabilities):
(Statistics.new.this.supportedOneSideTTestProbabilities):
(Statistics.new.this.confidenceIntervalDelta):
(Statistics.new.sampleMeanAndVarianceForMultipleSamples):
(Statistics.new.this.probabilityRangeForWelchsT):
(Statistics.new.this.probabilityRangeForWelchsTFromTwoSampleSets):
(Statistics.new.this._determinetwoSidedProbabilityBoundaryForWelchsT):
(Statistics.new.this.computeWelchsT):
(Statistics.new.this._computeWelchsTFromStatistics):
(Statistics.new.this.minimumTForOneSidedProbability): Function that does t-distribution inverse lookup.
* public/v3/components/analysis-results-viewer.js: Adapted TestGroup.compareTestResults change.
(AnalysisResultsViewer.TestGroupStackingBlock.prototype._measurementsForCommitSet):
(AnalysisResultsViewer.TestGroupStackingBlock.prototype._computeTestGroupStatus):
(AnalysisResultsViewer.TestGroupStackingBlock.prototype._valuesForCommitSet): Deleted.
* public/v3/components/test-group-results-viewer.js: Show both comparisions for both individual and mean.
(TestGroupResultsViewer.prototype._renderResultsTable):
(TestGroupResultsViewer.prototype._buildRowForMetric.):
(TestGroupResultsViewer.prototype._buildValueMap):
* public/v3/models/test-group.js:
(TestGroup.compareTestResults): Added comparison for individual iterations.
* tools/js/test-group-result-page.js:
(TestGroupResultPage.prototype._constructTableForMetric):
(TestGroupResultPage.prototype.get styleTemplate):
(TestGroupResultPage):
(TestGroupResultPage.prototype._URLForAnalysisTask): Renamed to '_resultsForTestGroup'
* unit-tests/statistics-tests.js: Updated and added unit tests.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@235255 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Websites/perf.webkit.org/ChangeLog
Websites/perf.webkit.org/public/shared/statistics.js
Websites/perf.webkit.org/public/v3/components/analysis-results-viewer.js
Websites/perf.webkit.org/public/v3/components/test-group-results-viewer.js
Websites/perf.webkit.org/public/v3/models/test-group.js
Websites/perf.webkit.org/tools/js/test-group-result-page.js
Websites/perf.webkit.org/unit-tests/statistics-tests.js

index c127a55..e0eac5f 100644 (file)
@@ -1,3 +1,43 @@
+2018-08-22  Dewei Zhu  <dewei_zhu@apple.com>
+
+        Show t-test results based on individual measurements to analysis task page.
+        https://bugs.webkit.org/show_bug.cgi?id=188425
+
+        Reviewed by Ryosuke Niwa.
+
+        Added comparison for individual iterations in analysis task page.
+        Added comparison for individual iterations for notification on A/B tests completion.
+        Refactored t-distribution inverse lookup to any degree of freedom with 5 significant figures.
+
+        * public/shared/statistics.js: Refactored t-distribution inverse lookup function and adapted this
+        change to all invocations.
+        (Statistics.new.this.supportedConfidenceIntervalProbabilities):
+        (Statistics.new.this.supportedOneSideTTestProbabilities):
+        (Statistics.new.this.confidenceIntervalDelta):
+        (Statistics.new.sampleMeanAndVarianceForMultipleSamples):
+        (Statistics.new.this.probabilityRangeForWelchsT):
+        (Statistics.new.this.probabilityRangeForWelchsTFromTwoSampleSets):
+        (Statistics.new.this._determinetwoSidedProbabilityBoundaryForWelchsT):
+        (Statistics.new.this.computeWelchsT):
+        (Statistics.new.this._computeWelchsTFromStatistics):
+        (Statistics.new.this.minimumTForOneSidedProbability): Function that does t-distribution inverse lookup.
+        * public/v3/components/analysis-results-viewer.js: Adapted TestGroup.compareTestResults change.
+        (AnalysisResultsViewer.TestGroupStackingBlock.prototype._measurementsForCommitSet):
+        (AnalysisResultsViewer.TestGroupStackingBlock.prototype._computeTestGroupStatus):
+        (AnalysisResultsViewer.TestGroupStackingBlock.prototype._valuesForCommitSet): Deleted.
+        * public/v3/components/test-group-results-viewer.js: Show both comparisions for both individual and mean.
+        (TestGroupResultsViewer.prototype._renderResultsTable):
+        (TestGroupResultsViewer.prototype._buildRowForMetric.):
+        (TestGroupResultsViewer.prototype._buildValueMap):
+        * public/v3/models/test-group.js:
+        (TestGroup.compareTestResults): Added comparison for individual iterations.
+        * tools/js/test-group-result-page.js:
+        (TestGroupResultPage.prototype._constructTableForMetric):
+        (TestGroupResultPage.prototype.get styleTemplate):
+        (TestGroupResultPage):
+        (TestGroupResultPage.prototype._URLForAnalysisTask): Renamed to '_resultsForTestGroup'
+        * unit-tests/statistics-tests.js: Updated and added unit tests.
+
 2018-07-13  Dewei Zhu  <dewei_zhu@apple.com>
 
         CustomConfigurationTestGroupForm should dispatch different arguments based on whether analysis task is created.
index 056ba94..871846c 100644 (file)
@@ -33,10 +33,10 @@ var Statistics = new (function () {
     }
 
     this.supportedConfidenceIntervalProbabilities = function () {
-        var supportedProbabilities = [];
-        for (var probability in tDistributionByOneSidedProbability)
+        const supportedProbabilities = [];
+        for (const probability in tDistributionByOneSidedProbability)
             supportedProbabilities.push(oneSidedToTwoSidedProbability(probability).toFixed(2));
-        return supportedProbabilities
+        return supportedProbabilities;
     }
 
     this.supportedOneSideTTestProbabilities = function () {
@@ -52,13 +52,8 @@ var Statistics = new (function () {
         }
         if (numberOfSamples - 2 < 0)
             return NaN;
-        var deltas = tDistributionByOneSidedProbability[oneSidedProbability];
-        var degreesOfFreedom = numberOfSamples - 1;
-        if (degreesOfFreedom > deltas.length)
-            throw 'We only support up to ' + deltas.length + ' degrees of freedom';
-
         // d = c * S/sqrt(numberOfSamples) where c ~ t-distribution(degreesOfFreedom) and S is the sample standard deviation.
-        return deltas[degreesOfFreedom - 1] * this.sampleStandardDeviation(numberOfSamples, sum, squareSum) / Math.sqrt(numberOfSamples);
+        return this.minimumTForOneSidedProbability(oneSidedProbability, numberOfSamples - 1) * this.sampleStandardDeviation(numberOfSamples, sum, squareSum) / Math.sqrt(numberOfSamples);
     }
 
     this.confidenceInterval = function (values, probability) {
@@ -73,41 +68,73 @@ var Statistics = new (function () {
         return this.computeWelchsT(values1, 0, values1.length, values2, 0, values2.length, probability).significantlyDifferent;
     }
 
+    function sampleMeanAndVarianceFromMultipleSamples(samples) {
+        let sum = 0;
+        let squareSum = 0;
+        let size = 0;
+        console.log(samples);
+        for (const sample of samples) {
+            sum += sample.sum;
+            squareSum += sample.squareSum;
+            size += sample.sampleSize;
+        }
+        const mean = sum / size;
+        const unbiasedSampleVariance = (squareSum - sum * sum / size) / (size - 1);
+        return {
+            mean,
+            variance: unbiasedSampleVariance,
+            size,
+            degreesOfFreedom: size - 1,
+        }
+    }
+
     this.probabilityRangeForWelchsT = function (values1, values2) {
         var result = this.computeWelchsT(values1, 0, values1.length, values2, 0, values2.length);
-        if (isNaN(result.t) || isNaN(result.degreesOfFreedom))
+        return this._determinetwoSidedProbabilityBoundaryForWelchsT(result.t, result.degreesOfFreedom);
+    }
+
+    this.probabilityRangeForWelchsTForMultipleSamples = function (sampleSet1, sampleSet2) {
+        const stat1 = sampleMeanAndVarianceFromMultipleSamples(sampleSet1);
+        const stat2 = sampleMeanAndVarianceFromMultipleSamples(sampleSet2);
+        const combinedT = this._computeWelchsTFromStatistics(stat1, stat2);
+        return this._determinetwoSidedProbabilityBoundaryForWelchsT(combinedT.t, combinedT.degreesOfFreedom)
+    }
+
+    this._determinetwoSidedProbabilityBoundaryForWelchsT = function(t, degreesOfFreedom) {
+        if (isNaN(t) || isNaN(degreesOfFreedom))
             return {t: NaN, degreesOfFreedom:NaN, range: [null, null]};
 
-        var lowerBound = null;
-        var upperBound = null;
-        for (var probability in tDistributionByOneSidedProbability) {
-            var twoSidedProbability = oneSidedToTwoSidedProbability(probability);
-            if (result.t > tDistributionByOneSidedProbability[probability][Math.round(result.degreesOfFreedom - 1)])
+        let lowerBound = null;
+        let upperBound = null;
+        for (const probability in tDistributionByOneSidedProbability) {
+            const twoSidedProbability = oneSidedToTwoSidedProbability(probability);
+            if (t > this.minimumTForOneSidedProbability(probability, Math.round(degreesOfFreedom)))
                 lowerBound = twoSidedProbability;
             else if (lowerBound) {
                 upperBound = twoSidedProbability;
                 break;
             }
         }
-        return {t: result.t, degreesOfFreedom: result.degreesOfFreedom, range: [lowerBound, upperBound]};
-    }
+        return {tdegreesOfFreedom, range: [lowerBound, upperBound]};
+    };
 
     this.computeWelchsT = function (values1, startIndex1, length1, values2, startIndex2, length2, probability) {
-        var stat1 = sampleMeanAndVarianceForValues(values1, startIndex1, length1);
-        var stat2 = sampleMeanAndVarianceForValues(values2, startIndex2, length2);
-        var sumOfSampleVarianceOverSampleSize = stat1.variance / stat1.size + stat2.variance / stat2.size;
-        var t = Math.abs((stat1.mean - stat2.mean) / Math.sqrt(sumOfSampleVarianceOverSampleSize));
+        const stat1 = sampleMeanAndVarianceForValues(values1, startIndex1, length1);
+        const stat2 = sampleMeanAndVarianceForValues(values2, startIndex2, length2);
+        const {t, degreesOfFreedom} = this._computeWelchsTFromStatistics(stat1, stat2);
+        const minT = this.minimumTForOneSidedProbability(twoSidedToOneSidedProbability(probability || 0.8), Math.round(degreesOfFreedom));
+        return {t, degreesOfFreedom, significantlyDifferent: t > minT};
+    };
+
+    this._computeWelchsTFromStatistics = function(stat1, stat2) {
+        const sumOfSampleVarianceOverSampleSize = stat1.variance / stat1.size + stat2.variance / stat2.size;
+        const t = Math.abs((stat1.mean - stat2.mean) / Math.sqrt(sumOfSampleVarianceOverSampleSize));
 
         // http://en.wikipedia.org/wiki/Welch–Satterthwaite_equation
-        var degreesOfFreedom = sumOfSampleVarianceOverSampleSize * sumOfSampleVarianceOverSampleSize
+        const degreesOfFreedom = sumOfSampleVarianceOverSampleSize * sumOfSampleVarianceOverSampleSize
             / (stat1.variance * stat1.variance / stat1.size / stat1.size / stat1.degreesOfFreedom
                 + stat2.variance * stat2.variance / stat2.size / stat2.size / stat2.degreesOfFreedom);
-        var minT = tDistributionByOneSidedProbability[twoSidedToOneSidedProbability(probability || 0.8)][Math.round(degreesOfFreedom - 1)];
-        return {
-            t: t,
-            degreesOfFreedom: degreesOfFreedom,
-            significantlyDifferent: t > minT,
-        };
+        return {t, degreesOfFreedom};
     }
 
     this.findRangesForChangeDetectionsWithWelchsTTest = function (values, segmentations, oneSidedPossibility=0.99) {
@@ -258,56 +285,252 @@ var Statistics = new (function () {
         recursivelySplitIntoTwoSegmentsAtMaxTIfSignificantlyDifferent(values, startIndex + argTMax, length - argTMax, minLength, segments);
     }
 
-    var tDistributionByOneSidedProbability = {
-        0.9: [
-            3.077684, 1.885618, 1.637744, 1.533206, 1.475884, 1.439756, 1.414924, 1.396815, 1.383029, 1.372184,
-            1.363430, 1.356217, 1.350171, 1.345030, 1.340606, 1.336757, 1.333379, 1.330391, 1.327728, 1.325341,
-            1.323188, 1.321237, 1.319460, 1.317836, 1.316345, 1.314972, 1.313703, 1.312527, 1.311434, 1.310415,
-            1.309464, 1.308573, 1.307737, 1.306952, 1.306212, 1.305514, 1.304854, 1.304230, 1.303639, 1.303077,
-            1.302543, 1.302035, 1.301552, 1.301090, 1.300649, 1.300228, 1.299825, 1.299439, 1.299069, 1.298714,
-
-            1.298373, 1.298045, 1.297730, 1.297426, 1.297134, 1.296853, 1.296581, 1.296319, 1.296066, 1.295821,
-            1.295585, 1.295356, 1.295134, 1.294920, 1.294712, 1.294511, 1.294315, 1.294126, 1.293942, 1.293763,
-            1.293589, 1.293421, 1.293256, 1.293097, 1.292941, 1.292790, 1.292643, 1.292500, 1.292360, 1.292224,
-            1.292091, 1.291961, 1.291835, 1.291711, 1.291591, 1.291473, 1.291358, 1.291246, 1.291136, 1.291029,
-            1.290924, 1.290821, 1.290721, 1.290623, 1.290527, 1.290432, 1.290340, 1.290250, 1.290161, 1.290075],
-        0.95: [
-            6.313752, 2.919986, 2.353363, 2.131847, 2.015048, 1.943180, 1.894579, 1.859548, 1.833113, 1.812461,
-            1.795885, 1.782288, 1.770933, 1.761310, 1.753050, 1.745884, 1.739607, 1.734064, 1.729133, 1.724718,
-            1.720743, 1.717144, 1.713872, 1.710882, 1.708141, 1.705618, 1.703288, 1.701131, 1.699127, 1.697261,
-            1.695519, 1.693889, 1.692360, 1.690924, 1.689572, 1.688298, 1.687094, 1.685954, 1.684875, 1.683851,
-            1.682878, 1.681952, 1.681071, 1.680230, 1.679427, 1.678660, 1.677927, 1.677224, 1.676551, 1.675905,
-
-            1.675285, 1.674689, 1.674116, 1.673565, 1.673034, 1.672522, 1.672029, 1.671553, 1.671093, 1.670649,
-            1.670219, 1.669804, 1.669402, 1.669013, 1.668636, 1.668271, 1.667916, 1.667572, 1.667239, 1.666914,
-            1.666600, 1.666294, 1.665996, 1.665707, 1.665425, 1.665151, 1.664885, 1.664625, 1.664371, 1.664125,
-            1.663884, 1.663649, 1.663420, 1.663197, 1.662978, 1.662765, 1.662557, 1.662354, 1.662155, 1.661961,
-            1.661771, 1.661585, 1.661404, 1.661226, 1.661052, 1.660881, 1.660715, 1.660551, 1.660391, 1.660234],
-        0.975: [
-            12.706205, 4.302653, 3.182446, 2.776445, 2.570582, 2.446912, 2.364624, 2.306004, 2.262157, 2.228139,
-            2.200985, 2.178813, 2.160369, 2.144787, 2.131450, 2.119905, 2.109816, 2.100922, 2.093024, 2.085963,
-            2.079614, 2.073873, 2.068658, 2.063899, 2.059539, 2.055529, 2.051831, 2.048407, 2.045230, 2.042272,
-            2.039513, 2.036933, 2.034515, 2.032245, 2.030108, 2.028094, 2.026192, 2.024394, 2.022691, 2.021075,
-            2.019541, 2.018082, 2.016692, 2.015368, 2.014103, 2.012896, 2.011741, 2.010635, 2.009575, 2.008559,
-
-            2.007584, 2.006647, 2.005746, 2.004879, 2.004045, 2.003241, 2.002465, 2.001717, 2.000995, 2.000298,
-            1.999624, 1.998972, 1.998341, 1.997730, 1.997138, 1.996564, 1.996008, 1.995469, 1.994945, 1.994437,
-            1.993943, 1.993464, 1.992997, 1.992543, 1.992102, 1.991673, 1.991254, 1.990847, 1.990450, 1.990063,
-            1.989686, 1.989319, 1.988960, 1.988610, 1.988268, 1.987934, 1.987608, 1.987290, 1.986979, 1.986675,
-            1.986377, 1.986086, 1.985802, 1.985523, 1.985251, 1.984984, 1.984723, 1.984467, 1.984217, 1.983972],
-        0.99: [
-            31.820516, 6.964557, 4.540703, 3.746947, 3.364930, 3.142668, 2.997952, 2.896459, 2.821438, 2.763769,
-            2.718079, 2.680998, 2.650309, 2.624494, 2.602480, 2.583487, 2.566934, 2.552380, 2.539483, 2.527977,
-            2.517648, 2.508325, 2.499867, 2.492159, 2.485107, 2.478630, 2.472660, 2.467140, 2.462021, 2.457262,
-            2.452824, 2.448678, 2.444794, 2.441150, 2.437723, 2.434494, 2.431447, 2.428568, 2.425841, 2.423257,
-            2.420803, 2.418470, 2.416250, 2.414134, 2.412116, 2.410188, 2.408345, 2.406581, 2.404892, 2.403272,
-
-            2.401718, 2.400225, 2.398790, 2.397410, 2.396081, 2.394801, 2.393568, 2.392377, 2.391229, 2.390119,
-            2.389047, 2.388011, 2.387008, 2.386037, 2.385097, 2.384186, 2.383302, 2.382446, 2.381615, 2.380807,
-            2.380024, 2.379262, 2.378522, 2.377802, 2.377102, 2.376420, 2.375757, 2.375111, 2.374482, 2.373868,
-            2.373270, 2.372687, 2.372119, 2.371564, 2.371022, 2.370493, 2.369977, 2.369472, 2.368979, 2.368497,
-            2.368026, 2.367566, 2.367115, 2.366674, 2.366243, 2.365821, 2.365407, 2.365002, 2.364606, 2.364217]
+    this.minimumTForOneSidedProbability = function(probability, degreesOfFreedom) {
+        if (degreesOfFreedom < 1 || isNaN(degreesOfFreedom))
+            return NaN;
+        const tDistributionTableForProbability = tDistributionByOneSidedProbability[probability];
+        if (degreesOfFreedom <= tDistributionTableForProbability.probabilityToTValue.length)
+            return tDistributionTableForProbability.probabilityToTValue[degreesOfFreedom - 1];
+        const tValuesSortedByProbability = tDistributionTableForProbability.tValuesSortedByProbability;
+
+        let low = 0;
+        let high = tValuesSortedByProbability.length;
+        while (low < high) {
+            const mid = low + Math.floor((high - low) / 2);
+            const entry = tValuesSortedByProbability[mid];
+            if (degreesOfFreedom <= entry.maxDF)
+                high = mid;
+            else
+                low = mid + 1;
+        }
+
+        return tValuesSortedByProbability[low].value;
+    }
+
+    const tDistributionByOneSidedProbability = {
+        0.9: {
+            probabilityToTValue: [
+                3.0777,  1.8856,  1.6377,  1.5332,  1.4759,  1.4398,  1.4149,  1.3968,  1.383,   1.3722,
+                1.3634,  1.3562,  1.3502,  1.345,   1.3406,  1.3368,  1.3334,  1.3304,  1.3277,  1.3253,
+                1.3232,  1.3212,  1.3195,  1.3178,  1.3163,  1.315,   1.3137,  1.3125,  1.3114,  1.3104,
+                1.3095,  1.3086,  1.3077,  1.307,   1.3062,  1.3055,  1.3049,  1.3042,  1.3036,  1.3031,
+                1.3025,  1.302,   1.3016,  1.3011,  1.3006,  1.3002,  1.2998,  1.2994,  1.2991,  1.2987,
+                1.2984,  1.298,   1.2977,  1.2974,  1.2971,  1.2969,  1.2966,  1.2963,  1.2961,  1.2958,
+                1.2956,  1.2954,  1.2951,  1.2949,  1.2947,  1.2945,  1.2943,  1.2941,  1.2939,  1.2938,
+                1.2936,  1.2934,  1.2933,  1.2931,  1.2929,  1.2928,  1.2926,  1.2925,  1.2924,  1.2922,
+                1.2921,  1.292,   1.2918,  1.2917,  1.2916,  1.2915,  1.2914,  1.2912,  1.2911,  1.291,
+                1.2909,  1.2908,  1.2907,  1.2906,  1.2905,  1.2904,  1.2903,  1.2903,  1.2902,  1.2901
+            ],
+            tValuesSortedByProbability: [
+                {maxDF: 101, value: 1.2900},  {maxDF: 102, value: 1.2899},  {maxDF: 103, value: 1.2898},  {maxDF: 105, value: 1.2897},
+                {maxDF: 106, value: 1.2896},  {maxDF: 107, value: 1.2895},  {maxDF: 109, value: 1.2894},  {maxDF: 110, value: 1.2893},
+                {maxDF: 112, value: 1.2892},  {maxDF: 113, value: 1.2891},  {maxDF: 115, value: 1.2890},  {maxDF: 116, value: 1.2889},
+                {maxDF: 118, value: 1.2888},  {maxDF: 119, value: 1.2887},  {maxDF: 121, value: 1.2886},  {maxDF: 123, value: 1.2885},
+                {maxDF: 125, value: 1.2884},  {maxDF: 127, value: 1.2883},  {maxDF: 128, value: 1.2882},  {maxDF: 130, value: 1.2881},
+                {maxDF: 132, value: 1.2880},  {maxDF: 135, value: 1.2879},  {maxDF: 137, value: 1.2878},  {maxDF: 139, value: 1.2877},
+                {maxDF: 141, value: 1.2876},  {maxDF: 144, value: 1.2875},  {maxDF: 146, value: 1.2874},  {maxDF: 149, value: 1.2873},
+                {maxDF: 151, value: 1.2872},  {maxDF: 154, value: 1.2871},  {maxDF: 157, value: 1.2870},  {maxDF: 160, value: 1.2869},
+                {maxDF: 163, value: 1.2868},  {maxDF: 166, value: 1.2867},  {maxDF: 170, value: 1.2866},  {maxDF: 173, value: 1.2865},
+                {maxDF: 177, value: 1.2864},  {maxDF: 180, value: 1.2863},  {maxDF: 184, value: 1.2862},  {maxDF: 188, value: 1.2861},
+                {maxDF: 193, value: 1.2860},  {maxDF: 197, value: 1.2859},  {maxDF: 202, value: 1.2858},  {maxDF: 207, value: 1.2857},
+                {maxDF: 212, value: 1.2856},  {maxDF: 217, value: 1.2855},  {maxDF: 223, value: 1.2854},  {maxDF: 229, value: 1.2853},
+                {maxDF: 235, value: 1.2852},  {maxDF: 242, value: 1.2851},  {maxDF: 249, value: 1.2850},  {maxDF: 257, value: 1.2849},
+                {maxDF: 265, value: 1.2848},  {maxDF: 273, value: 1.2847},  {maxDF: 283, value: 1.2846},  {maxDF: 292, value: 1.2845},
+                {maxDF: 303, value: 1.2844},  {maxDF: 314, value: 1.2843},  {maxDF: 326, value: 1.2842},  {maxDF: 339, value: 1.2841},
+                {maxDF: 353, value: 1.2840},  {maxDF: 369, value: 1.2839},  {maxDF: 385, value: 1.2838},  {maxDF: 404, value: 1.2837},
+                {maxDF: 424, value: 1.2836},  {maxDF: 446, value: 1.2835},  {maxDF: 471, value: 1.2834},  {maxDF: 499, value: 1.2833},
+                {maxDF: 530, value: 1.2832},  {maxDF: 565, value: 1.2831},  {maxDF: 606, value: 1.2830},  {maxDF: 652, value: 1.2829},
+                {maxDF: 707, value: 1.2828},  {maxDF: 771, value: 1.2827},  {maxDF: 848, value: 1.2826},  {maxDF: 942, value: 1.2825},
+                {maxDF: 1060, value: 1.2824}, {maxDF: 1212, value: 1.2823}, {maxDF: 1415, value: 1.2822}, {maxDF: 1699, value: 1.2821},
+                {maxDF: 2125, value: 1.2820}, {maxDF: 2837, value: 1.2819}, {maxDF: 4266, value: 1.2818}, {maxDF: 8601, value: 1.2817},
+                {maxDF: Infinity, value: 1.2816}
+            ]
+        },
+        0.95: {
+            probabilityToTValue: [
+                6.3138,  2.92,    2.3534,  2.1318,  2.015,   1.9432,  1.8946,  1.8595,  1.8331,  1.8125,
+                1.7959,  1.7823,  1.7709,  1.7613,  1.7531,  1.7459,  1.7396,  1.7341,  1.7291,  1.7247,
+                1.7207,  1.7171,  1.7139,  1.7109,  1.7081,  1.7056,  1.7033,  1.7011,  1.6991,  1.6973,
+                1.6955,  1.6939,  1.6924,  1.6909,  1.6896,  1.6883,  1.6871,  1.686,   1.6849,  1.6839,
+                1.6829,  1.682,   1.6811,  1.6802,  1.6794,  1.6787,  1.6779,  1.6772,  1.6766,  1.6759,
+                1.6753,  1.6747,  1.6741,  1.6736,  1.673,   1.6725,  1.672,   1.6716,  1.6711,  1.6706,
+                1.6702,  1.6698,  1.6694,  1.669,   1.6686,  1.6683,  1.6679,  1.6676,  1.6672,  1.6669,
+                1.6666,  1.6663,  1.666,   1.6657,  1.6654,  1.6652,  1.6649,  1.6646,  1.6644,  1.6641,
+                1.6639,  1.6636,  1.6634,  1.6632,  1.663,   1.6628,  1.6626,  1.6624,  1.6622,  1.662,
+                1.6618,  1.6616,  1.6614,  1.6612,  1.6611,  1.6609,  1.6607,  1.6606,  1.6604,  1.6602,
+                1.6601,  1.6599,  1.6598,  1.6596,  1.6595,  1.6594,  1.6592,  1.6591,  1.659,   1.6588,
+                1.6587,  1.6586,  1.6585,  1.6583,  1.6582,  1.6581,  1.658,   1.6579,  1.6578,  1.6577
+            ],
+            tValuesSortedByProbability: [
+                {maxDF: 121, value: 1.6575},  {maxDF: 122, value: 1.6574},   {maxDF: 123, value: 1.6573},  {maxDF: 124, value: 1.6572},
+                {maxDF: 125, value: 1.6571},  {maxDF: 126, value: 1.6570},   {maxDF: 127, value: 1.6569},  {maxDF: 129, value: 1.6568},
+                {maxDF: 130, value: 1.6567},  {maxDF: 131, value: 1.6566},   {maxDF: 132, value: 1.6565},  {maxDF: 133, value: 1.6564},
+                {maxDF: 134, value: 1.6563},  {maxDF: 135, value: 1.6562},   {maxDF: 137, value: 1.6561},  {maxDF: 138, value: 1.6560},
+                {maxDF: 139, value: 1.6559},  {maxDF: 140, value: 1.6558},   {maxDF: 142, value: 1.6557},  {maxDF: 143, value: 1.6556},
+                {maxDF: 144, value: 1.6555},  {maxDF: 146, value: 1.6554},   {maxDF: 147, value: 1.6553},  {maxDF: 148, value: 1.6552},
+                {maxDF: 150, value: 1.6551},  {maxDF: 151, value: 1.6550},   {maxDF: 153, value: 1.6549},  {maxDF: 154, value: 1.6548},
+                {maxDF: 156, value: 1.6547},  {maxDF: 158, value: 1.6546},   {maxDF: 159, value: 1.6545},  {maxDF: 161, value: 1.6544},
+                {maxDF: 163, value: 1.6543},  {maxDF: 164, value: 1.6542},   {maxDF: 166, value: 1.6541},  {maxDF: 168, value: 1.6540},
+                {maxDF: 170, value: 1.6539},  {maxDF: 172, value: 1.6538},   {maxDF: 174, value: 1.6537},  {maxDF: 176, value: 1.6536},
+                {maxDF: 178, value: 1.6535},  {maxDF: 180, value: 1.6534},   {maxDF: 182, value: 1.6533},  {maxDF: 184, value: 1.6532},
+                {maxDF: 186, value: 1.6531},  {maxDF: 189, value: 1.6530},   {maxDF: 191, value: 1.6529},  {maxDF: 193, value: 1.6528},
+                {maxDF: 196, value: 1.6527},  {maxDF: 198, value: 1.6526},   {maxDF: 201, value: 1.6525},  {maxDF: 204, value: 1.6524},
+                {maxDF: 206, value: 1.6523},  {maxDF: 209, value: 1.6522},   {maxDF: 212, value: 1.6521},  {maxDF: 215, value: 1.6520},
+                {maxDF: 218, value: 1.6519},  {maxDF: 221, value: 1.6518},   {maxDF: 225, value: 1.6517},  {maxDF: 228, value: 1.6516},
+                {maxDF: 231, value: 1.6515},  {maxDF: 235, value: 1.6514},   {maxDF: 239, value: 1.6513},  {maxDF: 242, value: 1.6512},
+                {maxDF: 246, value: 1.6511},  {maxDF: 250, value: 1.6510},   {maxDF: 255, value: 1.6509},  {maxDF: 259, value: 1.6508},
+                {maxDF: 263, value: 1.6507},  {maxDF: 268, value: 1.6506},   {maxDF: 273, value: 1.6505},  {maxDF: 278, value: 1.6504},
+                {maxDF: 283, value: 1.6503},  {maxDF: 288, value: 1.6502},   {maxDF: 294, value: 1.6501},  {maxDF: 299, value: 1.6500},
+                {maxDF: 305, value: 1.6499},  {maxDF: 312, value: 1.6498},   {maxDF: 318, value: 1.6497},  {maxDF: 325, value: 1.6496},
+                {maxDF: 332, value: 1.6495},  {maxDF: 339, value: 1.6494},   {maxDF: 347, value: 1.6493},  {maxDF: 355, value: 1.6492},
+                {maxDF: 364, value: 1.6491},  {maxDF: 372, value: 1.6490},   {maxDF: 382, value: 1.6489},  {maxDF: 392, value: 1.6488},
+                {maxDF: 402, value: 1.6487},  {maxDF: 413, value: 1.6486},   {maxDF: 424, value: 1.6485},  {maxDF: 436, value: 1.6484},
+                {maxDF: 449, value: 1.6483},  {maxDF: 463, value: 1.6482},   {maxDF: 477, value: 1.6481},  {maxDF: 493, value: 1.6480},
+                {maxDF: 509, value: 1.6479},  {maxDF: 527, value: 1.6478},   {maxDF: 545, value: 1.6477},  {maxDF: 566, value: 1.6476},
+                {maxDF: 587, value: 1.6475},  {maxDF: 611, value: 1.6474},   {maxDF: 636, value: 1.6473},  {maxDF: 664, value: 1.6472},
+                {maxDF: 694, value: 1.6471},  {maxDF: 727, value: 1.6470},   {maxDF: 764, value: 1.6469},  {maxDF: 804, value: 1.6468},
+                {maxDF: 849, value: 1.6467},  {maxDF: 899, value: 1.6466},   {maxDF: 955, value: 1.6465},  {maxDF: 1019, value: 1.6464},
+                {maxDF: 1092, value: 1.6463}, {maxDF: 1176, value: 1.6462},  {maxDF: 1274, value: 1.6461}, {maxDF: 1390, value: 1.6460},
+                {maxDF: 1530, value: 1.6459}, {maxDF: 1700, value: 1.6458},  {maxDF: 1914, value: 1.6457}, {maxDF: 2189, value: 1.6456},
+                {maxDF: 2555, value: 1.6455}, {maxDF: 3070, value: 1.6454},  {maxDF: 3845, value: 1.6453}, {maxDF: 5142, value: 1.6452},
+                {maxDF: 7760, value: 1.6451}, {maxDF: 15812, value: 1.6450}, {maxDF: Infinity, value: 1.6449}
+            ]
+        },
+        0.975: {
+            probabilityToTValue: [
+                12.7062,  4.3027,  3.1824,  2.7764,  2.5706,  2.4469,  2.3646,  2.306,   2.2622,  2.2281,
+                2.201,    2.1788,  2.1604,  2.1448,  2.1314,  2.1199,  2.1098,  2.1009,  2.093,   2.086,
+                2.0796,   2.0739,  2.0687,  2.0639,  2.0595,  2.0555,  2.0518,  2.0484,  2.0452,  2.0423,
+                2.0395,   2.0369,  2.0345,  2.0322,  2.0301,  2.0281,  2.0262,  2.0244,  2.0227,  2.0211,
+                2.0195,   2.0181,  2.0167,  2.0154,  2.0141,  2.0129,  2.0117,  2.0106,  2.0096,  2.0086,
+                2.0076,   2.0066,  2.0057,  2.0049,  2.004,   2.0032,  2.0025,  2.0017,  2.001,   2.0003,
+                1.9996,   1.999,   1.9983,  1.9977,  1.9971,  1.9966,  1.996,   1.9955,  1.9949,  1.9944,
+                1.9939,   1.9935,  1.993,   1.9925,  1.9921,  1.9917,  1.9913,  1.9908,  1.9905,  1.9901,
+                1.9897,   1.9893,  1.989,   1.9886,  1.9883,  1.9879,  1.9876,  1.9873,  1.987,   1.9867,
+                1.9864,   1.9861,  1.9858,  1.9855,  1.9853,  1.985,   1.9847,  1.9845,  1.9842,  1.984,
+                1.9837,   1.9835,  1.9833,  1.983,   1.9828,  1.9826,  1.9824,  1.9822,  1.982,   1.9818,
+                1.9816,   1.9814,  1.9812,  1.981,   1.9808,  1.9806,  1.9804,  1.9803,  1.9801,  1.9799,
+                1.9798,   1.9796,  1.9794,  1.9793,  1.9791,  1.979,   1.9788,  1.9787,  1.9785,  1.9784,
+                1.9782,   1.9781,  1.978,   1.9778,  1.9777,  1.9776,  1.9774,  1.9773,  1.9772,  1.9771,
+                1.9769,   1.9768,  1.9767,  1.9766,  1.9765,  1.9763,  1.9762,  1.9761,  1.976,   1.9759,
+                1.9758,   1.9757,  1.9756,  1.9755,  1.9754,  1.9753,  1.9752,  1.9751,  1.975,   1.9749,
+                1.9748,   1.9747,  1.9746,  1.9745
+            ],
+            tValuesSortedByProbability: [
+                {maxDF: 166, value: 1.9744},  {maxDF: 167, value: 1.9743},  {maxDF: 168, value: 1.9742},   {maxDF: 169, value: 1.9741},
+                {maxDF: 170, value: 1.9740},  {maxDF: 172, value: 1.9739},  {maxDF: 173, value: 1.9738},   {maxDF: 174, value: 1.9737},
+                {maxDF: 175, value: 1.9736},  {maxDF: 177, value: 1.9735},  {maxDF: 178, value: 1.9734},   {maxDF: 179, value: 1.9733},
+                {maxDF: 181, value: 1.9732},  {maxDF: 182, value: 1.9731},  {maxDF: 183, value: 1.9730},   {maxDF: 185, value: 1.9729},
+                {maxDF: 186, value: 1.9728},  {maxDF: 188, value: 1.9727},  {maxDF: 189, value: 1.9726},   {maxDF: 191, value: 1.9725},
+                {maxDF: 192, value: 1.9724},  {maxDF: 194, value: 1.9723},  {maxDF: 195, value: 1.9722},   {maxDF: 197, value: 1.9721},
+                {maxDF: 199, value: 1.9720},  {maxDF: 200, value: 1.9719},  {maxDF: 202, value: 1.9718},   {maxDF: 204, value: 1.9717},
+                {maxDF: 205, value: 1.9716},  {maxDF: 207, value: 1.9715},  {maxDF: 209, value: 1.9714},   {maxDF: 211, value: 1.9713},
+                {maxDF: 213, value: 1.9712},  {maxDF: 215, value: 1.9711},  {maxDF: 217, value: 1.9710},   {maxDF: 219, value: 1.9709},
+                {maxDF: 221, value: 1.9708},  {maxDF: 223, value: 1.9707},  {maxDF: 225, value: 1.9706},   {maxDF: 227, value: 1.9705},
+                {maxDF: 229, value: 1.9704},  {maxDF: 231, value: 1.9703},  {maxDF: 234, value: 1.9702},   {maxDF: 236, value: 1.9701},
+                {maxDF: 238, value: 1.9700},  {maxDF: 241, value: 1.9699},  {maxDF: 243, value: 1.9698},   {maxDF: 246, value: 1.9697},
+                {maxDF: 248, value: 1.9696},  {maxDF: 251, value: 1.9695},  {maxDF: 253, value: 1.9694},   {maxDF: 256, value: 1.9693},
+                {maxDF: 259, value: 1.9692},  {maxDF: 262, value: 1.9691},  {maxDF: 265, value: 1.9690},   {maxDF: 268, value: 1.9689},
+                {maxDF: 271, value: 1.9688},  {maxDF: 274, value: 1.9687},  {maxDF: 277, value: 1.9686},   {maxDF: 280, value: 1.9685},
+                {maxDF: 284, value: 1.9684},  {maxDF: 287, value: 1.9683},  {maxDF: 290, value: 1.9682},   {maxDF: 294, value: 1.9681},
+                {maxDF: 298, value: 1.9680},  {maxDF: 302, value: 1.9679},  {maxDF: 305, value: 1.9678},   {maxDF: 309, value: 1.9677},
+                {maxDF: 313, value: 1.9676},  {maxDF: 318, value: 1.9675},  {maxDF: 322, value: 1.9674},   {maxDF: 326, value: 1.9673},
+                {maxDF: 331, value: 1.9672},  {maxDF: 335, value: 1.9671},  {maxDF: 340, value: 1.9670},   {maxDF: 345, value: 1.9669},
+                {maxDF: 350, value: 1.9668},  {maxDF: 355, value: 1.9667},  {maxDF: 361, value: 1.9666},   {maxDF: 366, value: 1.9665},
+                {maxDF: 372, value: 1.9664},  {maxDF: 378, value: 1.9663},  {maxDF: 384, value: 1.9662},   {maxDF: 390, value: 1.9661},
+                {maxDF: 397, value: 1.9660},  {maxDF: 404, value: 1.9659},  {maxDF: 411, value: 1.9658},   {maxDF: 418, value: 1.9657},
+                {maxDF: 425, value: 1.9656},  {maxDF: 433, value: 1.9655},  {maxDF: 441, value: 1.9654},   {maxDF: 449, value: 1.9653},
+                {maxDF: 458, value: 1.9652},  {maxDF: 467, value: 1.9651},  {maxDF: 476, value: 1.9650},   {maxDF: 486, value: 1.9649},
+                {maxDF: 496, value: 1.9648},  {maxDF: 507, value: 1.9647},  {maxDF: 518, value: 1.9646},   {maxDF: 530, value: 1.9645},
+                {maxDF: 542, value: 1.9644},  {maxDF: 554, value: 1.9643},  {maxDF: 567, value: 1.9642},   {maxDF: 581, value: 1.9641},
+                {maxDF: 596, value: 1.9640},  {maxDF: 611, value: 1.9639},  {maxDF: 627, value: 1.9638},   {maxDF: 644, value: 1.9637},
+                {maxDF: 662, value: 1.9636},  {maxDF: 681, value: 1.9635},  {maxDF: 701, value: 1.9634},   {maxDF: 723, value: 1.9633},
+                {maxDF: 745, value: 1.9632},  {maxDF: 769, value: 1.9631},  {maxDF: 795, value: 1.9630},   {maxDF: 823, value: 1.9629},
+                {maxDF: 852, value: 1.9628},  {maxDF: 884, value: 1.9627},  {maxDF: 918, value: 1.9626},   {maxDF: 955, value: 1.9625},
+                {maxDF: 995, value: 1.9624},  {maxDF: 1038, value: 1.9623}, {maxDF: 1086, value: 1.9622},  {maxDF: 1138, value: 1.9621},
+                {maxDF: 1195, value: 1.9620}, {maxDF: 1259, value: 1.9619}, {maxDF: 1329, value: 1.9618},  {maxDF: 1408, value: 1.9617},
+                {maxDF: 1496, value: 1.9616}, {maxDF: 1597, value: 1.9615}, {maxDF: 1712, value: 1.9614},  {maxDF: 1845, value: 1.9613},
+                {maxDF: 2001, value: 1.9612}, {maxDF: 2185, value: 1.9611}, {maxDF: 2407, value: 1.9610},  {maxDF: 2678, value: 1.9609},
+                {maxDF: 3019, value: 1.9608}, {maxDF: 3459, value: 1.9607}, {maxDF: 4049, value: 1.9606},  {maxDF: 4882, value: 1.9605},
+                {maxDF: 6146, value: 1.9604}, {maxDF: 8295, value: 1.9603}, {maxDF: 12754, value: 1.9602}, {maxDF: 27580, value: 1.9601},
+                {maxDF: Infinity, value: 1.9600}
+            ]
+        },
+        0.99: {
+            probabilityToTValue: [
+                31.8205,  6.9646,  4.5407,  3.7469,  3.3649,  3.1427,  2.998,   2.8965,  2.8214,  2.7638,
+                2.7181,   2.681,   2.6503,  2.6245,  2.6025,  2.5835,  2.5669,  2.5524,  2.5395,  2.528,
+                2.5176,   2.5083,  2.4999,  2.4922,  2.4851,  2.4786,  2.4727,  2.4671,  2.462,   2.4573,
+                2.4528,   2.4487,  2.4448,  2.4411,  2.4377,  2.4345,  2.4314,  2.4286,  2.4258,  2.4233,
+                2.4208,   2.4185,  2.4163,  2.4141,  2.4121,  2.4102,  2.4083,  2.4066,  2.4049,  2.4033,
+                2.4017,   2.4002,  2.3988,  2.3974,  2.3961,  2.3948,  2.3936,  2.3924,  2.3912,  2.3901,
+                2.389,    2.388,   2.387,   2.386,   2.3851,  2.3842,  2.3833,  2.3824,  2.3816,  2.3808,
+                2.38,     2.3793,  2.3785,  2.3778,  2.3771,  2.3764,  2.3758,  2.3751,  2.3745,  2.3739,
+                2.3733,   2.3727,  2.3721,  2.3716,  2.371,   2.3705,  2.37,    2.3695,  2.369,   2.3685,
+                2.368,    2.3676,  2.3671,  2.3667,  2.3662,  2.3658,  2.3654,  2.365,   2.3646,  2.3642,
+                2.3638,   2.3635,  2.3631,  2.3627,  2.3624,  2.362,   2.3617,  2.3614,  2.361,   2.3607,
+                2.3604,   2.3601,  2.3598,  2.3595,  2.3592,  2.3589,  2.3586,  2.3584,  2.3581,  2.3578,
+                2.3576,   2.3573,  2.357,   2.3568,  2.3565,  2.3563,  2.3561,  2.3558,  2.3556,  2.3554,
+                2.3552,   2.3549,  2.3547,  2.3545,  2.3543,  2.3541,  2.3539,  2.3537,  2.3535,  2.3533,
+                2.3531,   2.3529,  2.3527,  2.3525,  2.3523,  2.3522,  2.352,   2.3518,  2.3516,  2.3515,
+                2.3513,   2.3511,  2.351,   2.3508,  2.3506,  2.3505,  2.3503,  2.3502,  2.35,    2.3499,
+                2.3497,   2.3496,  2.3494,  2.3493,  2.3492,  2.349,   2.3489,  2.3487,  2.3486,  2.3485,
+                2.3484,   2.3482,  2.3481,  2.348,   2.3478,  2.3477,  2.3476,  2.3475,  2.3474,  2.3472,
+                2.3471,   2.347,   2.3469,  2.3468,  2.3467,  2.3466,  2.3465,  2.3463,  2.3462,  2.3461,
+                2.346,    2.3459,  2.3458,  2.3457,  2.3456,  2.3455,  2.3454,  2.3453,  2.3452,  2.3451
+            ],
+            tValuesSortedByProbability: [
+                {maxDF: 201, value: 2.3450},   {maxDF: 203, value: 2.3449},   {maxDF: 204, value: 2.3448},     {maxDF: 205, value: 2.3447},
+                {maxDF: 206, value: 2.3446},   {maxDF: 207, value: 2.3445},   {maxDF: 208, value: 2.3444},     {maxDF: 209, value: 2.3443},
+                {maxDF: 211, value: 2.3442},   {maxDF: 212, value: 2.3441},   {maxDF: 213, value: 2.3440},     {maxDF: 214, value: 2.3439},
+                {maxDF: 215, value: 2.3438},   {maxDF: 217, value: 2.3437},   {maxDF: 218, value: 2.3436},     {maxDF: 219, value: 2.3435},
+                {maxDF: 220, value: 2.3434},   {maxDF: 222, value: 2.3433},   {maxDF: 223, value: 2.3432},     {maxDF: 224, value: 2.3431},
+                {maxDF: 226, value: 2.3430},   {maxDF: 227, value: 2.3429},   {maxDF: 228, value: 2.3428},     {maxDF: 230, value: 2.3427},
+                {maxDF: 231, value: 2.3426},   {maxDF: 233, value: 2.3425},   {maxDF: 234, value: 2.3424},     {maxDF: 236, value: 2.3423},
+                {maxDF: 237, value: 2.3422},   {maxDF: 239, value: 2.3421},   {maxDF: 240, value: 2.3420},     {maxDF: 242, value: 2.3419},
+                {maxDF: 243, value: 2.3418},   {maxDF: 245, value: 2.3417},   {maxDF: 246, value: 2.3416},     {maxDF: 248, value: 2.3415},
+                {maxDF: 250, value: 2.3414},   {maxDF: 251, value: 2.3413},   {maxDF: 253, value: 2.3412},     {maxDF: 255, value: 2.3411},
+                {maxDF: 256, value: 2.3410},   {maxDF: 258, value: 2.3409},   {maxDF: 260, value: 2.3408},     {maxDF: 262, value: 2.3407},
+                {maxDF: 264, value: 2.3406},   {maxDF: 265, value: 2.3405},   {maxDF: 267, value: 2.3404},     {maxDF: 269, value: 2.3403},
+                {maxDF: 271, value: 2.3402},   {maxDF: 273, value: 2.3401},   {maxDF: 275, value: 2.3400},     {maxDF: 277, value: 2.3399},
+                {maxDF: 279, value: 2.3398},   {maxDF: 281, value: 2.3397},   {maxDF: 283, value: 2.3396},     {maxDF: 286, value: 2.3395},
+                {maxDF: 288, value: 2.3394},   {maxDF: 290, value: 2.3393},   {maxDF: 292, value: 2.3392},     {maxDF: 295, value: 2.3391},
+                {maxDF: 297, value: 2.3390},   {maxDF: 299, value: 2.3389},   {maxDF: 302, value: 2.3388},     {maxDF: 304, value: 2.3387},
+                {maxDF: 307, value: 2.3386},   {maxDF: 309, value: 2.3385},   {maxDF: 312, value: 2.3384},     {maxDF: 314, value: 2.3383},
+                {maxDF: 317, value: 2.3382},   {maxDF: 320, value: 2.3381},   {maxDF: 322, value: 2.3380},     {maxDF: 325, value: 2.3379},
+                {maxDF: 328, value: 2.3378},   {maxDF: 331, value: 2.3377},   {maxDF: 334, value: 2.3376},     {maxDF: 337, value: 2.3375},
+                {maxDF: 340, value: 2.3374},   {maxDF: 343, value: 2.3373},   {maxDF: 346, value: 2.3372},     {maxDF: 349, value: 2.3371},
+                {maxDF: 353, value: 2.3370},   {maxDF: 356, value: 2.3369},   {maxDF: 360, value: 2.3368},     {maxDF: 363, value: 2.3367},
+                {maxDF: 367, value: 2.3366},   {maxDF: 370, value: 2.3365},   {maxDF: 374, value: 2.3364},     {maxDF: 378, value: 2.3363},
+                {maxDF: 381, value: 2.3362},   {maxDF: 385, value: 2.3361},   {maxDF: 389, value: 2.3360},     {maxDF: 393, value: 2.3359},
+                {maxDF: 398, value: 2.3358},   {maxDF: 402, value: 2.3357},   {maxDF: 406, value: 2.3356},     {maxDF: 411, value: 2.3355},
+                {maxDF: 415, value: 2.3354},   {maxDF: 420, value: 2.3353},   {maxDF: 425, value: 2.3352},     {maxDF: 430, value: 2.3351},
+                {maxDF: 435, value: 2.3350},   {maxDF: 440, value: 2.3349},   {maxDF: 445, value: 2.3348},     {maxDF: 450, value: 2.3347},
+                {maxDF: 456, value: 2.3346},   {maxDF: 461, value: 2.3345},   {maxDF: 467, value: 2.3344},     {maxDF: 473, value: 2.3343},
+                {maxDF: 479, value: 2.3342},   {maxDF: 485, value: 2.3341},   {maxDF: 492, value: 2.3340},     {maxDF: 498, value: 2.3339},
+                {maxDF: 505, value: 2.3338},   {maxDF: 512, value: 2.3337},   {maxDF: 519, value: 2.3336},     {maxDF: 526, value: 2.3335},
+                {maxDF: 534, value: 2.3334},   {maxDF: 541, value: 2.3333},   {maxDF: 549, value: 2.3332},     {maxDF: 557, value: 2.3331},
+                {maxDF: 566, value: 2.3330},   {maxDF: 575, value: 2.3329},   {maxDF: 584, value: 2.3328},     {maxDF: 593, value: 2.3327},
+                {maxDF: 602, value: 2.3326},   {maxDF: 612, value: 2.3325},   {maxDF: 622, value: 2.3324},     {maxDF: 633, value: 2.3323},
+                {maxDF: 644, value: 2.3322},   {maxDF: 655, value: 2.3321},   {maxDF: 667, value: 2.3320},     {maxDF: 679, value: 2.3319},
+                {maxDF: 691, value: 2.3318},   {maxDF: 704, value: 2.3317},   {maxDF: 718, value: 2.3316},     {maxDF: 732, value: 2.3315},
+                {maxDF: 747, value: 2.3314},   {maxDF: 762, value: 2.3313},   {maxDF: 778, value: 2.3312},     {maxDF: 794, value: 2.3311},
+                {maxDF: 811, value: 2.3310},   {maxDF: 829, value: 2.3309},   {maxDF: 848, value: 2.3308},     {maxDF: 868, value: 2.3307},
+                {maxDF: 888, value: 2.3306},   {maxDF: 910, value: 2.3305},   {maxDF: 933, value: 2.3304},     {maxDF: 957, value: 2.3303},
+                {maxDF: 982, value: 2.3302},   {maxDF: 1008, value: 2.3301},  {maxDF: 1036, value: 2.3300},    {maxDF: 1066, value: 2.3299},
+                {maxDF: 1097, value: 2.3298},  {maxDF: 1130, value: 2.3297},  {maxDF: 1166, value: 2.3296},    {maxDF: 1203, value: 2.3295},
+                {maxDF: 1243, value: 2.3294},  {maxDF: 1286, value: 2.3293},  {maxDF: 1332, value: 2.3292},    {maxDF: 1381, value: 2.3291},
+                {maxDF: 1434, value: 2.3290},  {maxDF: 1491, value: 2.3289},  {maxDF: 1553, value: 2.3288},    {maxDF: 1621, value: 2.3287},
+                {maxDF: 1694, value: 2.3286},  {maxDF: 1775, value: 2.3285},  {maxDF: 1864, value: 2.3284},    {maxDF: 1962, value: 2.3283},
+                {maxDF: 2070, value: 2.3282},  {maxDF: 2192, value: 2.3281},  {maxDF: 2329, value: 2.3280},    {maxDF: 2484, value: 2.3279},
+                {maxDF: 2661, value: 2.3278},  {maxDF: 2865, value: 2.3277},  {maxDF: 3103, value: 2.3276},    {maxDF: 3385, value: 2.3275},
+                {maxDF: 3722, value: 2.3274},  {maxDF: 4135, value: 2.3273},  {maxDF: 4650, value: 2.3272},    {maxDF: 5312, value: 2.3271},
+                {maxDF: 6194, value: 2.3270},  {maxDF: 7428, value: 2.3269},  {maxDF: 9274, value: 2.3268},    {maxDF: 12344, value: 2.3267},
+                {maxDF: 18450, value: 2.3266}, {maxDF: 36515, value: 2.3265}, {maxDF: 1754068, value: 2.3264}, {maxDF: Infinity, value: 2.3263}
+            ]
+        },
     };
+
     function oneSidedToTwoSidedProbability(probability) { return 2 * probability - 1; }
     function twoSidedToOneSidedProbability(probability) { return (1 - (1 - probability) / 2); }
 
index ecd6e2f..0bc8c28 100644 (file)
@@ -506,11 +506,11 @@ AnalysisResultsViewer.TestGroupStackingBlock = class {
     startRowIndex() { return this._commitSetIndexRowIndexMap[0].rowIndex; }
     endRowIndex() { return this._commitSetIndexRowIndexMap[this._commitSetIndexRowIndexMap.length - 1].rowIndex; }
 
-    _valuesForCommitSet(testGroup, commitSet)
+    _measurementsForCommitSet(testGroup, commitSet)
     {
         return testGroup.requestsForCommitSet(commitSet).map((request) => {
             return this._analysisResultsView.resultForRequest(request);
-        }).filter((result) => !!result).map((result) => result.value);
+        }).filter((result) => !!result);
     }
 
     _computeTestGroupStatus()
@@ -518,9 +518,9 @@ AnalysisResultsViewer.TestGroupStackingBlock = class {
         if (!this.isComplete())
             return {label: null, title: null, status: null};
         console.assert(this._commitSetIndexRowIndexMap.length <= 2); // FIXME: Support having more root sets.
-        const startValues = this._valuesForCommitSet(this._testGroup, this._commitSetIndexRowIndexMap[0].commitSet);
-        const endValues = this._valuesForCommitSet(this._testGroup, this._commitSetIndexRowIndexMap[1].commitSet);
+        const startValues = this._measurementsForCommitSet(this._testGroup, this._commitSetIndexRowIndexMap[0].commitSet);
+        const endValues = this._measurementsForCommitSet(this._testGroup, this._commitSetIndexRowIndexMap[1].commitSet);
         const result = this._testGroup.compareTestResults(this._analysisResultsView.metric(), startValues, endValues);
-        return {label: result.label, title: result.fullLabel, status: result.status};
+        return {label: result.label, title: result.fullLabelForMean, status: result.status};
     }
 }
index 4b2feac..4dea785 100644 (file)
@@ -57,7 +57,8 @@ class TestGroupResultsViewer extends ComponentBase {
                     element('th', {class: 'metric-direction'}, ''),
                     element('th', {colspan: 2}, 'Results'),
                     element('th', 'Averages'),
-                    element('th', 'Comparison'),
+                    element('th', 'Comparison by mean'),
+                    element('th', 'Comparison by individual iterations')
                 ]),
             ]),
             tests.map((test) => this._buildRowsForTest(testGroup, expandedTests, test, [], maxDepth, 0))]);
@@ -112,7 +113,7 @@ class TestGroupResultsViewer extends ComponentBase {
             const entry = valueMap.get(commitSet);
             const previousEntry = valueMap.get(previousCommitSet);
 
-            const comparison = entry && previousEntry ? testGroup.compareTestResults(metric, previousEntry.filteredValues, entry.filteredValues) : null;
+            const comparison = entry && previousEntry ? testGroup.compareTestResults(metric, previousEntry.filteredMeasurements, entry.filteredMeasurements) : null;
             const valueLabels = entry.measurements.map((measurement) => measurement ?  formatValue(measurement.value, measurement.interval) : '-');
 
             const barCell = element('td', {class: 'plot-bar'},
@@ -120,15 +121,17 @@ class TestGroupResultsViewer extends ComponentBase {
             barCell.expandedHeight = +valueLabels.length + 'rem';
             barCells.push(barCell);
 
-            const significance = comparison && comparison.isStatisticallySignificant ? 'significant' : 'negligible';
+            const significanceForMean = comparison && comparison.isStatisticallySignificantForMean ? 'significant' : 'negligible';
+            const significanceForIndividual = comparison && comparison.isStatisticallySignificantForIndividual ? 'significant' : 'negligible';
             const changeType = comparison ? comparison.changeType : null;
             return [
                 element('th', testGroup.labelForCommitSet(commitSet)),
                 barCell,
                 element('td', formatValue(entry.mean, entry.interval)),
-                element('td', {class: `comparison ${changeType} ${significance}`}, comparison ? comparison.fullLabel : ''),
+                element('td', {class: `comparison ${changeType} ${significanceForMean}`}, comparison ? comparison.fullLabelForMean : ''),
+                element('td', {class: `comparison ${changeType} ${significanceForIndividual}`}, comparison ? comparison.fullLabelForIndividual : ''),
             ];
-        }
+        };
 
         this._barGraphCellMap.set(metric, {barGroup, barCells});
 
@@ -166,10 +169,11 @@ class TestGroupResultsViewer extends ComponentBase {
         for (const commitSet of commitSets) {
             const requests = testGroup.requestsForCommitSet(commitSet);
             const measurements = requests.map((request) => resultsView.resultForRequest(request));
-            const filteredValues = measurements.filter((result) => !!result).map((measurement) => measurement.value);
+            const filteredMeasurements = measurements.filter((result) => !!result);
+            const filteredValues = filteredMeasurements.map((measurement) => measurement.value);
             const allValues = measurements.map((result) => result != null ? result.value : NaN);
             const interval = Statistics.confidenceInterval(filteredValues);
-            map.set(commitSet, {requests, measurements, filteredValues, allValues, mean: Statistics.mean(filteredValues), interval});
+            map.set(commitSet, {requests, measurements, filteredMeasurements, allValues, mean: Statistics.mean(filteredValues), interval});
         }
         return map;
     }
index fa41dda..4ac43b4 100644 (file)
@@ -134,25 +134,31 @@ class TestGroup extends LabeledObject {
         return this._buildRequests.some(function (request) { return request.isPending(); });
     }
 
-    compareTestResults(metric, beforeValues, afterValues)
+    compareTestResults(metric, beforeMeasurements, afterMeasurements)
     {
         console.assert(metric);
+        const beforeValues = beforeMeasurements.map((measurment) => measurment.value);
+        const afterValues = afterMeasurements.map((measurement) => measurement.value);
         const beforeMean = Statistics.sum(beforeValues) / beforeValues.length;
         const afterMean = Statistics.sum(afterValues) / afterValues.length;
 
-        var result = {changeType: null, status: 'failed', label: 'Failed', fullLabel: 'Failed', isStatisticallySignificant: false};
+        const result = {changeType: null, status: 'failed', label: 'Failed', fullLabelForMean: 'Failed',
+            isStatisticallySignificantForMean: false, fullLabelForIndividual: 'Failed', isStatisticallySignificantForIndividual: false,
+            probabilityRangeForMean: [null, null], probabilityRangeForIndividual: [null, null]};
 
-        var hasCompleted = this.hasFinished();
+        const hasCompleted = this.hasFinished();
         if (!hasCompleted) {
             if (this.hasStarted()) {
                 result.status = 'running';
                 result.label = 'Running';
-                result.fullLabel = 'Running';
+                result.fullLabelForMean = 'Running';
+                result.fullLabelForIndividual = 'Running';
             } else {
                 console.assert(result.changeType === null);
                 result.status = 'pending';
                 result.label = 'Pending';
-                result.fullLabel = 'Pending';
+                result.fullLabelForMean = 'Pending';
+                result.fullLabelForIndividual = 'Pending';
             }
         }
 
@@ -160,13 +166,25 @@ class TestGroup extends LabeledObject {
             const summary = metric.labelForDifference(beforeMean, afterMean, 'better', 'worse');
             result.changeType = summary.changeType;
             result.label = summary.changeLabel;
-            var isSignificant = Statistics.testWelchsT(beforeValues, afterValues);
-            var significanceLabel = isSignificant ? 'significant' : 'insignificant';
+
+
+            const constructSignificanceLabel = (probabilityRange) => !!probabilityRange.range[0] ? `significant with ${(probabilityRange.range[0] * 100).toFixed()}% probability` : 'insignificant';
+
+            const probabilityRangeForMean = Statistics.probabilityRangeForWelchsT(beforeValues, afterValues);
+            const significanceLabelForMean = constructSignificanceLabel(probabilityRangeForMean);
+            result.fullLabelForMean = `${result.label} (${significanceLabelForMean})`;
+            result.isStatisticallySignificantForMean = !!probabilityRangeForMean.range[0];
+            result.probabilityRangeForMean = probabilityRangeForMean.range;
+
+            const adaptMeasurementToSamples = (measurement) => ({sum: measurement.sum, squareSum: measurement.squareSum, sampleSize: measurement.iterationCount});
+            const probabilityRangeForIndividual = Statistics.probabilityRangeForWelchsTForMultipleSamples(beforeMeasurements.map(adaptMeasurementToSamples), afterMeasurements.map(adaptMeasurementToSamples));
+            const significanceLabelForIndividual = constructSignificanceLabel(probabilityRangeForIndividual);
+            result.fullLabelForIndividual = `${result.label} (${significanceLabelForIndividual})`;
+            result.isStatisticallySignificantForIndividual = !!probabilityRangeForIndividual.range[0];
+            result.probabilityRangeForIndividual = probabilityRangeForIndividual.range;
 
             if (hasCompleted)
-                result.status = isSignificant ? result.changeType : 'unchanged';
-            result.fullLabel = `${result.label} (statistically ${significanceLabel})`;
-            result.isStatisticallySignificant = isSignificant;
+                result.status = result.isStatisticallySignificantForMean ? result.changeType : 'unchanged';
         }
 
         return result;
index 285a3e2..9af21f1 100644 (file)
@@ -23,7 +23,7 @@ class TestGroupResultPage extends MarkupPage {
         return global.RemoteAPI.url(`/v3/#/analysis/task/${analysisTask.id()}`);
     }
 
-    _URLForAnalysisTask(testGroup, analysisResultsView)
+    _resultsForTestGroup(testGroup, analysisResultsView)
     {
         const resultsByCommitSet = new Map;
         let maxValue = -Infinity;
@@ -73,9 +73,24 @@ class TestGroupResultPage extends MarkupPage {
         };
 
         const analysisResultsView = analysisResults.viewForMetric(metric);
-        const {resultsByCommitSet, widthForValue} = this._URLForAnalysisTask(testGroup, analysisResultsView);
+        const {resultsByCommitSet, widthForValue} = this._resultsForTestGroup(testGroup, analysisResultsView);
 
         const tableBodies = [];
+
+        const beforeResults = resultsByCommitSet.get(requestedCommitSets[0]).filter((result) => !!result);
+        const afterResults = resultsByCommitSet.get(requestedCommitSets[1]).filter((result) => !!result);
+        const comparison = testGroup.compareTestResults(metric, beforeResults, afterResults);
+        const changeStyleClassForMean = `${comparison.isStatisticallySignificantForMean ? comparison.changeType : 'insignificant'}-result`;
+        const changeStyleClassForIndividual = `${comparison.isStatisticallySignificantForIndividual ? comparison.changeType : 'insignificant'}-result`;
+        const caption = this.createElement('caption', `${testGroup.test().name()} - ${metric.aggregatorLabel()}`);
+
+        tableBodies.push(this.createElement('tbody', {class: 'comparision-table-body'}, [
+            this.createElement('tr', [this.createElement('td', 'Comparision by Mean'),
+                this.createElement('td', this.createElement('em', {class: changeStyleClassForMean}, comparison.fullLabelForMean))]),
+            this.createElement('tr', [this.createElement('td', 'Comparision by Individual'),
+                this.createElement('td', this.createElement('em', {class: changeStyleClassForIndividual}, comparison.fullLabelForIndividual))])
+        ]));
+
         for (const commitSet of requestedCommitSets) {
             let firstRow = true;
             const tableRows = [];
@@ -93,13 +108,6 @@ class TestGroupResultPage extends MarkupPage {
             tableBodies.push(this.createElement('tbody', tableRows));
         }
 
-        const beforeValues = resultsByCommitSet.get(requestedCommitSets[0]).filter((result) => !!result).map((result) => result.value);
-        const afterValues = resultsByCommitSet.get(requestedCommitSets[1]).filter((result) => !!result).map((result) => result.value);
-        const comparison = testGroup.compareTestResults(metric, beforeValues, afterValues);
-        const changeStyleClass =  `${comparison.isStatisticallySignificant ? comparison.changeType : 'insignificant'}-result`;
-        const caption = this.createElement('caption', [`${testGroup.test().name()} - ${metric.aggregatorLabel()}: `,
-            this.createElement('em', {class: changeStyleClass}, comparison.fullLabel)]);
-
         return this.createElement('table', {class: 'result-table'}, [caption, tableBodies]);
     }
 
@@ -144,6 +152,7 @@ class TestGroupResultPage extends MarkupPage {
             'em': {
                 'font-weight': 'bold',
                 'font-style': 'normal',
+                'padding-right': '2rem',
             },
             'caption': {
                 'font-size': '1.3rem',
@@ -168,6 +177,9 @@ class TestGroupResultPage extends MarkupPage {
                 'text-align': 'center',
                 'border-collapse': 'collapse',
             },
+            '.comparision-table-body': {
+                'text-align': 'left',
+            },
             '.result-cell': {
                 'min-width': '20rem',
                 'position': 'relative',
index d910422..ad6f705 100644 (file)
@@ -105,25 +105,25 @@ describe('Statistics', function () {
             assert.almostEqual(delta(values, 0.95), 3.015, 3);
 
             // Following values are computed using Excel Online's STDEV and CONFIDENCE.T
-            assert.almostEqual(delta([1, 2, 3, 4], 0.8), 1.057159);
-            assert.almostEqual(delta([1, 2, 3, 4], 0.9), 1.519090);
-            assert.almostEqual(delta([1, 2, 3, 4], 0.95), 2.054260);
+            assert.almostEqual(delta([1, 2, 3, 4], 0.8), 1.057159, 4);
+            assert.almostEqual(delta([1, 2, 3, 4], 0.9), 1.519090, 4);
+            assert.almostEqual(delta([1, 2, 3, 4], 0.95), 2.054260, 4);
 
-            assert.almostEqual(delta([0.3, 0.06, 0.5], 0.8), 0.2398353);
-            assert.almostEqual(delta([0.3, 0.06, 0.5], 0.9), 0.3713985);
-            assert.almostEqual(delta([0.3, 0.06, 0.5], 0.95), 0.5472625);
+            assert.almostEqual(delta([0.3, 0.06, 0.5], 0.8), 0.2398353, 4);
+            assert.almostEqual(delta([0.3, 0.06, 0.5], 0.9), 0.3713985, 4);
+            assert.almostEqual(delta([0.3, 0.06, 0.5], 0.95), 0.5472625, 4);
 
-            assert.almostEqual(delta([-0.3, 0.06, 0.5], 0.8), 0.4361900);
-            assert.almostEqual(delta([-0.3, 0.06, 0.5], 0.9), 0.6754647);
-            assert.almostEqual(delta([-0.3, 0.06, 0.5], 0.95), 0.9953098);
+            assert.almostEqual(delta([-0.3, 0.06, 0.5], 0.8), 0.4361900, 4);
+            assert.almostEqual(delta([-0.3, 0.06, 0.5], 0.9), 0.6754647, 4);
+            assert.almostEqual(delta([-0.3, 0.06, 0.5], 0.95), 0.9953098, 4);
 
-            assert.almostEqual(delta([123, 107, 109, 104, 111], 0.8), 5.001167);
-            assert.almostEqual(delta([123, 107, 109, 104, 111], 0.9), 6.953874);
-            assert.almostEqual(delta([123, 107, 109, 104, 111], 0.95), 9.056490);
+            assert.almostEqual(delta([123, 107, 109, 104, 111], 0.8), 5.001167, 4);
+            assert.almostEqual(delta([123, 107, 109, 104, 111], 0.9), 6.953874, 4);
+            assert.almostEqual(delta([123, 107, 109, 104, 111], 0.95), 9.056490, 4);
 
-            assert.almostEqual(delta([6785, 7812, 6904, 7503, 6943, 7207, 6812], 0.8), 212.6155);
-            assert.almostEqual(delta([6785, 7812, 6904, 7503, 6943, 7207, 6812], 0.9), 286.9585);
-            assert.almostEqual(delta([6785, 7812, 6904, 7503, 6943, 7207, 6812], 0.95), 361.3469);
+            assert.almostEqual(delta([6785, 7812, 6904, 7503, 6943, 7207, 6812], 0.8), 212.6155, 4);
+            assert.almostEqual(delta([6785, 7812, 6904, 7503, 6943, 7207, 6812], 0.9), 286.9585, 4);
+            assert.almostEqual(delta([6785, 7812, 6904, 7503, 6943, 7207, 6812], 0.95), 361.3469, 4);
 
         });
     });
@@ -236,6 +236,127 @@ describe('Statistics', function () {
         });
     });
 
+    describe('minimumTForOneSidedProbability', () => {
+        it('should not infinite loop when lookup t-value for any degrees of freedom', () => {
+            for(const probability of [0.9, 0.95, 0.975, 0.99]) {
+                for (let degreesOfFreedom = 1; degreesOfFreedom < 100000; degreesOfFreedom += 1)
+                    Statistics.minimumTForOneSidedProbability(probability, degreesOfFreedom);
+            }
+        })
+    });
+
+    describe('probabilityRangeForWelchsTForMultipleSamples', () => {
+        function splitSample(samples) {
+            const mid = samples.length / 2;
+            return splitSampleByIndices(samples, mid);
+        }
+
+        function splitSampleByIndices(samples, ...indices) {
+            const sampleSize = samples.length;
+            const splittedSamples = [];
+            let previousIndex = 0;
+            for (const index of indices) {
+                if (index == previousIndex)
+                    continue;
+                console.assert(index > previousIndex);
+                console.assert(index <= sampleSize);
+                splittedSamples.push(samples.slice(previousIndex, index));
+                previousIndex = index;
+            }
+            if (previousIndex < sampleSize)
+                splittedSamples.push(samples.slice(previousIndex, sampleSize));
+            return splittedSamples.map((values) => ({sum: Statistics.sum(values), squareSum: Statistics.squareSum(values), sampleSize: values.length}));
+        }
+
+        it('should find the t-value of values using Welch\'s t-test', () => {
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSample(example1.A1), splitSample(example1.A2)).t, example1.expectedT, 2);
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSample(example2.A1), splitSample(example2.A2)).t, example2.expectedT, 2);
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSample(example3.A1), splitSample(example3.A2)).t, example3.expectedT, 2);
+
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSampleByIndices(example1.A1, 1), splitSampleByIndices(example1.A2, 1)).t, example1.expectedT, 2);
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSampleByIndices(example2.A1, 1), splitSampleByIndices(example2.A2, 1)).t, example2.expectedT, 2);
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSampleByIndices(example3.A1, 1), splitSampleByIndices(example3.A2, 1)).t, example3.expectedT, 2);
+
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSampleByIndices(example1.A1, 0), splitSampleByIndices(example1.A2, 0)).t, example1.expectedT, 2);
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSampleByIndices(example2.A1, 0), splitSampleByIndices(example2.A2, 0)).t, example2.expectedT, 2);
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSampleByIndices(example3.A1, 0), splitSampleByIndices(example3.A2, 0)).t, example3.expectedT, 2);
+
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSampleByIndices(example1.A1, 1, 4), splitSampleByIndices(example1.A2, 1, 4)).t, example1.expectedT, 2);
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSampleByIndices(example2.A1, 1, 4), splitSampleByIndices(example2.A2, 1, 4)).t, example2.expectedT, 2);
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSampleByIndices(example3.A1, 1, 4), splitSampleByIndices(example3.A2, 1, 4)).t, example3.expectedT, 2);
+        });
+
+        it('should find the degreees of freedom using Welch–Satterthwaite equation when split evenly', () => {
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSample(example1.A1), splitSample(example1.A2)).degreesOfFreedom,
+                example1.expectedDegreesOfFreedom, 2);
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSample(example2.A1), splitSample(example2.A2)).degreesOfFreedom,
+                example2.expectedDegreesOfFreedom, 2);
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSample(example3.A1), splitSample(example3.A2)).degreesOfFreedom,
+                example3.expectedDegreesOfFreedom, 2);
+
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSampleByIndices(example1.A1, 1), splitSampleByIndices(example1.A2, 1)).degreesOfFreedom,
+                example1.expectedDegreesOfFreedom, 2);
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSampleByIndices(example2.A1, 1), splitSampleByIndices(example2.A2, 1)).degreesOfFreedom,
+                example2.expectedDegreesOfFreedom, 2);
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSampleByIndices(example3.A1, 1), splitSampleByIndices(example3.A2, 1)).degreesOfFreedom,
+                example3.expectedDegreesOfFreedom, 2);
+
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSampleByIndices(example1.A1, 0), splitSampleByIndices(example1.A2, 0)).degreesOfFreedom,
+                example1.expectedDegreesOfFreedom, 2);
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSampleByIndices(example2.A1, 0), splitSampleByIndices(example2.A2, 0)).degreesOfFreedom,
+                example2.expectedDegreesOfFreedom, 2);
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSampleByIndices(example3.A1, 0), splitSampleByIndices(example3.A2, 0)).degreesOfFreedom,
+                example3.expectedDegreesOfFreedom, 2);
+
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSampleByIndices(example1.A1, 1, 4), splitSampleByIndices(example1.A2, 1, 4)).degreesOfFreedom,
+                example1.expectedDegreesOfFreedom, 2);
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSampleByIndices(example2.A1, 1, 4), splitSampleByIndices(example2.A2, 1, 4)).degreesOfFreedom,
+                example2.expectedDegreesOfFreedom, 2);
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSampleByIndices(example3.A1, 1, 4), splitSampleByIndices(example3.A2, 1, 4)).degreesOfFreedom,
+                example3.expectedDegreesOfFreedom, 2);
+        });
+
+        it('should compute the range of probabilites using the p-value of Welch\'s t-test when split evenly', function () {
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSample(example1.A1), splitSample(example1.A2)).range[0], example1.expectedRange[0]);
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSample(example1.A1), splitSample(example1.A2)).range[1], example1.expectedRange[1]);
+
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSample(example2.A1), splitSample(example2.A2)).range[0], example2.expectedRange[0]);
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSample(example2.A1), splitSample(example2.A2)).range[1], example2.expectedRange[1]);
+
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSample(example3.A1), splitSample(example3.A2)).range[0], example3.expectedRange[0]);
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSample(example3.A1), splitSample(example3.A2)).range[1], example3.expectedRange[1]);
+
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSampleByIndices(example1.A1, 1), splitSampleByIndices(example1.A2, 1)).range[0], example1.expectedRange[0]);
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSampleByIndices(example1.A1, 1), splitSampleByIndices(example1.A2, 1)).range[1], example1.expectedRange[1]);
+
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSampleByIndices(example2.A1, 1), splitSampleByIndices(example2.A2, 1)).range[0], example2.expectedRange[0]);
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSampleByIndices(example2.A1, 1), splitSampleByIndices(example2.A2, 1)).range[1], example2.expectedRange[1]);
+
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSampleByIndices(example3.A1, 1), splitSampleByIndices(example3.A2, 1)).range[0], example3.expectedRange[0]);
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSampleByIndices(example3.A1, 1), splitSampleByIndices(example3.A2, 1)).range[1], example3.expectedRange[1]);
+
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSampleByIndices(example1.A1, 0), splitSampleByIndices(example1.A2, 0)).range[0], example1.expectedRange[0]);
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSampleByIndices(example1.A1, 0), splitSampleByIndices(example1.A2, 0)).range[1], example1.expectedRange[1]);
+
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSampleByIndices(example2.A1, 0), splitSampleByIndices(example2.A2, 0)).range[0], example2.expectedRange[0]);
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSampleByIndices(example2.A1, 0), splitSampleByIndices(example2.A2, 0)).range[1], example2.expectedRange[1]);
+
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSampleByIndices(example3.A1, 0), splitSampleByIndices(example3.A2, 0)).range[0], example3.expectedRange[0]);
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSampleByIndices(example3.A1, 0), splitSampleByIndices(example3.A2, 0)).range[1], example3.expectedRange[1]);
+
+
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSampleByIndices(example1.A1, 1, 4), splitSampleByIndices(example1.A2, 1, 4)).range[0], example1.expectedRange[0]);
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSampleByIndices(example1.A1, 1, 4), splitSampleByIndices(example1.A2, 1, 4)).range[1], example1.expectedRange[1]);
+
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSampleByIndices(example2.A1, 1, 4), splitSampleByIndices(example2.A2, 1, 4)).range[0], example2.expectedRange[0]);
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSampleByIndices(example2.A1, 1, 4), splitSampleByIndices(example2.A2, 1, 4)).range[1], example2.expectedRange[1]);
+
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSampleByIndices(example3.A1, 1, 4), splitSampleByIndices(example3.A2, 1, 4)).range[0], example3.expectedRange[0]);
+            assert.almostEqual(Statistics.probabilityRangeForWelchsTForMultipleSamples(splitSampleByIndices(example3.A1, 1, 4), splitSampleByIndices(example3.A2, 1, 4)).range[1], example3.expectedRange[1]);
+
+        });
+    });
+
     describe('movingAverage', function () {
         it('should return the origian values when both forward and backward window size is 0', function () {
             assert.deepEqual(Statistics.movingAverage([1, 2, 3, 4, 5], 0, 0), [1, 2, 3, 4, 5]);