-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
84 lines (73 loc) · 2.52 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
/* eslint-disable id-length */ // disable id-length because we're dealing with mathy things that are all one letter.
const isNumbery = isFinite;
const constants = require('./constants');
/**
* Returns the c4 correction factor aka the "unbiasing constant" for small sample standard deviations.
*
* @param {array}
* @return {number}
*/
function getC4(n) {
if (constants.c4[n]) return constants.c4[n];
return constants.c4.others;
}
/**
* Online mean, variance and standard deviation tracker.
*
* @param {number} values the list of values.
* @param {boolean} [sample] whether this is population or sample variance.
* @return {number}
*/
class Online {
constructor() {
this.instanceData = {
mean: 0,
deltaMean: 0,
deltaMean2: 0,
count: 0,
};
}
observe(record) {
function onlineAverage(meanSoFar, countSoFar, newRecord) {
return ((meanSoFar * countSoFar) + newRecord) / (countSoFar + 1);
}
if (isNumbery(record)) {
const number = Number(record);
this.instanceData.mean = onlineAverage(this.instanceData.mean, this.instanceData.count, number);
const delta = number - this.instanceData.mean;
this.instanceData.deltaMean = onlineAverage(this.instanceData.deltaMean, this.instanceData.count, delta);
this.instanceData.deltaMean2 = onlineAverage(this.instanceData.deltaMean2, this.instanceData.count, Math.pow(delta, 2));
this.instanceData.count++;
} else {
throw Error('Non-numeric record.');
}
}
count() { return this.instanceData.count; }
mean() { return this.instanceData.mean; }
variance(sample = false) {
if (this.instanceData.count < 2) return NaN;
const divisor = sample ? this.instanceData.count - 1 : this.instanceData.count;
return this.instanceData.deltaMean2 / divisor;
}
standardDeviation(sample = false) {
return Math.sqrt(this.variance(sample)) * getC4(this.instanceData.count);
}
toJSON() {
return this.instanceData;
}
fromJSON(input) {
const values = typeof input === 'string' ? JSON.parse(input) : input;
const fields = Object.keys(values);
if (!isNumbery(values.mean) || !isNumbery(values.deltaMean) || !isNumbery(values.deltaMean2) || !isNumbery(values.count)) {
throw Error('Invalid fromJSON call. Call your mother.');
}
if (fields.length !== 4) {
throw Error('Invalid fromJSON call. Remember to brush your teeth.');
}
this.instanceData = {};
fields.forEach((key) => {
this.instanceData[key] = values[key];
});
}
}
module.exports = Online;