nano-benchmark 1.0.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/LICENSE +29 -0
- package/README.md +99 -0
- package/bin/nano-bench.js +327 -0
- package/bin/nano-watch.js +200 -0
- package/package.json +52 -0
- package/src/bench/compare.js +35 -0
- package/src/bench/runner.js +173 -0
- package/src/median.js +39 -0
- package/src/significance/kstest.js +43 -0
- package/src/significance/kwtest.js +81 -0
- package/src/significance/mwtest.js +35 -0
- package/src/stats/beta-ppf.js +13 -0
- package/src/stats/beta.js +26 -0
- package/src/stats/chi-squared-ppf.js +10 -0
- package/src/stats/erf.js +23 -0
- package/src/stats/gamma.js +14 -0
- package/src/stats/normal-ppf.js +19 -0
- package/src/stats/normal.js +25 -0
- package/src/stats/ppf.js +18 -0
- package/src/stats/rank.js +41 -0
- package/src/stats/z-ppf.js +17 -0
- package/src/stats/z.js +7 -0
- package/src/stats/zeta.js +14 -0
- package/src/stats.js +116 -0
- package/src/stream-median.js +93 -0
- package/src/stream-stats.js +67 -0
- package/src/utils/bsearch.js +9 -0
- package/src/utils/rk.js +36 -0
package/src/stats.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
export const mean = data => {
|
|
2
|
+
let m = 0;
|
|
3
|
+
const size = data.length;
|
|
4
|
+
for (let i = 0; i < size; ++i) {
|
|
5
|
+
m += data[i] / size;
|
|
6
|
+
}
|
|
7
|
+
return m;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const variance = (data, meanValue = mean(data)) => {
|
|
11
|
+
let s = 0;
|
|
12
|
+
const size = data.length;
|
|
13
|
+
for (let i = 0; i < size; ++i) {
|
|
14
|
+
const diff = data[i] - meanValue;
|
|
15
|
+
s += (diff * diff) / size;
|
|
16
|
+
}
|
|
17
|
+
return s;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const stdDev = (data, meanValue = mean(data)) => Math.sqrt(variance(data, meanValue));
|
|
21
|
+
|
|
22
|
+
export const zScore = (x, mean, stdDev) => (x - mean) / stdDev;
|
|
23
|
+
|
|
24
|
+
export const makeZScoreFn = data => {
|
|
25
|
+
const m = mean(data),
|
|
26
|
+
s = stdDev(data, m);
|
|
27
|
+
return x => (x - m) / s;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const skewness = (data, meanValue = mean(data), stdDevValue = stdDev(data, meanValue)) => {
|
|
31
|
+
let s = 0;
|
|
32
|
+
const size = data.length;
|
|
33
|
+
for (let i = 0; i < size; ++i) {
|
|
34
|
+
const z = (data[i] - meanValue) / stdDevValue;
|
|
35
|
+
s += (z * z * z) / size;
|
|
36
|
+
}
|
|
37
|
+
return s;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const adjustedSkewness = (
|
|
41
|
+
data,
|
|
42
|
+
meanValue = mean(data),
|
|
43
|
+
stdDevValue = stdDev(data, meanValue)
|
|
44
|
+
) => {
|
|
45
|
+
const size = data.length;
|
|
46
|
+
return (Math.sqrt(size * (size - 1)) / (size - 2)) * skewness(data, meanValue, stdDevValue);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const kurtosis = (data, meanValue = mean(data), stdDevValue = stdDev(data, meanValue)) => {
|
|
50
|
+
let s = 0;
|
|
51
|
+
const size = data.length;
|
|
52
|
+
for (let i = 0; i < size; ++i) {
|
|
53
|
+
const z = (data[i] - meanValue) / stdDevValue,
|
|
54
|
+
z2 = z * z;
|
|
55
|
+
s += (z2 * z2) / size;
|
|
56
|
+
}
|
|
57
|
+
return s;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const excessKurtosis = (
|
|
61
|
+
data,
|
|
62
|
+
meanValue = mean(data),
|
|
63
|
+
stdDevValue = stdDev(data, meanValue)
|
|
64
|
+
) => kurtosis(data, meanValue, stdDevValue) - 3;
|
|
65
|
+
|
|
66
|
+
export const getPercentile = (sortedArray, value) => {
|
|
67
|
+
// getting percentile (index) by value
|
|
68
|
+
let lowerIndex = 0,
|
|
69
|
+
upperIndex = sortedArray.length - 1;
|
|
70
|
+
while (lowerIndex < upperIndex) {
|
|
71
|
+
let middleIndex = (lowerIndex + upperIndex) >> 1;
|
|
72
|
+
if (sortedArray[middleIndex] < value) {
|
|
73
|
+
lowerIndex = middleIndex + 1;
|
|
74
|
+
} else {
|
|
75
|
+
upperIndex = middleIndex;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return lowerIndex < sortedArray.length && value < sortedArray[lowerIndex]
|
|
79
|
+
? lowerIndex
|
|
80
|
+
: lowerIndex + 1;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export const getWeightedValue = (sortedArray, weight = 0.5) => {
|
|
84
|
+
// getting weighted data from a sorted array
|
|
85
|
+
let pos = weight * (sortedArray.length - 1),
|
|
86
|
+
upperIndex = Math.ceil(pos),
|
|
87
|
+
lowerIndex = upperIndex - 1;
|
|
88
|
+
if (lowerIndex <= 0) {
|
|
89
|
+
// return first element
|
|
90
|
+
return sortedArray[0];
|
|
91
|
+
}
|
|
92
|
+
if (upperIndex >= sortedArray.length) {
|
|
93
|
+
// return last element
|
|
94
|
+
return sortedArray[sortedArray.length - 1];
|
|
95
|
+
}
|
|
96
|
+
// linear approximation
|
|
97
|
+
return (
|
|
98
|
+
sortedArray[lowerIndex] * (upperIndex - pos) + sortedArray[upperIndex] * (pos - lowerIndex)
|
|
99
|
+
);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export const bootstrap = (fn, data, n = 1000) => {
|
|
103
|
+
const size = data.length,
|
|
104
|
+
samples = new Array(data.length),
|
|
105
|
+
results = new Array(n);
|
|
106
|
+
|
|
107
|
+
for (let i = 0; i < n; ++i) {
|
|
108
|
+
// resample
|
|
109
|
+
for (let j = 0; j < size; ++j) {
|
|
110
|
+
samples[j] = data[Math.floor(Math.random() * size)];
|
|
111
|
+
}
|
|
112
|
+
results[i] = fn(samples);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return results;
|
|
116
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
export class MedianCounter {
|
|
2
|
+
constructor(limit = 10) {
|
|
3
|
+
this.limit = limit;
|
|
4
|
+
this.array = [];
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
add(value) {
|
|
8
|
+
for (let i = 0; i < this.array.length; ++i) {
|
|
9
|
+
const counter = this.array[i];
|
|
10
|
+
|
|
11
|
+
if (counter.length < 3) {
|
|
12
|
+
counter.push(value);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
this.array[i] = [value];
|
|
17
|
+
|
|
18
|
+
const a = counter[0],
|
|
19
|
+
b = counter[1],
|
|
20
|
+
c = counter[2];
|
|
21
|
+
if (a < b) {
|
|
22
|
+
value = b < c ? b : a < c ? c : a;
|
|
23
|
+
} else {
|
|
24
|
+
value = a < c ? a : b < c ? c : b;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (this.array.length < this.limit) {
|
|
29
|
+
this.array.push([value]);
|
|
30
|
+
} else {
|
|
31
|
+
this.add(value);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get() {
|
|
36
|
+
const copy = this.clone();
|
|
37
|
+
|
|
38
|
+
let value;
|
|
39
|
+
|
|
40
|
+
while (copy.array.length) {
|
|
41
|
+
const counter = copy.array.shift();
|
|
42
|
+
|
|
43
|
+
switch (counter.length) {
|
|
44
|
+
case 1:
|
|
45
|
+
value = counter[0];
|
|
46
|
+
break;
|
|
47
|
+
case 2:
|
|
48
|
+
{
|
|
49
|
+
const a = counter[0],
|
|
50
|
+
b = counter[1];
|
|
51
|
+
value = a < b ? a : b;
|
|
52
|
+
}
|
|
53
|
+
break;
|
|
54
|
+
case 3:
|
|
55
|
+
{
|
|
56
|
+
const a = counter[0],
|
|
57
|
+
b = counter[1],
|
|
58
|
+
c = counter[2];
|
|
59
|
+
if (a < b) {
|
|
60
|
+
value = b < c ? b : a < c ? c : a;
|
|
61
|
+
} else {
|
|
62
|
+
value = a < c ? a : b < c ? c : b;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!copy.array.length) break;
|
|
69
|
+
|
|
70
|
+
copy.add(value);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return value;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
clone() {
|
|
77
|
+
const newMedian = new MedianCounter();
|
|
78
|
+
for (const counter of this.array) {
|
|
79
|
+
newMedian.array.push(counter.slice());
|
|
80
|
+
}
|
|
81
|
+
return newMedian;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export const streamMedian = iterable => {
|
|
86
|
+
const medianCounter = new MedianCounter();
|
|
87
|
+
for (const value of iterable) {
|
|
88
|
+
medianCounter.add(value);
|
|
89
|
+
}
|
|
90
|
+
return medianCounter.get();
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export default streamMedian;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// based on https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance
|
|
2
|
+
|
|
3
|
+
export class StatCounter {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.count = this.mean = this.M2 = this.M3 = this.M4 = 0;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
add(value) {
|
|
9
|
+
const n = ++this.count,
|
|
10
|
+
delta = value - this.mean,
|
|
11
|
+
term1 = delta / n,
|
|
12
|
+
term2 = delta * term1 * (n - 1),
|
|
13
|
+
term3 = term1 * term1;
|
|
14
|
+
|
|
15
|
+
this.mean += delta / n;
|
|
16
|
+
this.M4 += term3 * term2 * ((n - 3) * n + 3) + 6 * term3 * this.M2 - 4 * term1 * this.M3;
|
|
17
|
+
this.M3 += term1 * term2 * (n - 2) - 3 * term1 * this.M2;
|
|
18
|
+
this.M2 += term2;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
get variance() {
|
|
22
|
+
return this.M2 / this.count;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
get sampleVariance() {
|
|
26
|
+
return this.M2 / (this.count - 1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get skewness() {
|
|
30
|
+
return (Math.sqrt(this.count) * this.M3) / Math.pow(this.M2, 1.5);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get kurtosis() {
|
|
34
|
+
return (this.count * this.M4) / (this.M2 * this.M2) - 3;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
clone() {
|
|
38
|
+
const newStatCounter = new StatCounter();
|
|
39
|
+
|
|
40
|
+
newStatCounter.count = this.count;
|
|
41
|
+
newStatCounter.mean = this.mean;
|
|
42
|
+
newStatCounter.M2 = this.M2;
|
|
43
|
+
newStatCounter.M3 = this.M3;
|
|
44
|
+
newStatCounter.M4 = this.M4;
|
|
45
|
+
|
|
46
|
+
return newStatCounter;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const streamStats = iterable => {
|
|
51
|
+
const statCounter = new StatCounter();
|
|
52
|
+
|
|
53
|
+
for (const value of iterable) {
|
|
54
|
+
statCounter.add(value);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
count: statCounter.count,
|
|
59
|
+
mean: statCounter.mean,
|
|
60
|
+
variance: statCounter.variance,
|
|
61
|
+
sampleVariance: statCounter.sampleVariance,
|
|
62
|
+
skewness: statCounter.skewness,
|
|
63
|
+
kurtosis: statCounter.kurtosis
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export default streamStats;
|
package/src/utils/rk.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const ONE_THIRD = 1 / 3;
|
|
2
|
+
|
|
3
|
+
export const rk23 = (fn, {a = 0, b = 1, tolerance = 1e-6, initialValue = 0} = {}) => {
|
|
4
|
+
const ts = [a],
|
|
5
|
+
us = [initialValue];
|
|
6
|
+
let t = ts[0],
|
|
7
|
+
u = us[0],
|
|
8
|
+
h = Math.min(0.5 * Math.pow(tolerance, ONE_THIRD), b - t),
|
|
9
|
+
s1 = fn(a, initialValue);
|
|
10
|
+
|
|
11
|
+
while (t < b) {
|
|
12
|
+
if (t + h == t) {
|
|
13
|
+
// underflow
|
|
14
|
+
console.warn('Warning: step is too small near:', t);
|
|
15
|
+
break;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let s2 = fn(t + h / 2, u + (h / 2) * s1),
|
|
19
|
+
s3 = fn(t + 0.75 * h, u + 0.75 * h * s2),
|
|
20
|
+
uNew = u + (h * (2 * s1 + 3 * s2 + 4 * s3)) / 9, // 2nd order solution
|
|
21
|
+
s4 = fn(t + h, uNew),
|
|
22
|
+
error = Math.abs(h * ((-5 / 72) * s1 + s2 / 12 + s3 / 9 - s4 / 8)), // 2nd/3rd diff error estimate
|
|
23
|
+
maxError = tolerance * (1 + Math.abs(u)); // relative/absolute blend
|
|
24
|
+
|
|
25
|
+
if (error < maxError) {
|
|
26
|
+
ts.push((t += h));
|
|
27
|
+
us.push(u = uNew);
|
|
28
|
+
s1 = s4;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const q = Math.min(4, 0.8 * Math.pow(maxError / error, ONE_THIRD));
|
|
32
|
+
h = Math.min(q * h, b - t);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return {ts, us, finalValue: us[us.length - 1]};
|
|
36
|
+
};
|