summary.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. 'use strict';
  2. function isArray(x) {
  3. return Array.isArray(x) || ArrayBuffer.isView(x) && !(x instanceof DataView);
  4. }
  5. function Summary(data, sorted) {
  6. if (!(this instanceof Summary)) return new Summary(data, sorted);
  7. if (!isArray(data)) {
  8. throw TypeError('data must be an array');
  9. }
  10. this._data = data;
  11. this._sorted = sorted ? data : null;
  12. this._length = data.length;
  13. this._cache_sum = null;
  14. this._cache_mode = null;
  15. this._cache_mean = null;
  16. this._cache_quartiles = {};
  17. this._cache_variance = null;
  18. this._cache_sd = null;
  19. this._cache_max = null;
  20. this._cache_min = null;
  21. }
  22. module.exports = Summary;
  23. //
  24. // Not all values are in lazy calculated since that wouldn't do any good
  25. //
  26. Summary.prototype.data = function() {
  27. return this._data;
  28. };
  29. Summary.prototype.sort = function() {
  30. if (this._sorted === null) {
  31. this._sorted = this._data.slice(0).sort(function (a, b) { return a - b; });
  32. }
  33. return this._sorted;
  34. };
  35. Summary.prototype.size = function () {
  36. return this._length;
  37. };
  38. //
  39. // Always lazy calculated functions
  40. //
  41. Summary.prototype.sum = function () {
  42. if (this._cache_sum === null) {
  43. // Numerically stable sum
  44. // https://en.m.wikipedia.org/wiki/Pairwise_summation
  45. const partials = [];
  46. for (let i = 0; i < this._length; i++) {
  47. partials.push(this._data[i]);
  48. for (let j = i; j % 2 == 1; j = j >> 1) {
  49. const p = partials.pop();
  50. const q = partials.pop();
  51. partials.push(p + q);
  52. }
  53. }
  54. let total = 0.0;
  55. for (let i = 0; i < partials.length; i++) {
  56. total += partials[i];
  57. }
  58. this._cache_sum = total;
  59. }
  60. return this._cache_sum;
  61. };
  62. Summary.prototype.mode = function () {
  63. if (this._cache_mode === null) {
  64. const data = this.sort();
  65. let modeValue = NaN;
  66. let modeCount = 0;
  67. let currValue = data[0];
  68. let currCount = 1;
  69. // Count the amount of repeat and update mode variables
  70. for (let i = 1; i < this._length; i++) {
  71. if (data[i] === currValue) {
  72. currCount += 1;
  73. } else {
  74. if (currCount >= modeCount) {
  75. modeCount = currCount;
  76. modeValue = currValue;
  77. }
  78. currValue = data[i];
  79. currCount = 1;
  80. }
  81. }
  82. // Check the last count
  83. if (currCount >= modeCount) {
  84. modeCount = currCount;
  85. modeValue = currValue;
  86. }
  87. this._cache_mode = modeValue;
  88. }
  89. return this._cache_mode;
  90. };
  91. Summary.prototype.mean = function () {
  92. if (this._cache_mean === null) {
  93. // Numerically stable mean algorithm
  94. let mean = 0;
  95. for (let i = 0; i < this._length; i++) {
  96. mean += (this._data[i] - mean) / (i+1);
  97. }
  98. this._cache_mean = mean;
  99. }
  100. return this._cache_mean;
  101. };
  102. Summary.prototype.quartile = function (prob) {
  103. if (!this._cache_quartiles.hasOwnProperty(prob)) {
  104. const data = this.sort();
  105. const product = prob * this.size();
  106. const ceil = Math.ceil(product);
  107. if (ceil === product) {
  108. if (ceil === 0) {
  109. this._cache_quartiles[prob] = data[0];
  110. } else if (ceil === data.length) {
  111. this._cache_quartiles[prob] = data[data.length - 1];
  112. } else {
  113. this._cache_quartiles[prob] = (data[ceil - 1] + data[ceil]) / 2;
  114. }
  115. } else {
  116. this._cache_quartiles[prob] = data[ceil - 1];
  117. }
  118. }
  119. return this._cache_quartiles[prob];
  120. };
  121. Summary.prototype.median = function () {
  122. return this.quartile(0.5);
  123. };
  124. Summary.prototype.variance = function () {
  125. if (this._cache_variance === null) {
  126. // Numerically stable variance algorithm
  127. const mean = this.mean();
  128. let biasedVariance = 0;
  129. for (let i = 0; i < this._length; i++) {
  130. const diff = this._data[i] - mean;
  131. biasedVariance += (diff * diff - biasedVariance) / (i+1);
  132. }
  133. // Debias the variance
  134. const debiasTerm = ((this._length) / (this._length - 1));
  135. this._cache_variance = biasedVariance * debiasTerm;
  136. }
  137. return this._cache_variance;
  138. };
  139. Summary.prototype.sd = function () {
  140. if (this._cache_sd === null) {
  141. this._cache_sd = Math.sqrt(this.variance());
  142. }
  143. return this._cache_sd;
  144. };
  145. Summary.prototype.max = function () {
  146. if (this._cache_max === null) {
  147. this._cache_max = this.sort()[this._length - 1];
  148. }
  149. return this._cache_max;
  150. };
  151. Summary.prototype.min = function () {
  152. if (this._cache_min === null) {
  153. this._cache_min = this.sort()[0];
  154. }
  155. return this._cache_min;
  156. };