Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

quantileIndex, medianIndex #159

Merged
merged 3 commits into from Jul 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Expand Up @@ -117,6 +117,10 @@ Returns the mean of the given *iterable* of numbers. If the iterable contains no

Returns the median of the given *iterable* of numbers using the [R-7 method](https://en.wikipedia.org/wiki/Quantile#Estimating_quantiles_from_a_sample). If the iterable contains no numbers, returns undefined. An optional *accessor* function may be specified, which is equivalent to calling Array.from before computing the median. This method ignores undefined and NaN values; this is useful for ignoring missing data.

<a name="medianIndex" href="#medianIndex">#</a> d3.<b>medianIndex</b>(<i>array</i>, <i>p</i>[, <i>accessor</i>]) [Source](https://github.com/d3/d3-array/blob/main/src/median.js "Source")

Similar to *median*, but returns the index of the element to the left of the median.

<a name="cumsum" href="#cumsum">#</a> d3.<b>cumsum</b>(<i>iterable</i>[, <i>accessor</i>]) · [Source](https://github.com/d3/d3-array/blob/main/src/cumsum.js), [Examples](https://observablehq.com/@d3/d3-cumsum)

Returns the cumulative sum of the given *iterable* of numbers, as a Float64Array of the same length. If the iterable contains no numbers, returns zeros. An optional *accessor* function may be specified, which is equivalent to calling Array.from before computing the cumulative sum. This method ignores undefined and NaN values; this is useful for ignoring missing data.
Expand All @@ -137,6 +141,10 @@ d3.quantile(a, 0.1); // 2

An optional *accessor* function may be specified, which is equivalent to calling *array*.map(*accessor*) before computing the quantile.

<a name="quantileIndex" href="#quantileIndex">#</a> d3.<b>quantileIndex</b>(<i>array</i>, <i>p</i>[, <i>accessor</i>]) [Source](https://github.com/d3/d3-array/blob/main/src/quantile.js "Source")

Similar to *quantile*, but returns the index to the left of *p*.

<a name="quantileSorted" href="#quantileSorted">#</a> d3.<b>quantileSorted</b>(<i>array</i>, <i>p</i>[, <i>accessor</i>]) · [Source](https://github.com/d3/d3-array/blob/main/src/quantile.js), [Examples](https://observablehq.com/@d3/d3-mean-d3-median-and-friends)

Similar to *quantile*, but expects the input to be a **sorted** *array* of values. In contrast with *quantile*, the accessor is only called on the elements needed to compute the quantile.
Expand Down
4 changes: 2 additions & 2 deletions src/index.js
Expand Up @@ -17,15 +17,15 @@ export {default as thresholdSturges} from "./threshold/sturges.js";
export {default as max} from "./max.js";
export {default as maxIndex} from "./maxIndex.js";
export {default as mean} from "./mean.js";
export {default as median} from "./median.js";
export {default as median, medianIndex} from "./median.js";
export {default as merge} from "./merge.js";
export {default as min} from "./min.js";
export {default as minIndex} from "./minIndex.js";
export {default as mode} from "./mode.js";
export {default as nice} from "./nice.js";
export {default as pairs} from "./pairs.js";
export {default as permute} from "./permute.js";
export {default as quantile, quantileSorted} from "./quantile.js";
export {default as quantile, quantileIndex, quantileSorted} from "./quantile.js";
export {default as quickselect} from "./quickselect.js";
export {default as range} from "./range.js";
export {default as rank} from "./rank.js";
Expand Down
6 changes: 5 additions & 1 deletion src/median.js
@@ -1,5 +1,9 @@
import quantile from "./quantile.js";
import quantile, {quantileIndex} from "./quantile.js";

export default function median(values, valueof) {
return quantile(values, 0.5, valueof);
}

export function medianIndex(values, valueof) {
return quantileIndex(values, 0.5, valueof);
}
16 changes: 16 additions & 0 deletions src/quantile.js
@@ -1,7 +1,11 @@
import max from "./max.js";
import maxIndex from "./maxIndex.js";
import min from "./min.js";
import minIndex from "./minIndex.js";
import quickselect from "./quickselect.js";
import number, {numbers} from "./number.js";
import {ascendingDefined} from "./sort.js";
import greatest from "./greatest.js";

export default function quantile(values, p, valueof) {
values = Float64Array.from(numbers(values, valueof));
Expand All @@ -27,3 +31,15 @@ export function quantileSorted(values, p, valueof = number) {
value1 = +valueof(values[i0 + 1], i0 + 1, values);
return value0 + (value1 - value0) * (i - i0);
}

export function quantileIndex(values, p, valueof) {
values = Float64Array.from(numbers(values, valueof));
if (!(n = values.length)) return;
if ((p = +p) <= 0 || n < 2) return minIndex(values);
if (p >= 1) return maxIndex(values);
var n,
i = Math.floor((n - 1) * p),
order = (i, j) => ascendingDefined(values[i], values[j]),
index = quickselect(Uint32Array.from(values, (_, i) => i), i, 0, n - 1, order);
return greatest(index.subarray(0, i + 1), i => values[i]);
}
1 change: 1 addition & 0 deletions src/quickselect.js
Expand Up @@ -36,6 +36,7 @@ export default function quickselect(array, k, left = 0, right = array.length - 1
if (j <= k) left = j + 1;
if (k <= j) right = j - 1;
}

return array;
}

Expand Down
12 changes: 11 additions & 1 deletion test/median-test.js
@@ -1,5 +1,5 @@
import assert from "assert";
import {median} from "../src/index.js";
import {median, medianIndex} from "../src/index.js";
import {OneTimeNumber} from "./OneTimeNumber.js";

it("median(array) returns the median value for numbers", () => {
Expand Down Expand Up @@ -98,6 +98,16 @@ it("median(array, f) uses the undefined context", () => {
assert.deepStrictEqual(results, [undefined, undefined]);
});

it("medianIndex(array) returns the index", () => {
assert.deepStrictEqual(medianIndex([1, 2]), 0);
assert.deepStrictEqual(medianIndex([1, 2, 3]), 1);
assert.deepStrictEqual(medianIndex([1, 3, 2]), 2);
assert.deepStrictEqual(medianIndex([2, 3, 1]), 0);
assert.deepStrictEqual(medianIndex([1]), 0);
assert.deepStrictEqual(medianIndex([]), undefined);
});


function box(value) {
return {value: value};
}
Expand Down
29 changes: 28 additions & 1 deletion test/quantile-test.js
@@ -1,5 +1,5 @@
import assert from "assert";
import {quantile, quantileSorted} from "../src/index.js";
import {quantile, quantileIndex, quantileSorted} from "../src/index.js";

it("quantileSorted(array, p) requires sorted numeric input, quantile doesn't", () => {
assert.strictEqual(quantileSorted([1, 2, 3, 4], 0), 1);
Expand Down Expand Up @@ -83,6 +83,33 @@ it("quantile(array, p, f) observes the specified accessor", () => {
assert.strictEqual(quantile([], 1, unbox), undefined);
});

it("quantileIndex(array, p) returns the index", () => {
assert.deepStrictEqual(quantileIndex([1, 2], 0.2), 0);
assert.deepStrictEqual(quantileIndex([1, 2, 3], 0.2), 0);
assert.deepStrictEqual(quantileIndex([1, 3, 2], 0.2), 0);
assert.deepStrictEqual(quantileIndex([2, 3, 1], 0.2), 2);
assert.deepStrictEqual(quantileIndex([1], 0.2), 0);
assert.deepStrictEqual(quantileIndex([], 0.2), undefined);
});

it("quantileIndex(array, 0) returns the minimum index", () => {
assert.deepStrictEqual(quantileIndex([1, 2], 0), 0);
assert.deepStrictEqual(quantileIndex([1, 2, 3], 0), 0);
assert.deepStrictEqual(quantileIndex([1, 3, 2], 0), 0);
assert.deepStrictEqual(quantileIndex([2, 3, 1], 0), 2);
assert.deepStrictEqual(quantileIndex([1], 0), 0);
assert.deepStrictEqual(quantileIndex([], 0), undefined);
});

it("quantileIndex(array, 1) returns the maxium index", () => {
assert.deepStrictEqual(quantileIndex([1, 2], 1), 1);
assert.deepStrictEqual(quantileIndex([1, 2, 3], 1), 2);
assert.deepStrictEqual(quantileIndex([1, 3, 2], 1), 1);
assert.deepStrictEqual(quantileIndex([2, 3, 1], 1), 1);
assert.deepStrictEqual(quantileIndex([1], 1), 0);
assert.deepStrictEqual(quantileIndex([], 1), undefined);
});

function box(value) {
return {value: value};
}
Expand Down