Skip to content

Commit f8d7148

Browse files
author
Annie Zhang
authored
Update __table sorting to handle missing values (#338)
* Update __table sorting to handle missing values * always move null values to the end
1 parent bcd86dc commit f8d7148

File tree

2 files changed

+43
-6
lines changed

2 files changed

+43
-6
lines changed

src/table.js

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {ascending, descending, reverse} from "d3-array";
1+
import {reverse} from "d3-array";
22
import {FileAttachment} from "./fileAttachment.js";
33
import {isArqueroTable} from "./arquero.js";
44
import {isArrowTable, loadArrow} from "./arrow.js";
@@ -488,6 +488,24 @@ function likeOperand(operand) {
488488
return {...operand, value: `%${operand.value}%`};
489489
}
490490

491+
// Comparator function that moves null values (undefined, null, NaN) to the
492+
// end of the array.
493+
function defined(a, b) {
494+
return (a == null || !(a >= a)) - (b == null || !(b >= b));
495+
}
496+
497+
// Comparator function that sorts values in ascending order, with null values at
498+
// the end.
499+
function ascendingDefined(a, b) {
500+
return defined(a, b) || (a < b ? -1 : a > b ? 1 : 0);
501+
}
502+
503+
// Comparator function that sorts values in descending order, with null values
504+
// at the end.
505+
function descendingDefined(a, b) {
506+
return defined(a, b) || (a > b ? -1 : a < b ? 1 : 0);
507+
}
508+
491509
// Functions for checking type validity
492510
const isValidNumber = (value) => typeof value === "number" && !Number.isNaN(value);
493511
const isValidString = (value) => typeof value === "string";
@@ -623,7 +641,7 @@ export function __table(source, operations) {
623641
}
624642
}
625643
for (const {column, direction} of reverse(operations.sort)) {
626-
const compare = direction === "desc" ? descending : ascending;
644+
const compare = direction === "desc" ? descendingDefined : ascendingDefined;
627645
if (source === input) source = source.slice(); // defensive copy
628646
source.sort((a, b) => compare(a[column], b[column]));
629647
}

test/table-test.js

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -495,22 +495,41 @@ describe("__table", () => {
495495
});
496496

497497
it("__table sort", () => {
498-
const operations1 = {...EMPTY_TABLE_DATA.operations, sort: [{column: "a", direction: "desc"}]};
498+
const operationsDesc = {...EMPTY_TABLE_DATA.operations, sort: [{column: "a", direction: "desc"}]};
499499
assert.deepStrictEqual(
500-
__table(source, operations1),
500+
__table(source, operationsDesc),
501501
[{a: 3, b: 6, c: 9}, {a: 2, b: 4, c: 6}, {a: 1, b: 2, c: 3}]
502502
);
503+
const operationsAsc = {...EMPTY_TABLE_DATA.operations, sort: [{column: "a", direction: "asc"}]};
504+
assert.deepStrictEqual(
505+
__table(source, operationsAsc),
506+
[{a: 1, b: 2, c: 3}, {a: 2, b: 4, c: 6}, {a: 3, b: 6, c: 9}]
507+
);
503508
const sourceExtended = [...source, {a: 1, b: 3, c: 3}, {a: 1, b: 5, c: 3}];
504-
const operations2 = {
509+
const operationsMulti = {
505510
...EMPTY_TABLE_DATA.operations,
506511
sort: [{column: "a", direction: "desc"}, {column: "b", direction: "desc"}]
507512
};
508513
assert.deepStrictEqual(
509-
__table(sourceExtended, operations2),
514+
__table(sourceExtended, operationsMulti),
510515
[{a: 3, b: 6, c: 9}, {a: 2, b: 4, c: 6}, {a: 1, b: 5, c: 3}, {a: 1, b: 3, c: 3}, {a: 1, b: 2, c: 3}]
511516
);
512517
});
513518

519+
it("__table sort missing values", () => {
520+
const sourceWithMissing = [{a: 1}, {a: null}, {a: undefined}, {a: 10}, {a: 5}, {a: NaN}, {a: null}, {a: 20}];
521+
const operationsDesc = {...EMPTY_TABLE_DATA.operations, sort: [{column: "a", direction: "desc"}]};
522+
assert.deepStrictEqual(
523+
__table(sourceWithMissing, operationsDesc),
524+
[{a: 20}, {a: 10}, {a: 5}, {a: 1}, {a: null}, {a: undefined}, {a: NaN}, {a: null}]
525+
);
526+
const operationsAsc = {...EMPTY_TABLE_DATA.operations, sort: [{column: "a", direction: "asc"}]};
527+
assert.deepStrictEqual(
528+
__table(sourceWithMissing, operationsAsc),
529+
[{a: 1}, {a: 5}, {a: 10}, {a: 20}, {a: null}, {a: undefined}, {a: NaN}, {a: null}]
530+
);
531+
});
532+
514533
it("__table sort does not mutate input", () => {
515534
const operations = {...EMPTY_TABLE_DATA.operations, sort: [{column: "a", direction: "desc"}]};
516535
assert.deepStrictEqual(

0 commit comments

Comments
 (0)