@antv/g2plot
Version:
An interactive and responsive charting library
200 lines • 6.43 kB
JavaScript
// from https://github.com/simple-statistics
Object.defineProperty(exports, "__esModule", { value: true });
exports.quantile = exports.quickselect = exports.swap = exports.quantileSorted = void 0;
/**
* This is the internal implementation of quantiles: when you know
* that the order is sorted, you don't need to re-sort it, and the computations
* are faster.
*
* @param {Array<number>} x sample of one or more data points
* @param {number} p desired quantile: a number between 0 to 1, inclusive
* @returns {number} quantile value
* @throws {Error} if p ix outside of the range from 0 to 1
* @throws {Error} if x is empty
* @example
* quantileSorted([3, 6, 7, 8, 8, 9, 10, 13, 15, 16, 20], 0.5); // => 9
*/
function quantileSorted(x, p) {
var idx = x.length * p;
if (x.length === 0) {
throw new Error('quantile requires at least one data point.');
}
else if (p < 0 || p > 1) {
throw new Error('quantiles must be between 0 and 1');
}
else if (p === 1) {
// If p is 1, directly return the last element
return x[x.length - 1];
}
else if (p === 0) {
// If p is 0, directly return the first element
return x[0];
}
else if (idx % 1 !== 0) {
// If p is not integer, return the next element in array
return x[Math.ceil(idx) - 1];
}
else if (x.length % 2 === 0) {
// If the list has even-length, we'll take the average of this number
// and the next value, if there is one
return (x[idx - 1] + x[idx]) / 2;
}
else {
// Finally, in the simple case of an integer value
// with an odd-length list, return the x value at the index.
return x[idx];
}
}
exports.quantileSorted = quantileSorted;
/**
* 交换数组位置
* @param arr T[]
* @param i number
* @param j number
*/
function swap(arr, i, j) {
var tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
exports.swap = swap;
/**
* Rearrange items in `arr` so that all items in `[left, k]` range are the smallest.
* The `k`-th element will have the `(k - left + 1)`-th smallest value in `[left, right]`.
*
* Implements Floyd-Rivest selection algorithm https://en.wikipedia.org/wiki/Floyd-Rivest_algorithm
*
* @param {Array<number>} arr input array
* @param {number} k pivot index
* @param {number} [left] left index
* @param {number} [right] right index
* @returns {void} mutates input array
* @example
* var arr = [65, 28, 59, 33, 21, 56, 22, 95, 50, 12, 90, 53, 28, 77, 39];
* quickselect(arr, 8);
* // = [39, 28, 28, 33, 21, 12, 22, 50, 53, 56, 59, 65, 90, 77, 95]
*/
function quickselect(arr, k, left, right) {
left = left || 0;
right = right || arr.length - 1;
while (right > left) {
// 600 and 0.5 are arbitrary constants chosen in the original paper to minimize execution time
if (right - left > 600) {
var n = right - left + 1;
var m = k - left + 1;
var z = Math.log(n);
var s = 0.5 * Math.exp((2 * z) / 3);
var sd = 0.5 * Math.sqrt((z * s * (n - s)) / n);
if (m - n / 2 < 0)
sd *= -1;
var newLeft = Math.max(left, Math.floor(k - (m * s) / n + sd));
var newRight = Math.min(right, Math.floor(k + ((n - m) * s) / n + sd));
quickselect(arr, k, newLeft, newRight);
}
var t = arr[k];
var i = left;
var j = right;
swap(arr, left, k);
if (arr[right] > t)
swap(arr, left, right);
while (i < j) {
swap(arr, i, j);
i++;
j--;
while (arr[i] < t)
i++;
while (arr[j] > t)
j--;
}
if (arr[left] === t)
swap(arr, left, j);
else {
j++;
swap(arr, j, right);
}
if (j <= k)
left = j + 1;
if (k <= j)
right = j - 1;
}
}
exports.quickselect = quickselect;
function quantile(x, p) {
var copy = x.slice();
if (Array.isArray(p)) {
// rearrange elements so that each element corresponding to a requested
// quantile is on a place it would be if the array was fully sorted
multiQuantileSelect(copy, p);
// Initialize the result array
var results = [];
// For each requested quantile
for (var i = 0; i < p.length; i++) {
results[i] = quantileSorted(copy, p[i]);
}
return results;
}
else {
var idx = quantileIndex(copy.length, p);
quantileSelect(copy, idx, 0, copy.length - 1);
return quantileSorted(copy, p);
}
}
exports.quantile = quantile;
function quantileSelect(arr, k, left, right) {
if (k % 1 === 0) {
quickselect(arr, k, left, right);
}
else {
k = Math.floor(k);
quickselect(arr, k, left, right);
quickselect(arr, k + 1, k + 1, right);
}
}
function multiQuantileSelect(arr, p) {
var indices = [0];
for (var i = 0; i < p.length; i++) {
indices.push(quantileIndex(arr.length, p[i]));
}
indices.push(arr.length - 1);
indices.sort(compare);
var stack = [0, indices.length - 1];
while (stack.length) {
var r = Math.ceil(stack.pop());
var l = Math.floor(stack.pop());
if (r - l <= 1)
continue;
var m = Math.floor((l + r) / 2);
quantileSelect(arr, indices[m], Math.floor(indices[l]), Math.ceil(indices[r]));
stack.push(l, m, m, r);
}
}
function compare(a, b) {
return a - b;
}
function quantileIndex(len, p) {
var idx = len * p;
if (p === 1) {
// If p is 1, directly return the last index
return len - 1;
}
else if (p === 0) {
// If p is 0, directly return the first index
return 0;
}
else if (idx % 1 !== 0) {
// If index is not integer, return the next index in array
return Math.ceil(idx) - 1;
}
else if (len % 2 === 0) {
// If the list has even-length, we'll return the middle of two indices
// around quantile to indicate that we need an average value of the two
return idx - 0.5;
}
else {
// Finally, in the simple case of an integer index
// with an odd-length list, return the index
return idx;
}
}
//# sourceMappingURL=quantile.js.map
;