perfshield 0.0.9 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/lib/stats.js +98 -16
  2. package/package.json +1 -1
package/lib/stats.js CHANGED
@@ -7,6 +7,102 @@ export const relativeMarginOfError = stats => {
7
7
  return Math.abs(margin / stats.mean);
8
8
  };
9
9
  const sumOf = values => values.reduce((total, value) => total + value, 0);
10
+ const sortNumbers = values => [...values].sort((a, b) => a - b);
11
+ const medianOfSorted = values => {
12
+ if (values.length === 0) {
13
+ throw new Error("Cannot compute median of an empty sample set.");
14
+ }
15
+ const mid = Math.floor(values.length / 2);
16
+ if (values.length % 2 === 1) {
17
+ return values[mid];
18
+ }
19
+ return (values[mid - 1] + values[mid]) / 2;
20
+ };
21
+ const walshAverages = values => {
22
+ const averages = [];
23
+ for (let i = 0; i < values.length; i += 1) {
24
+ const base = values[i];
25
+ for (let j = i; j < values.length; j += 1) {
26
+ averages.push((base + values[j]) / 2);
27
+ }
28
+ }
29
+ return averages;
30
+ };
31
+ const normalQuantile = prob => {
32
+ if (prob <= 0 || prob >= 1) {
33
+ throw new Error("Probability must be between 0 and 1.");
34
+ }
35
+ const a1 = Number.parseFloat("-39.69683028665376");
36
+ const a2 = Number.parseFloat("220.9460984245205");
37
+ const a3 = Number.parseFloat("-275.9285104469687");
38
+ const a4 = Number.parseFloat("138.357751867269");
39
+ const a5 = Number.parseFloat("-30.66479806614716");
40
+ const a6 = Number.parseFloat("2.506628277459239");
41
+ const b1 = Number.parseFloat("-54.47609879822406");
42
+ const b2 = Number.parseFloat("161.5858368580409");
43
+ const b3 = Number.parseFloat("-155.6989798598866");
44
+ const b4 = Number.parseFloat("66.80131188771972");
45
+ const b5 = Number.parseFloat("-13.28068155288572");
46
+ const c1 = Number.parseFloat("-0.007784894002430293");
47
+ const c2 = Number.parseFloat("-0.3223964580411365");
48
+ const c3 = Number.parseFloat("-2.400758277161838");
49
+ const c4 = Number.parseFloat("-2.549732539343734");
50
+ const c5 = Number.parseFloat("4.374664141464968");
51
+ const c6 = Number.parseFloat("2.938163982698783");
52
+ const d1 = Number.parseFloat("0.007784695709041462");
53
+ const d2 = Number.parseFloat("0.3224671290700398");
54
+ const d3 = Number.parseFloat("2.445134137142996");
55
+ const d4 = Number.parseFloat("3.754408661907416");
56
+ const plow = Number.parseFloat("0.02425");
57
+ const phigh = 1 - plow;
58
+ if (prob < plow) {
59
+ const q = Math.sqrt(-2 * Math.log(prob));
60
+ return (((((c1 * q + c2) * q + c3) * q + c4) * q + c5) * q + c6) / ((((d1 * q + d2) * q + d3) * q + d4) * q + 1);
61
+ }
62
+ if (prob > phigh) {
63
+ const q = Math.sqrt(-2 * Math.log(1 - prob));
64
+ return -(((((c1 * q + c2) * q + c3) * q + c4) * q + c5) * q + c6) / ((((d1 * q + d2) * q + d3) * q + d4) * q + 1);
65
+ }
66
+ const q = prob - 0.5;
67
+ const r = q * q;
68
+ return (((((a1 * r + a2) * r + a3) * r + a4) * r + a5) * r + a6) * q / (((((b1 * r + b2) * r + b3) * r + b4) * r + b5) * r + 1);
69
+ };
70
+ const hodgesLehmannConfidenceInterval = (sortedWalsh, size) => {
71
+ if (sortedWalsh.length === 0) {
72
+ throw new Error("Cannot compute confidence interval for empty samples.");
73
+ }
74
+ if (size <= 1) {
75
+ const estimate = medianOfSorted(sortedWalsh);
76
+ return {
77
+ high: estimate,
78
+ low: estimate
79
+ };
80
+ }
81
+ const walshCount = sortedWalsh.length;
82
+ const alpha = 0.05;
83
+ const meanRank = size * (size + 1) / 4;
84
+ const varianceRank = size * (size + 1) * (2 * size + 1) / 24;
85
+ const stdDevRank = Math.sqrt(varianceRank);
86
+ const z = normalQuantile(1 - alpha / 2);
87
+ const critical = Math.floor(meanRank - z * stdDevRank);
88
+ const lowIndex = Math.max(0, Math.min(walshCount - 1, critical));
89
+ const highIndex = Math.max(lowIndex, Math.min(walshCount - 1, walshCount - critical - 1));
90
+ return {
91
+ high: sortedWalsh[highIndex],
92
+ low: sortedWalsh[lowIndex]
93
+ };
94
+ };
95
+ const hodgesLehmannStats = values => {
96
+ if (values.length === 0) {
97
+ throw new Error("Cannot compute stats for an empty sample set.");
98
+ }
99
+ const walsh = walshAverages(values);
100
+ const sortedWalsh = sortNumbers(walsh);
101
+ return {
102
+ ci: hodgesLehmannConfidenceInterval(sortedWalsh, values.length),
103
+ mean: medianOfSorted(sortedWalsh)
104
+ };
105
+ };
10
106
  const squareResiduals = (values, mean) => values.map(value => {
11
107
  const diff = value - mean;
12
108
  return diff * diff;
@@ -99,14 +195,7 @@ const computePairedRelativeStats = (baselineSamples, currentSamples) => {
99
195
  }
100
196
  diffs.push((currentSamples[index] - baseline) / baseline);
101
197
  }
102
- const diffStats = summaryStats(diffs);
103
- return {
104
- ci: confidenceInterval95(samplingDistributionOfTheMean({
105
- mean: diffStats.mean,
106
- variance: diffStats.variance
107
- }, diffStats.size), diffStats.size),
108
- mean: diffStats.mean
109
- };
198
+ return hodgesLehmannStats(diffs);
110
199
  };
111
200
  const computePairedAbsoluteStats = (baselineSamples, currentSamples) => {
112
201
  const size = Math.min(baselineSamples.length, currentSamples.length);
@@ -117,14 +206,7 @@ const computePairedAbsoluteStats = (baselineSamples, currentSamples) => {
117
206
  for (let index = 0; index < size; index += 1) {
118
207
  diffs.push(currentSamples[index] - baselineSamples[index]);
119
208
  }
120
- const diffStats = summaryStats(diffs);
121
- return {
122
- ci: confidenceInterval95(samplingDistributionOfTheMean({
123
- mean: diffStats.mean,
124
- variance: diffStats.variance
125
- }, diffStats.size), diffStats.size),
126
- mean: diffStats.mean
127
- };
209
+ return hodgesLehmannStats(diffs);
128
210
  };
129
211
  export const computeRelativeDifferenceFromSamples = (baselineSamples, currentSamples) => ({
130
212
  absolute: computePairedAbsoluteStats(baselineSamples, currentSamples),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "perfshield",
3
- "version": "0.0.9",
3
+ "version": "0.0.10",
4
4
  "description": "A tool for doing web benchmarking across multiple JS engines and with statistical signifigance",
5
5
  "license": "MIT",
6
6
  "type": "module",