'use strict'; function isArray(x) { return Array.isArray(x) || ArrayBuffer.isView(x) && !(x instanceof DataView); } function Summary(data, sorted) { if (!(this instanceof Summary)) return new Summary(data, sorted); if (!isArray(data)) { throw TypeError('data must be an array'); } this._data = data; this._sorted = sorted ? data : null; this._length = data.length; this._cache_sum = null; this._cache_mode = null; this._cache_mean = null; this._cache_quartiles = {}; this._cache_variance = null; this._cache_sd = null; this._cache_max = null; this._cache_min = null; } module.exports = Summary; // // Not all values are in lazy calculated since that wouldn't do any good // Summary.prototype.data = function() { return this._data; }; Summary.prototype.sort = function() { if (this._sorted === null) { this._sorted = this._data.slice(0).sort(function (a, b) { return a - b; }); } return this._sorted; }; Summary.prototype.size = function () { return this._length; }; // // Always lazy calculated functions // Summary.prototype.sum = function () { if (this._cache_sum === null) { // Numerically stable sum // https://en.m.wikipedia.org/wiki/Pairwise_summation const partials = []; for (let i = 0; i < this._length; i++) { partials.push(this._data[i]); for (let j = i; j % 2 == 1; j = j >> 1) { const p = partials.pop(); const q = partials.pop(); partials.push(p + q); } } let total = 0.0; for (let i = 0; i < partials.length; i++) { total += partials[i]; } this._cache_sum = total; } return this._cache_sum; }; Summary.prototype.mode = function () { if (this._cache_mode === null) { const data = this.sort(); let modeValue = NaN; let modeCount = 0; let currValue = data[0]; let currCount = 1; // Count the amount of repeat and update mode variables for (let i = 1; i < this._length; i++) { if (data[i] === currValue) { currCount += 1; } else { if (currCount >= modeCount) { modeCount = currCount; modeValue = currValue; } currValue = data[i]; currCount = 1; } } // Check the last count if (currCount >= modeCount) { modeCount = currCount; modeValue = currValue; } this._cache_mode = modeValue; } return this._cache_mode; }; Summary.prototype.mean = function () { if (this._cache_mean === null) { // Numerically stable mean algorithm let mean = 0; for (let i = 0; i < this._length; i++) { mean += (this._data[i] - mean) / (i+1); } this._cache_mean = mean; } return this._cache_mean; }; Summary.prototype.quartile = function (prob) { if (!this._cache_quartiles.hasOwnProperty(prob)) { const data = this.sort(); const product = prob * this.size(); const ceil = Math.ceil(product); if (ceil === product) { if (ceil === 0) { this._cache_quartiles[prob] = data[0]; } else if (ceil === data.length) { this._cache_quartiles[prob] = data[data.length - 1]; } else { this._cache_quartiles[prob] = (data[ceil - 1] + data[ceil]) / 2; } } else { this._cache_quartiles[prob] = data[ceil - 1]; } } return this._cache_quartiles[prob]; }; Summary.prototype.median = function () { return this.quartile(0.5); }; Summary.prototype.variance = function () { if (this._cache_variance === null) { // Numerically stable variance algorithm const mean = this.mean(); let biasedVariance = 0; for (let i = 0; i < this._length; i++) { const diff = this._data[i] - mean; biasedVariance += (diff * diff - biasedVariance) / (i+1); } // Debias the variance const debiasTerm = ((this._length) / (this._length - 1)); this._cache_variance = biasedVariance * debiasTerm; } return this._cache_variance; }; Summary.prototype.sd = function () { if (this._cache_sd === null) { this._cache_sd = Math.sqrt(this.variance()); } return this._cache_sd; }; Summary.prototype.max = function () { if (this._cache_max === null) { this._cache_max = this.sort()[this._length - 1]; } return this._cache_max; }; Summary.prototype.min = function () { if (this._cache_min === null) { this._cache_min = this.sort()[0]; } return this._cache_min; };