quantile.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. "use strict";
  2. // from https://github.com/simple-statistics
  3. Object.defineProperty(exports, "__esModule", { value: true });
  4. exports.quantile = exports.quickselect = exports.swap = exports.quantileSorted = void 0;
  5. /**
  6. * This is the internal implementation of quantiles: when you know
  7. * that the order is sorted, you don't need to re-sort it, and the computations
  8. * are faster.
  9. *
  10. * @param {Array<number>} x sample of one or more data points
  11. * @param {number} p desired quantile: a number between 0 to 1, inclusive
  12. * @returns {number} quantile value
  13. * @throws {Error} if p ix outside of the range from 0 to 1
  14. * @throws {Error} if x is empty
  15. * @example
  16. * quantileSorted([3, 6, 7, 8, 8, 9, 10, 13, 15, 16, 20], 0.5); // => 9
  17. */
  18. function quantileSorted(x, p) {
  19. var idx = x.length * p;
  20. if (x.length === 0) {
  21. throw new Error('quantile requires at least one data point.');
  22. }
  23. else if (p < 0 || p > 1) {
  24. throw new Error('quantiles must be between 0 and 1');
  25. }
  26. else if (p === 1) {
  27. // If p is 1, directly return the last element
  28. return x[x.length - 1];
  29. }
  30. else if (p === 0) {
  31. // If p is 0, directly return the first element
  32. return x[0];
  33. }
  34. else if (idx % 1 !== 0) {
  35. // If p is not integer, return the next element in array
  36. return x[Math.ceil(idx) - 1];
  37. }
  38. else if (x.length % 2 === 0) {
  39. // If the list has even-length, we'll take the average of this number
  40. // and the next value, if there is one
  41. return (x[idx - 1] + x[idx]) / 2;
  42. }
  43. else {
  44. // Finally, in the simple case of an integer value
  45. // with an odd-length list, return the x value at the index.
  46. return x[idx];
  47. }
  48. }
  49. exports.quantileSorted = quantileSorted;
  50. /**
  51. * 交换数组位置
  52. * @param arr T[]
  53. * @param i number
  54. * @param j number
  55. */
  56. function swap(arr, i, j) {
  57. var tmp = arr[i];
  58. arr[i] = arr[j];
  59. arr[j] = tmp;
  60. }
  61. exports.swap = swap;
  62. /**
  63. * Rearrange items in `arr` so that all items in `[left, k]` range are the smallest.
  64. * The `k`-th element will have the `(k - left + 1)`-th smallest value in `[left, right]`.
  65. *
  66. * Implements Floyd-Rivest selection algorithm https://en.wikipedia.org/wiki/Floyd-Rivest_algorithm
  67. *
  68. * @param {Array<number>} arr input array
  69. * @param {number} k pivot index
  70. * @param {number} [left] left index
  71. * @param {number} [right] right index
  72. * @returns {void} mutates input array
  73. * @example
  74. * var arr = [65, 28, 59, 33, 21, 56, 22, 95, 50, 12, 90, 53, 28, 77, 39];
  75. * quickselect(arr, 8);
  76. * // = [39, 28, 28, 33, 21, 12, 22, 50, 53, 56, 59, 65, 90, 77, 95]
  77. */
  78. function quickselect(arr, k, left, right) {
  79. left = left || 0;
  80. right = right || arr.length - 1;
  81. while (right > left) {
  82. // 600 and 0.5 are arbitrary constants chosen in the original paper to minimize execution time
  83. if (right - left > 600) {
  84. var n = right - left + 1;
  85. var m = k - left + 1;
  86. var z = Math.log(n);
  87. var s = 0.5 * Math.exp((2 * z) / 3);
  88. var sd = 0.5 * Math.sqrt((z * s * (n - s)) / n);
  89. if (m - n / 2 < 0)
  90. sd *= -1;
  91. var newLeft = Math.max(left, Math.floor(k - (m * s) / n + sd));
  92. var newRight = Math.min(right, Math.floor(k + ((n - m) * s) / n + sd));
  93. quickselect(arr, k, newLeft, newRight);
  94. }
  95. var t = arr[k];
  96. var i = left;
  97. var j = right;
  98. swap(arr, left, k);
  99. if (arr[right] > t)
  100. swap(arr, left, right);
  101. while (i < j) {
  102. swap(arr, i, j);
  103. i++;
  104. j--;
  105. while (arr[i] < t)
  106. i++;
  107. while (arr[j] > t)
  108. j--;
  109. }
  110. if (arr[left] === t)
  111. swap(arr, left, j);
  112. else {
  113. j++;
  114. swap(arr, j, right);
  115. }
  116. if (j <= k)
  117. left = j + 1;
  118. if (k <= j)
  119. right = j - 1;
  120. }
  121. }
  122. exports.quickselect = quickselect;
  123. function quantile(x, p) {
  124. var copy = x.slice();
  125. if (Array.isArray(p)) {
  126. // rearrange elements so that each element corresponding to a requested
  127. // quantile is on a place it would be if the array was fully sorted
  128. multiQuantileSelect(copy, p);
  129. // Initialize the result array
  130. var results = [];
  131. // For each requested quantile
  132. for (var i = 0; i < p.length; i++) {
  133. results[i] = quantileSorted(copy, p[i]);
  134. }
  135. return results;
  136. }
  137. else {
  138. var idx = quantileIndex(copy.length, p);
  139. quantileSelect(copy, idx, 0, copy.length - 1);
  140. return quantileSorted(copy, p);
  141. }
  142. }
  143. exports.quantile = quantile;
  144. function quantileSelect(arr, k, left, right) {
  145. if (k % 1 === 0) {
  146. quickselect(arr, k, left, right);
  147. }
  148. else {
  149. k = Math.floor(k);
  150. quickselect(arr, k, left, right);
  151. quickselect(arr, k + 1, k + 1, right);
  152. }
  153. }
  154. function multiQuantileSelect(arr, p) {
  155. var indices = [0];
  156. for (var i = 0; i < p.length; i++) {
  157. indices.push(quantileIndex(arr.length, p[i]));
  158. }
  159. indices.push(arr.length - 1);
  160. indices.sort(compare);
  161. var stack = [0, indices.length - 1];
  162. while (stack.length) {
  163. var r = Math.ceil(stack.pop());
  164. var l = Math.floor(stack.pop());
  165. if (r - l <= 1)
  166. continue;
  167. var m = Math.floor((l + r) / 2);
  168. quantileSelect(arr, indices[m], Math.floor(indices[l]), Math.ceil(indices[r]));
  169. stack.push(l, m, m, r);
  170. }
  171. }
  172. function compare(a, b) {
  173. return a - b;
  174. }
  175. function quantileIndex(len, p) {
  176. var idx = len * p;
  177. if (p === 1) {
  178. // If p is 1, directly return the last index
  179. return len - 1;
  180. }
  181. else if (p === 0) {
  182. // If p is 0, directly return the first index
  183. return 0;
  184. }
  185. else if (idx % 1 !== 0) {
  186. // If index is not integer, return the next index in array
  187. return Math.ceil(idx) - 1;
  188. }
  189. else if (len % 2 === 0) {
  190. // If the list has even-length, we'll return the middle of two indices
  191. // around quantile to indicate that we need an average value of the two
  192. return idx - 0.5;
  193. }
  194. else {
  195. // Finally, in the simple case of an integer index
  196. // with an odd-length list, return the index
  197. return idx;
  198. }
  199. }
  200. //# sourceMappingURL=quantile.js.map