Dataframe (#576)

* initial dataframe commit

* initial dataframe port of core app

* rename variables for clarity

* remove unused import

* comment out unused code

* fix array handling bug in crossfilter dimension creation

* allow creation of empty dataframes

* handle non-existent columns

* handle non-existent columns

* revise tests for new dataframe

* comments for clarity

* comments for clarity

* generate bulk add placeholder with real gene names

* fix bug in gene name adding

* more dataframe unit tests

* fix bug - subset from current world, not universe

* put cut and pasted code into a single function

* improve caching of crossfilter

* remove cascading update bug from graph

* more performance work

* improve state handling for scatterplot

* performance optimization of critical path

* add column summarization

* dataframe utils

* add callOnceLazy

* fix tests

* minor updates found during review

* fix misspelling

* remove RESTv02 from function names

* comment cleanup

* cut/icut col parameter defaults to null

* break up large test

* improve tests and comments on dataframe at/has functions
This commit is contained in:
Bruce Martin
2019-02-22 11:31:34 -08:00
committed by GitHub
parent 57c4e9ff33
commit 6b33315cbe
29 changed files with 1798 additions and 611 deletions
@@ -0,0 +1,377 @@
import * as Dataframe from "../../../src/util/dataframe";
describe("dataframe constructor", () => {
test("empty dataframe", () => {
const df = new Dataframe.Dataframe([0, 0], []);
expect(df).toBeDefined();
expect(df.dims).toEqual([0, 0]);
expect(df).toHaveLength(0);
expect(df.icol(0)).not.toBeDefined();
});
test("create with default indices", () => {
const df = new Dataframe.Dataframe(
[3, 2],
[new Int32Array(3).fill(0), new Int32Array(3).fill(1)]
);
expect(df).toBeDefined();
expect(df.dims).toEqual([3, 2]);
expect(df.rowIndex).toBeInstanceOf(Dataframe.IdentityInt32Index);
expect(df.colIndex).toBeInstanceOf(Dataframe.IdentityInt32Index);
expect(df.at(0, 0)).toEqual(0);
expect(df.at(2, 1)).toEqual(1);
expect(df.iat(0, 0)).toEqual(0);
expect(df.iat(2, 1)).toEqual(1);
});
test("create with labelled indices", () => {
const df = new Dataframe.Dataframe(
[3, 2],
[new Int32Array([0, 1, 2]), new Int32Array([3, 4, 5])],
new Dataframe.DenseInt32Index([2, 1, 0]),
new Dataframe.KeyIndex(["A", "B"])
);
expect(df).toBeDefined();
expect(df.dims).toEqual([3, 2]);
expect(df.rowIndex).toBeInstanceOf(Dataframe.DenseInt32Index);
expect(df.colIndex).toBeInstanceOf(Dataframe.KeyIndex);
expect(df.rowIndex.keys()).toEqual(new Int32Array([2, 1, 0]));
expect(df.colIndex.keys()).toEqual(["A", "B"]);
expect(df.at(0, "A")).toEqual(2);
expect(df.at(2, "B")).toEqual(3);
expect(df.iat(0, 0)).toEqual(0);
expect(df.iat(2, 1)).toEqual(5);
});
});
describe("simple data access", () => {
const df = new Dataframe.Dataframe(
[4, 2],
[
new Float64Array([0.0, Number.NaN, Number.POSITIVE_INFINITY, 3.14159]),
["red", "blue", "green", "nan"]
],
new Dataframe.DenseInt32Index([3, 2, 1, 0]),
new Dataframe.KeyIndex(["numbers", "colors"])
);
test("iat", () => {
expect(df).toBeDefined();
// present
expect(df.iat(0, 0)).toEqual(0.0);
expect(df.iat(0, 1)).toEqual("red");
expect(df.iat(1, 0)).toEqual(Number.NaN);
expect(df.iat(1, 1)).toEqual("blue");
expect(df.iat(2, 0)).toEqual(Number.POSITIVE_INFINITY);
expect(df.iat(2, 1)).toEqual("green");
expect(df.iat(3, 0)).toEqual(3.14159);
expect(df.iat(3, 1)).toEqual("nan");
// labels out of range have no defined behavior
});
test("at", () => {
expect(df).toBeDefined();
// present
expect(df.at(3, "numbers")).toEqual(0.0);
expect(df.at(3, "colors")).toEqual("red");
expect(df.at(2, "numbers")).toEqual(Number.NaN);
expect(df.at(2, "colors")).toEqual("blue");
expect(df.at(1, "numbers")).toEqual(Number.POSITIVE_INFINITY);
expect(df.at(1, "colors")).toEqual("green");
expect(df.at(0, "numbers")).toEqual(3.14159);
expect(df.at(0, "colors")).toEqual("nan");
// labels out of range have no defined behavior
});
test("ihas", () => {
expect(df).toBeDefined();
// present
expect(df.ihas(0, 0)).toBeTruthy();
expect(df.ihas(1, 1)).toBeTruthy();
expect(df.ihas(3, 1)).toBeTruthy();
// not present
expect(df.ihas(-1, -1)).toBeFalsy();
expect(df.ihas(0, 99)).toBeFalsy();
expect(df.ihas(99, 0)).toBeFalsy();
expect(df.ihas(99, 99)).toBeFalsy();
expect(df.ihas(-1, 0)).toBeFalsy();
expect(df.ihas(0, -1)).toBeFalsy();
});
test("has", () => {
expect(df).toBeDefined();
// present
expect(df.has(3, "numbers")).toBeTruthy();
expect(df.has(0, "numbers")).toBeTruthy();
expect(df.has(3, "colors")).toBeTruthy();
expect(df.has(0, "colors")).toBeTruthy();
// not present
expect(df.has(3, "foo")).toBeFalsy();
expect(df.has(-1, "numbers")).toBeFalsy();
expect(df.has(-1, -1)).toBeFalsy();
expect(df.has(null, null)).toBeFalsy();
expect(df.has(0, "foo")).toBeFalsy();
expect(df.has(99, "numbers")).toBeFalsy();
expect(df.has(99, "foo")).toBeFalsy();
});
});
describe("dataframe subsetting", () => {
describe("cutByList", () => {
const sourceDf = new Dataframe.Dataframe(
[3, 4],
[
new Int32Array([0, 1, 2]),
["A", "B", "C"],
new Float32Array([4.4, 5.5, 6.6]),
["red", "green", "blue"]
],
null,
new Dataframe.KeyIndex(["int32", "string", "float32", "colors"])
);
test("all rows, one column", () => {
const dfA = sourceDf.cutByList(null, ["colors"]);
expect(dfA).toBeDefined();
expect(dfA.dims).toEqual([3, 1]);
expect(dfA.iat(0, 0)).toEqual("red");
expect(dfA.at(2, "colors")).toEqual("blue");
expect(dfA.col("colors").asArray()).toEqual(["red", "green", "blue"]);
expect(dfA.icol(0).asArray()).toEqual(["red", "green", "blue"]);
expect(dfA.col("colors").asArray()).toEqual(
sourceDf.col("colors").asArray()
);
expect(dfA.rowIndex.keys()).toEqual(sourceDf.rowIndex.keys());
expect(dfA.colIndex.keys()).toEqual(["colors"]);
});
test("all rows, two columns", () => {
const dfB = sourceDf.cutByList(null, ["colors", "float32"]);
expect(dfB).toBeDefined();
expect(dfB.dims).toEqual([3, 2]);
expect(dfB.iat(0, 0)).toBeCloseTo(4.4);
expect(dfB.iat(0, 1)).toEqual("red");
expect(dfB.at(2, "colors")).toEqual("blue");
expect(dfB.at(2, "float32")).toBeCloseTo(6.6);
expect(dfB.col("colors").asArray()).toEqual(["red", "green", "blue"]);
expect(dfB.col("float32").asArray()).toEqual(
new Float32Array([4.4, 5.5, 6.6])
);
expect(dfB.icol(0).asArray()).toEqual(dfB.col("float32").asArray());
expect(dfB.icol(1).asArray()).toEqual(dfB.col("colors").asArray());
expect(dfB.col("colors").asArray()).toEqual(
sourceDf.col("colors").asArray()
);
expect(dfB.col("float32").asArray()).toEqual(
sourceDf.col("float32").asArray()
);
expect(dfB.rowIndex.keys()).toEqual(sourceDf.rowIndex.keys());
expect(dfB.colIndex.keys()).toEqual(["float32", "colors"]);
});
test("one row, all columns", () => {
const dfC = sourceDf.cutByList([1], null);
expect(dfC).toBeDefined();
expect(dfC.dims).toEqual([1, 4]);
expect(dfC.iat(0, 0)).toEqual(1);
expect(dfC.iat(0, 1)).toEqual("B");
expect(dfC.iat(0, 2)).toBeCloseTo(5.5);
expect(dfC.iat(0, 3)).toEqual("green");
expect(dfC.rowIndex.keys()).toEqual(new Int32Array([1]));
expect(dfC.colIndex.keys()).toEqual(sourceDf.colIndex.keys());
});
test("two rows, all columns", () => {
const dfD = sourceDf.cutByList([0, 2], null);
expect(dfD).toBeDefined();
expect(dfD.dims).toEqual([2, 4]);
expect(dfD.icol(0).asArray()).toEqual(new Int32Array([0, 2]));
expect(dfD.icol(1).asArray()).toEqual(["A", "C"]);
expect(dfD.icol(2).asArray()).toEqual(new Float32Array([4.4, 6.6]));
expect(dfD.icol(3).asArray()).toEqual(["red", "blue"]);
expect(dfD.rowIndex.keys()).toEqual(new Int32Array([0, 2]));
expect(dfD.colIndex.keys()).toEqual(sourceDf.colIndex.keys());
});
test("all rows, all columns", () => {
const dfE = sourceDf.cutByList(null, null);
expect(dfE).toBeDefined();
expect(dfE.dims).toEqual([3, 4]);
expect(dfE.icol(0).asArray()).toEqual(sourceDf.icol(0).asArray());
expect(dfE.icol(1).asArray()).toEqual(sourceDf.icol(1).asArray());
expect(dfE.icol(2).asArray()).toEqual(sourceDf.icol(2).asArray());
expect(dfE.icol(3).asArray()).toEqual(sourceDf.icol(3).asArray());
expect(dfE.rowIndex.keys()).toEqual(sourceDf.rowIndex.keys());
expect(dfE.colIndex.keys()).toEqual(sourceDf.colIndex.keys());
});
test("two rows, two colums", () => {
const dfF = sourceDf.cutByList([0, 2], ["int32", "float32"]);
expect(dfF).toBeDefined();
expect(dfF.dims).toEqual([2, 2]);
expect(dfF.icol(0).asArray()).toEqual(new Int32Array([0, 2]));
expect(dfF.icol(1).asArray()).toEqual(new Float32Array([4.4, 6.6]));
expect(dfF.rowIndex.keys()).toEqual(new Int32Array([0, 2]));
expect(dfF.colIndex.keys()).toEqual(["int32", "float32"]);
});
});
test("icutByMask", () => {
const sourceDf = new Dataframe.Dataframe(
[3, 4],
[
new Int32Array([0, 1, 2]),
["A", "B", "C"],
new Float32Array([4.4, 5.5, 6.6]),
["red", "green", "blue"]
],
new Dataframe.DenseInt32Index([2, 4, 6]),
new Dataframe.KeyIndex(["int32", "string", "float32", "colors"])
);
const dfA = sourceDf.icutByMask(
new Uint8Array([0, 1, 1]),
new Uint8Array([1, 0, 0, 1])
);
expect(dfA.dims).toEqual([2, 2]);
expect(dfA.icol(0).asArray()).toEqual(new Int32Array([1, 2]));
expect(dfA.icol(1).asArray()).toEqual(["green", "blue"]);
expect(dfA.rowIndex.keys()).toEqual(new Int32Array([4, 6]));
expect(dfA.colIndex.keys()).toEqual(["int32", "colors"]);
});
});
describe("dataframe factories", () => {
test("create", () => {
const df = Dataframe.Dataframe.create(
[3, 3],
[
new Array(3).fill(0),
new Int16Array(3).fill(99),
new Float64Array(3).fill(1.1)
]
);
expect(df).toBeDefined();
expect(df.dims).toEqual([3, 3]);
expect(df).toHaveLength(3);
expect(df.iat(0, 0)).toEqual(0);
expect(df.iat(1, 1)).toEqual(99);
expect(df.iat(2, 2)).toBeCloseTo(1.1);
expect(df.iat(0, 0)).toEqual(df.at(0, 0));
expect(df.iat(1, 1)).toEqual(df.at(1, 1));
expect(df.iat(2, 2)).toEqual(df.at(2, 2));
});
test("clone", () => {
const dfA = new Dataframe.Dataframe(
[3, 2],
[new Int32Array([0, 1, 2]), new Int32Array([3, 4, 5])],
new Dataframe.DenseInt32Index([2, 1, 0]),
new Dataframe.KeyIndex(["A", "B"])
);
const dfB = dfA.clone();
expect(dfB).not.toBe(dfA);
expect(dfB.dims).toEqual(dfA.dims);
expect(dfB).toHaveLength(dfA.length);
expect(dfB.rowIndex.keys()).toEqual(dfA.rowIndex.keys());
expect(dfB.colIndex.keys()).toEqual(dfA.colIndex.keys());
for (let i = 0, l = dfB.dims[1]; i < l; i += 1) {
expect(dfB.icol(i).asArray()).toEqual(dfA.icol(i).asArray());
}
});
});
describe("dataframe col", () => {
let df = null;
beforeEach(() => {
df = new Dataframe.Dataframe(
[2, 2],
[[true, false], [1, 0]],
null,
new Dataframe.KeyIndex(["A", "B"])
);
});
test("col", () => {
expect(df).toBeDefined();
expect(df.col("A")).toBe(df.icol(0));
expect(df.col("B")).toBe(df.icol(1));
expect(df.col("undefined")).toBeUndefined();
expect(df.icol("undefined")).toBeUndefined();
const colA = df.col("A");
expect(colA).toBeInstanceOf(Function);
expect(colA.asArray).toBeInstanceOf(Function);
expect(colA.has).toBeInstanceOf(Function);
expect(colA.ihas).toBeInstanceOf(Function);
expect(colA.indexOf).toBeInstanceOf(Function);
expect(colA.iget).toBeInstanceOf(Function);
});
test("col.asArray", () => {
expect(df).toBeDefined();
expect(df.col("A").asArray()).toEqual([true, false]);
expect(df.icol(0).asArray()).toEqual([true, false]);
expect(df.col("B").asArray()).toEqual([1, 0]);
expect(df.icol(1).asArray()).toEqual([1, 0]);
});
test("col.has", () => {
expect(df).toBeDefined();
expect(df.col("A").has(-1)).toBe(false);
expect(df.col("A").has(0)).toBe(true);
expect(df.col("A").has(1)).toBe(true);
expect(df.col("A").has(2)).toBe(false);
expect(df.col("B").has(-1)).toBe(false);
expect(df.col("B").has(0)).toBe(true);
expect(df.col("B").has(1)).toBe(true);
expect(df.col("B").has(2)).toBe(false);
});
test("col.ihas", () => {
expect(df).toBeDefined();
expect(df.col("A").ihas(-1)).toBe(false);
expect(df.col("A").ihas(0)).toBe(true);
expect(df.col("A").ihas(1)).toBe(true);
expect(df.col("A").ihas(2)).toBe(false);
expect(df.col("B").ihas(-1)).toBe(false);
expect(df.col("B").ihas(0)).toBe(true);
expect(df.col("B").ihas(1)).toBe(true);
expect(df.col("B").ihas(2)).toBe(false);
});
test("col.iget", () => {
expect(df).toBeDefined();
expect(df.col("A").iget(0)).toEqual(df.iat(0, 0));
expect(df.col("B").iget(1)).toEqual(df.iat(1, 1));
});
test("col.indexOf", () => {
expect(df).toBeDefined();
expect(df.col("A").indexOf(true)).toEqual(0);
expect(df.col("A").indexOf(false)).toEqual(1);
expect(df.col("A").indexOf(99)).toBeUndefined();
expect(df.col("A").indexOf(undefined)).toBeUndefined();
expect(df.col("A").indexOf(1)).toBeUndefined();
expect(df.col("B").indexOf(1)).toEqual(0);
expect(df.col("B").indexOf(0)).toEqual(1);
expect(df.col("B").indexOf(99)).toBeUndefined();
expect(df.col("B").indexOf(undefined)).toBeUndefined();
expect(df.col("B").indexOf(true)).toBeUndefined();
});
});
@@ -157,16 +157,6 @@ const anAnnotationsVarFBSResponse = (() => {
return encodeMatrix(columns, anAnnotationsVarJSONResponse.names);
})();
const aLayoutJSONResponse = {
layout: {
ndims: 2,
coordinates: _()
.range(nObs)
.map(idx => [idx, Math.random(), Math.random()])
.value()
}
};
const aLayoutFBSResponse = (() => {
const coords = [
new Float32Array(nObs).fill(Math.random()),
@@ -190,7 +180,7 @@ const aLayoutFBSResponse = (() => {
NetEncoding.Matrix.startMatrix(builder);
NetEncoding.Matrix.addNRows(builder, nObs);
NetEncoding.Matrix.addNCols(builder, nVar);
NetEncoding.Matrix.addNCols(builder, coords.length);
NetEncoding.Matrix.addColumns(builder, columns);
const matrix = NetEncoding.Matrix.endMatrix(builder);
builder.finish(matrix);
@@ -1,4 +1,9 @@
import summarizeAnnotations from "../../../src/util/stateManager/summarizeAnnotations";
import * as Dataframe from "../../../src/util/dataframe";
function float32Conversion(f) {
return new Float32Array([39.3])[0];
}
describe("summarizeAnnotations", () => {
const schema = {
@@ -20,7 +25,8 @@ describe("summarizeAnnotations", () => {
};
test("empty test", () => {
const summary = summarizeAnnotations(schema, [], []);
const df = Dataframe.Dataframe.empty();
const summary = summarizeAnnotations(schema, df, df.clone());
expect(summary).toEqual(
expect.objectContaining({
obs: {
@@ -69,18 +75,27 @@ describe("summarizeAnnotations", () => {
});
test("simple test", () => {
const obsAnnotations = [
{
__index__: 0,
name: "n1",
nameString: "hi",
nameBoolean: true,
nameFloat32: 39.3,
nameInt32: 99,
nameCategorical: 1
}
];
const varAnnotations = [];
const obsAnnotations = new Dataframe.Dataframe(
[1, 6],
[
["n1"],
["hi"],
[true],
new Float32Array([39.3]),
new Int32Array([99]),
[1]
],
null,
new Dataframe.KeyIndex([
"name",
"nameString",
"nameBoolean",
"nameFloat32",
"nameInt32",
"nameCategorical"
])
);
const varAnnotations = Dataframe.Dataframe.empty();
const summary = summarizeAnnotations(
schema,
@@ -105,7 +120,13 @@ describe("summarizeAnnotations", () => {
},
nameFloat32: {
categorical: false,
range: { min: 39.3, max: 39.3, nan: 0, ninf: 0, pinf: 0 }
range: {
min: float32Conversion(39.3),
max: float32Conversion(39.3),
nan: 0,
ninf: 0,
pinf: 0
}
},
nameInt32: {
categorical: false,
@@ -124,36 +145,27 @@ describe("summarizeAnnotations", () => {
});
test("multi test", () => {
const obsAnnotations = [
{
__index__: 0,
name: "n0",
nameString: "hi",
nameBoolean: false,
nameFloat32: 39.3,
nameInt32: 99,
nameCategorical: 1
},
{
__index__: 1,
name: "n1",
nameString: "hi",
nameBoolean: true,
nameFloat32: 39.3,
nameInt32: 99,
nameCategorical: false
},
{
__index__: 2,
name: "n2",
nameString: "bye",
nameBoolean: true,
nameFloat32: 0,
nameInt32: 99,
nameCategorical: "0"
}
];
const varAnnotations = [];
const obsAnnotations = new Dataframe.Dataframe(
[3, 6],
[
["n0", "n1", "n2"],
["hi", "hi", "bye"],
[false, true, true],
new Float32Array([39.3, 39.3, 0]),
new Int32Array([99, 99, 99]),
[1, false, "0"]
],
null,
new Dataframe.KeyIndex([
"name",
"nameString",
"nameBoolean",
"nameFloat32",
"nameInt32",
"nameCategorical"
])
);
const varAnnotations = Dataframe.Dataframe.empty();
const summary = summarizeAnnotations(
schema,
@@ -178,7 +190,13 @@ describe("summarizeAnnotations", () => {
},
nameFloat32: {
categorical: false,
range: { min: 0, max: 39.3, nan: 0, ninf: 0, pinf: 0 }
range: {
min: 0,
max: float32Conversion(39.3),
nan: 0,
ninf: 0,
pinf: 0
}
},
nameInt32: {
categorical: false,
@@ -197,45 +215,32 @@ describe("summarizeAnnotations", () => {
});
test("non-finite numbers", () => {
const obsAnnotations = [
{
__index__: 0,
name: "n0",
nameString: "hi",
nameBoolean: false,
nameFloat32: 39.3,
nameInt32: 99,
nameCategorical: 1
},
{
__index__: 1,
name: "n1",
nameString: "hi",
nameBoolean: true,
nameFloat32: Number.NEGATIVE_INFINITY,
nameInt32: 99,
nameCategorical: false
},
{
__index__: 2,
name: "n2",
nameString: "bye",
nameBoolean: true,
nameFloat32: Number.NaN,
nameInt32: 99,
nameCategorical: "0"
},
{
__index__: 3,
name: "n2",
nameString: "bye",
nameBoolean: true,
nameFloat32: Number.POSITIVE_INFINITY,
nameInt32: 99,
nameCategorical: "0"
}
];
const varAnnotations = [];
const obsAnnotations = new Dataframe.Dataframe(
[4, 6],
[
["n0", "n1", "n2", "n2"],
["hi", "hi", "bye", "bye"],
[false, true, true, true],
new Float32Array([
39.3,
Number.NEGATIVE_INFINITY,
Number.NaN,
Number.POSITIVE_INFINITY
]),
new Int32Array([99, 99, 99, 99]),
[1, false, "0", "0"]
],
null,
new Dataframe.KeyIndex([
"name",
"nameString",
"nameBoolean",
"nameFloat32",
"nameInt32",
"nameCategorical"
])
);
const varAnnotations = Dataframe.Dataframe.empty();
const summary = summarizeAnnotations(
schema,
@@ -260,7 +265,13 @@ describe("summarizeAnnotations", () => {
},
nameFloat32: {
categorical: false,
range: { min: 39.3, max: 39.3, nan: 1, ninf: 1, pinf: 1 }
range: {
min: float32Conversion(39.3),
max: float32Conversion(39.3),
nan: 1,
ninf: 1,
pinf: 1
}
},
nameInt32: {
categorical: false,
@@ -1,13 +1,13 @@
import _ from "lodash";
import * as Universe from "../../../src/util/stateManager/universe";
import * as Dataframe from "../../../src/util/dataframe";
import * as REST from "./sampleResponses";
describe("createUniverseFromRestV02Response", () => {
describe("createUniverseFromResponse", () => {
/*
test createUniverseFromRestV02Response - this function converts
test createUniverseFromResponse - this function converts
a set of REST 0.2 responses into a "new" Universe.
createUniverseFromRestV02Response(
createUniverseFromResponse(
configResponse,
schemaResponse,
annotationsObsResponse,
@@ -30,7 +30,7 @@ describe("createUniverseFromRestV02Response", () => {
create a universe from sample data nad validate its shape & contents
*/
const { nObs, nVar } = REST.schema.schema.dataframe;
const universe = Universe.createUniverseFromRestV02Response(
const universe = Universe.createUniverseFromResponse(
REST.config,
REST.schema,
REST.annotationsObs,
@@ -45,23 +45,23 @@ describe("createUniverseFromRestV02Response", () => {
nObs,
nVar,
schema: REST.schema.schema,
obsAnnotations: expect.any(Array),
varAnnotations: expect.any(Array),
obsNameToIndexMap: expect.any(Object),
varNameToIndexMap: expect.any(Object),
obsLayout: expect.objectContaining({
X: expect.any(Float32Array),
Y: expect.any(Float32Array)
}),
obsAnnotations: expect.any(Dataframe.Dataframe),
varAnnotations: expect.any(Dataframe.Dataframe),
obsLayout: expect.any(Dataframe.Dataframe),
summary: expect.any(Object),
varDataCache: expect.any(Object)
})
);
expect(universe.obsAnnotations).toHaveLength(nObs);
expect(_.keys(universe.obsNameToIndexMap)).toHaveLength(nObs);
expect(universe.obsLayout.X).toHaveLength(nObs);
expect(universe.obsLayout.Y).toHaveLength(nObs);
expect(universe.varAnnotations).toHaveLength(nVar);
expect(_.keys(universe.varNameToIndexMap)).toHaveLength(nVar);
expect(universe.obsAnnotations.dims).toEqual([
nObs,
REST.schema.schema.annotations.obs.length
]);
expect(universe.obsLayout.dims).toEqual([nObs, 2]);
expect(universe.obsLayout.colIndex.keys()).toEqual(["X", "Y"]);
expect(universe.varAnnotations.dims).toEqual([
nVar,
REST.schema.schema.annotations.var.length
]);
});
});
@@ -1,6 +1,7 @@
import _ from "lodash";
import * as Universe from "../../../src/util/stateManager/universe";
import * as World from "../../../src/util/stateManager/world";
import * as Dataframe from "../../../src/util/dataframe";
import Crossfilter from "../../../src/util/typedCrossfilter";
import * as REST from "./sampleResponses";
import {
@@ -16,7 +17,7 @@ the default REST test response.
const defaultBigBang = () => {
/* create unverse, world, crossfilter and dimensionMap */
/* create universe */
const universe = Universe.createUniverseFromRestV02Response(
const universe = Universe.createUniverseFromResponse(
REST.config,
REST.schema,
REST.annotationsObs,
@@ -40,7 +41,7 @@ const defaultBigBang = () => {
describe("createWorldFromEntireUniverse", () => {
test("create from REST sample", () => {
const universe = Universe.createUniverseFromRestV02Response(
const universe = Universe.createUniverseFromResponse(
REST.config,
REST.schema,
REST.annotationsObs,
@@ -75,10 +76,7 @@ describe("createWorldFromEntireUniverse", () => {
.value()
}),
varDataCache: expect.any(Object),
obsIndex: null, // null indicating full universe
obsBackIndex: null
varDataCache: expect.any(Object)
})
);
});
@@ -111,51 +109,43 @@ describe("createWorldFromCurrentSelection", () => {
*/
/* matchFilter must match the dimension filters above */
const matchFilter = val => val.field1 >= 0 && val.field1 < 5 && !val.field3;
const universeIndices = _()
.range(universe.nObs)
.filter(idx => matchFilter(universe.obsAnnotations[idx]))
.value();
const expected = {
nObs: universeIndices.length,
obsAnnotations: _.map(universeIndices, i => universe.obsAnnotations[i]),
obsLayout: {
X: new Float32Array(
_.map(universeIndices, i => universe.obsLayout.X[i])
),
Y: new Float32Array(
_.map(universeIndices, i => universe.obsLayout.Y[i])
)
},
obsBackIndex: _.transform(
universeIndices,
(result, univIdx, worldIdx) => {
result[univIdx] = worldIdx;
},
new Uint32Array(universe.nObs).fill(-1)
),
obsIndex: new Uint32Array(universeIndices)
const matchFilter = (df, row) => {
const field1 = df.at(row, "field1");
const field3 = df.at(row, "field3");
return field1 >= 0 && field1 < 5 && !field3;
};
const matchingIndices = _()
.range(universe.nObs)
.filter(idx => matchFilter(universe.obsAnnotations, idx))
.value();
expect(world).toMatchObject(
expect.objectContaining({
api: "0.2",
nObs: expected.nObs,
nObs: matchingIndices.length,
nVar: universe.nVar,
schema: universe.schema,
obsAnnotations: expected.obsAnnotations,
obsAnnotations: expect.any(Dataframe.Dataframe),
varAnnotations: universe.varAnnotations,
obsLayout: expected.obsLayout,
obsLayout: expect.any(Dataframe.Dataframe),
summary: {
obs: expect.any(Object) /* we could do better! */,
var: expect.any(Object) /* we could do better! */
},
varDataCache: expect.any(Object),
obsIndex: expected.obsIndex,
obsBackIndex: expected.obsBackIndex
varDataCache: expect.any(Object)
})
);
expect(world.obsAnnotations.rowIndex.keys()).toEqual(
new Int32Array(matchingIndices)
);
expect(world.obsAnnotations.colIndex.keys()).toEqual(
universe.obsAnnotations.colIndex.keys()
);
expect(world.obsLayout.rowIndex.keys()).toEqual(
new Int32Array(matchingIndices)
);
expect(world.obsLayout.colIndex.keys()).toEqual(["X", "Y"]);
});
});
@@ -219,7 +209,9 @@ describe("subsetVarData", () => {
world,
crossfilter
);
expect(newWorld.obsIndex).toMatchObject(new Uint32Array([0, 2]));
expect(newWorld.obsAnnotations.rowIndex.keys()).toEqual(
new Int32Array([0, 2])
);
/* expect a subset */
const result = World.subsetVarData(newWorld, universe, sourceVarData);
@@ -2,16 +2,27 @@ import {
countCategoryValues2D,
clearCaches
} from "../../../src/util/stateManager/worldUtil";
import * as Dataframe from "../../../src/util/dataframe";
describe("WorldUtil cache management", () => {
test("empty", () => {
const count = countCategoryValues2D("a", "b", []);
const count = countCategoryValues2D(
"a",
"b",
new Dataframe.Dataframe([0, 0], [])
);
expect(count).toMatchObject(new Map());
expect(count.size).toBe(0);
});
test("simple couts", () => {
const rows = [{ a: 0, b: false }, { a: 0, b: true }, { a: 1, b: false }];
const count = countCategoryValues2D("a", "b", rows);
const df = new Dataframe.Dataframe(
[3, 2],
[[0, 0, 1], [false, true, false]],
null,
new Dataframe.KeyIndex(["a", "b"])
);
const count = countCategoryValues2D("a", "b", df);
expect(count).toMatchObject(
new Map([
[0, new Map([[true, 1], [false, 1]])],
@@ -22,16 +33,22 @@ describe("WorldUtil cache management", () => {
test("memo cache clear", () => {
clearCaches();
const row1 = [];
const row2 = [{ a: 0, b: false }, { a: 0, b: true }, { a: 1, b: false }];
const count1 = countCategoryValues2D("a", "b", row1);
const count2 = countCategoryValues2D("a", "b", row1);
const count3 = countCategoryValues2D("a", "b", []);
const count4 = countCategoryValues2D("a", "b", row2);
const df1 = new Dataframe.Dataframe([0, 0], []);
const df2 = new Dataframe.Dataframe(
[3, 2],
[[0, 0, 1], [false, true, false]],
null,
new Dataframe.KeyIndex(["a", "b"])
);
const count1 = countCategoryValues2D("a", "b", df1);
const count2 = countCategoryValues2D("a", "b", df1);
const count3 = countCategoryValues2D("a", "b", df1.clone());
const count4 = countCategoryValues2D("a", "b", df2);
clearCaches();
const count10 = countCategoryValues2D("a", "b", row1);
const count11 = countCategoryValues2D("a", "b", row2);
const count10 = countCategoryValues2D("a", "b", df1);
const count11 = countCategoryValues2D("a", "b", df2);
expect(count1).toEqual(count2);
expect(count1).toEqual(count3);
@@ -118,16 +118,16 @@ describe("selectionCount", () => {
const dim2 = ba.allocDimension();
expect(dim2).toBeDefined();
expect(ba.selectionCount).toEqual(0);
expect(ba.selectionCount()).toEqual(0);
ba.selectAll(dim1);
expect(ba.selectionCount).toEqual(0);
expect(ba.selectionCount()).toEqual(0);
ba.selectAll(dim2);
expect(ba.selectionCount).toEqual(defaultTestLength);
expect(ba.selectionCount()).toEqual(defaultTestLength);
for (let i = 0; i < defaultTestLength; i += 1) {
ba.deselectOne(dim1, i);
expect(ba.selectionCount).toEqual(defaultTestLength - i - 1);
expect(ba.selectionCount).toEqual(ba.countAllOnes());
expect(ba.selectionCount()).toEqual(defaultTestLength - i - 1);
expect(ba.selectionCount()).toEqual(ba.countAllOnes());
}
ba.freeDimension(dim1);
@@ -119,14 +119,22 @@ function groupReduce(data, valueMap, valueReduce, valueInit) {
}
function groupCount(data, map) {
return groupReduce(data, map, (p, v) => p + 1, () => 0);
return groupReduce(data, map, p => p + 1, () => 0);
}
function groupSum(data, map) {
return groupReduce(data, map, (p, v) => (p += map(v)), () => 0);
return groupReduce(
data,
map,
(p, v) => {
p += map(v);
return p;
},
() => 0
);
}
var payments = null;
let payments = null;
beforeEach(() => {
payments = crossfilter(someData);
});
@@ -139,7 +147,7 @@ describe("typedCrossfilter", () => {
const quantity = payments.dimension(
crossfilter.ScalarDimension,
r => r.quantity,
(i, data) => data[i].quantity,
Int32Array
);
expect(quantity).toBeDefined();
@@ -154,20 +162,23 @@ describe("typedCrossfilter", () => {
expect(payments).toBeDefined();
const quantity = payments.dimension(
crossfilter.ScalarDimension,
r => r.quantity,
(i, data) => data[i].quantity,
Int32Array
);
const tip = payments.dimension(
crossfilter.ScalarDimension,
r => r.tip,
(i, data) => data[i].tip,
Float32Array
);
const total = payments.dimension(
crossfilter.ScalarDimension,
r => r.total,
(i, data) => data[i].total,
Float32Array
);
const type = payments.dimension(crossfilter.EnumDimension, r => r.type);
const type = payments.dimension(
crossfilter.EnumDimension,
(i, data) => data[i].type
);
expect(quantity).toBeDefined();
expect(tip).toBeDefined();
@@ -214,20 +225,18 @@ describe("typedCrossfilter", () => {
expect(payments).toBeDefined();
const quantity = payments.dimension(
crossfilter.ScalarDimension,
r => r.quantity,
(i, data) => data[i].quantity,
Int32Array
);
const tip = payments.dimension(
crossfilter.ScalarDimension,
r => r.tip,
(i, data) => data[i].tip,
Float32Array
);
const total = payments.dimension(
crossfilter.ScalarDimension,
r => r.total,
Float32Array
const type = payments.dimension(
crossfilter.EnumDimension,
(i, data) => data[i].type
);
const type = payments.dimension(crossfilter.EnumDimension, r => r.type);
quantity.filterExact(1);
expect(payments.countFiltered()).toEqual(
@@ -250,20 +259,23 @@ describe("typedCrossfilter", () => {
expect(payments).toBeDefined();
const quantity = payments.dimension(
crossfilter.ScalarDimension,
r => r.quantity,
(i, data) => data[i].quantity,
Int32Array
);
const tip = payments.dimension(
crossfilter.ScalarDimension,
r => r.tip,
(i, data) => data[i].tip,
Float32Array
);
const total = payments.dimension(
crossfilter.ScalarDimension,
r => r.total,
(i, data) => data[i].total,
Float32Array
);
const type = payments.dimension(crossfilter.EnumDimension, r => r.type);
const type = payments.dimension(
crossfilter.EnumDimension,
(i, data) => data[i].type
);
tip.filterRange([0, 91]);
expect(payments.allFiltered()).toEqual(
@@ -291,20 +303,23 @@ describe("typedCrossfilter", () => {
expect(payments).toBeDefined();
const quantity = payments.dimension(
crossfilter.ScalarDimension,
r => r.quantity,
(i, data) => data[i].quantity,
Int32Array
);
const tip = payments.dimension(
crossfilter.ScalarDimension,
r => r.tip,
(i, data) => data[i].tip,
Float32Array
);
const total = payments.dimension(
crossfilter.ScalarDimension,
r => r.total,
(i, data) => data[i].total,
Float32Array
);
const type = payments.dimension(crossfilter.EnumDimension, r => r.type);
const type = payments.dimension(
crossfilter.EnumDimension,
(i, data) => data[i].type
);
type.filterEnum(["tab", "cash"]);
expect(payments.allFiltered()).toEqual(
@@ -326,27 +341,30 @@ describe("typedCrossfilter", () => {
expect(payments).toBeDefined();
const quantity = payments.dimension(
crossfilter.ScalarDimension,
r => r.quantity,
(i, data) => data[i].quantity,
Int32Array
);
const tip = payments.dimension(
crossfilter.ScalarDimension,
r => r.tip,
(i, data) => data[i].tip,
Float32Array
);
const total = payments.dimension(
crossfilter.ScalarDimension,
r => r.total,
(i, data) => data[i].total,
Float32Array
);
const type = payments.dimension(crossfilter.EnumDimension, r => r.type);
const type = payments.dimension(
crossfilter.EnumDimension,
(i, data) => data[i].type
);
// Create a bunch of fake dimensions to ensure we can handle > 32
let dimMap = {};
for (let i = 0; i < 65; i++) {
dimMap[i] = payments.dimension(
crossfilter.ScalarDimension,
r => Math.random(),
() => Math.random(),
Float32Array
);
expect(dimMap[i]).toBeDefined();
@@ -372,18 +390,21 @@ describe("typedCrossfilter", () => {
const quantity = payments.dimension(
crossfilter.ScalarDimension,
r => r.quantity,
(i, data) => data[i].quantity,
Int32Array
);
const tip = payments.dimension(
crossfilter.ScalarDimension,
r => r.tip,
(i, data) => data[i].tip,
Int32Array
);
const type = payments.dimension(crossfilter.EnumDimension, r => r.type);
const type = payments.dimension(
crossfilter.EnumDimension,
(i, data) => data[i].type
);
const total = payments.dimension(
crossfilter.ScalarDimension,
r => r.total,
(i, data) => data[i].total,
Int32Array
);
@@ -411,15 +432,18 @@ describe("typedCrossfilter", () => {
const tip = payments.dimension(
crossfilter.ScalarDimension,
r => r.tip,
(i, data) => data[i].tip,
Int32Array
);
const totalX10 = payments.dimension(
crossfilter.ScalarDimension,
r => r.total * 10,
(i, data) => data[i].total * 10,
Int32Array
);
const type = payments.dimension(crossfilter.EnumDimension, r => r.type);
const type = payments.dimension(
crossfilter.EnumDimension,
(i, data) => data[i].type
);
const paymentsByTip_A = tip.group();
const paymentsByTip_B = tip.group(r => 10 * r);
@@ -458,10 +482,13 @@ describe("typedCrossfilter", () => {
const total = payments.dimension(
crossfilter.ScalarDimension,
r => r.total,
(i, data) => data[i].total,
Float32Array
);
const type = payments.dimension(crossfilter.EnumDimension, r => r.type);
const type = payments.dimension(
crossfilter.EnumDimension,
(i, data) => data[i].type
);
const paymentsByTotal = total.group();
const paymentsByType = type.group();
@@ -499,15 +526,18 @@ describe("typedCrossfilter", () => {
const tip = payments.dimension(
crossfilter.ScalarDimension,
r => r.tip,
(i, data) => data[i].tip,
Int32Array
);
const total = payments.dimension(
crossfilter.ScalarDimension,
r => r.total,
(i, data) => data[i].total,
Int32Array
);
const type = payments.dimension(crossfilter.EnumDimension, r => r.type);
const type = payments.dimension(
crossfilter.EnumDimension,
(i, data) => data[i].type
);
const paymentsByTip = tip.group();
const paymentsByTotal = total.group();