Dataframe, part deux - add varData and summarize() (#608)

* 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

* add Dataframe withCol/dropCol

* expression varData now stored in a dataframe

* dead code cleanup

* use dataframe.summarize()

* test cases for Dataframe.col.summarize

* update test cases for new dataframe summarize

* improve naming

* use new hasCol API

* add comments

* add more Dataframe.withCol tests

* add ability to specify row index in cut operation

* retire subsetVarData function

* correctly handle expression subsetting

* lint and improve comments

* rename cut to subset

* changes based on PR review
This commit is contained in:
Bruce Martin
2019-02-28 08:34:22 -08:00
committed by GitHub
parent 2bae696986
commit ffd6273419
21 changed files with 910 additions and 1140 deletions
@@ -129,7 +129,7 @@ describe("simple data access", () => {
});
describe("dataframe subsetting", () => {
describe("cutByList", () => {
describe("subset", () => {
const sourceDf = new Dataframe.Dataframe(
[3, 4],
[
@@ -143,7 +143,7 @@ describe("dataframe subsetting", () => {
);
test("all rows, one column", () => {
const dfA = sourceDf.cutByList(null, ["colors"]);
const dfA = sourceDf.subset(null, ["colors"]);
expect(dfA).toBeDefined();
expect(dfA.dims).toEqual([3, 1]);
expect(dfA.iat(0, 0)).toEqual("red");
@@ -158,7 +158,7 @@ describe("dataframe subsetting", () => {
});
test("all rows, two columns", () => {
const dfB = sourceDf.cutByList(null, ["colors", "float32"]);
const dfB = sourceDf.subset(null, ["colors", "float32"]);
expect(dfB).toBeDefined();
expect(dfB.dims).toEqual([3, 2]);
expect(dfB.iat(0, 0)).toBeCloseTo(4.4);
@@ -182,7 +182,7 @@ describe("dataframe subsetting", () => {
});
test("one row, all columns", () => {
const dfC = sourceDf.cutByList([1], null);
const dfC = sourceDf.subset([1], null);
expect(dfC).toBeDefined();
expect(dfC.dims).toEqual([1, 4]);
expect(dfC.iat(0, 0)).toEqual(1);
@@ -194,7 +194,7 @@ describe("dataframe subsetting", () => {
});
test("two rows, all columns", () => {
const dfD = sourceDf.cutByList([0, 2], null);
const dfD = sourceDf.subset([0, 2], null);
expect(dfD).toBeDefined();
expect(dfD.dims).toEqual([2, 4]);
expect(dfD.icol(0).asArray()).toEqual(new Int32Array([0, 2]));
@@ -206,7 +206,7 @@ describe("dataframe subsetting", () => {
});
test("all rows, all columns", () => {
const dfE = sourceDf.cutByList(null, null);
const dfE = sourceDf.subset(null, null);
expect(dfE).toBeDefined();
expect(dfE.dims).toEqual([3, 4]);
expect(dfE.icol(0).asArray()).toEqual(sourceDf.icol(0).asArray());
@@ -218,7 +218,7 @@ describe("dataframe subsetting", () => {
});
test("two rows, two colums", () => {
const dfF = sourceDf.cutByList([0, 2], ["int32", "float32"]);
const dfF = sourceDf.subset([0, 2], ["int32", "float32"]);
expect(dfF).toBeDefined();
expect(dfF.dims).toEqual([2, 2]);
expect(dfF.icol(0).asArray()).toEqual(new Int32Array([0, 2]));
@@ -226,9 +226,32 @@ describe("dataframe subsetting", () => {
expect(dfF.rowIndex.keys()).toEqual(new Int32Array([0, 2]));
expect(dfF.colIndex.keys()).toEqual(["int32", "float32"]);
});
test("withRowIndex", () => {
const df = sourceDf.subset(
null,
["int32", "float32"],
new Dataframe.DenseInt32Index([3, 2, 1])
);
expect(df.colIndex).toBeInstanceOf(Dataframe.KeyIndex);
expect(df.rowIndex).toBeInstanceOf(Dataframe.DenseInt32Index);
expect(df.at(3, "int32")).toEqual(df.iat(0, 0));
});
test("withRowIndex error checks", () => {
expect(() =>
sourceDf.subset(null, ["red"], new Dataframe.IdentityInt32Index(1))
).toThrow(RangeError);
expect(() =>
sourceDf.subset(null, ["red"], new Dataframe.DenseInt32Index([0, 1]))
).toThrow(RangeError);
expect(() =>
sourceDf.subset(null, ["red"], new Dataframe.KeyIndex([0, 1, 2, 3]))
).toThrow(RangeError);
});
});
test("icutByMask", () => {
test("isubsetMask", () => {
const sourceDf = new Dataframe.Dataframe(
[3, 4],
[
@@ -241,7 +264,7 @@ describe("dataframe subsetting", () => {
new Dataframe.KeyIndex(["int32", "string", "float32", "colors"])
);
const dfA = sourceDf.icutByMask(
const dfA = sourceDf.isubsetMask(
new Uint8Array([0, 1, 1]),
new Uint8Array([1, 0, 0, 1])
);
@@ -293,6 +316,222 @@ describe("dataframe factories", () => {
expect(dfB.icol(i).asArray()).toEqual(dfA.icol(i).asArray());
}
});
describe("withCol", () => {
test("KeyIndex", () => {
const df = new Dataframe.Dataframe(
[2, 2],
[["red", "blue"], [true, false]],
null,
new Dataframe.KeyIndex(["colors", "bools"])
);
const dfA = df.withCol("numbers", [1, 0]);
expect(dfA).toBeDefined();
expect(dfA.dims).toEqual([2, 3]);
expect(dfA.icol(0).asArray()).toEqual(["red", "blue"]);
expect(dfA.icol(1).asArray()).toEqual([true, false]);
expect(dfA.icol(2).asArray()).toEqual([1, 0]);
expect(dfA.col("numbers").asArray()).toEqual([1, 0]);
expect(dfA.colIndex.keys()).toEqual(["colors", "bools", "numbers"]);
expect(df.colIndex.keys()).toEqual(["colors", "bools"]);
expect(df.rowIndex.keys()).toEqual(dfA.rowIndex.keys());
});
test("DenseInt32Index", () => {
const df = new Dataframe.Dataframe(
[2, 2],
[["red", "blue"], [true, false]],
null,
new Dataframe.DenseInt32Index([74, 75])
);
const dfA = df.withCol(72, [1, 0]);
expect(dfA).toBeDefined();
expect(dfA.dims).toEqual([2, 3]);
expect(dfA.icol(0).asArray()).toEqual(["red", "blue"]);
expect(dfA.icol(1).asArray()).toEqual([true, false]);
expect(dfA.icol(2).asArray()).toEqual([1, 0]);
expect(dfA.col(74).asArray()).toEqual(["red", "blue"]);
expect(dfA.col(75).asArray()).toEqual([true, false]);
expect(dfA.col(72).asArray()).toEqual([1, 0]);
expect(dfA.colIndex.keys()).toEqual(new Int32Array([74, 75, 72]));
expect(df.colIndex.keys()).toEqual(new Int32Array([74, 75]));
expect(df.rowIndex.keys()).toEqual(dfA.rowIndex.keys());
});
test("DenseInt32Index promote", () => {
const df = new Dataframe.Dataframe(
[2, 2],
[["red", "blue"], [true, false]],
null,
new Dataframe.DenseInt32Index([74, 75])
);
const dfA = df.withCol(999, [1, 0]);
expect(dfA).toBeDefined();
expect(dfA.dims).toEqual([2, 3]);
expect(dfA.icol(0).asArray()).toEqual(["red", "blue"]);
expect(dfA.icol(1).asArray()).toEqual([true, false]);
expect(dfA.icol(2).asArray()).toEqual([1, 0]);
expect(dfA.col(74).asArray()).toEqual(["red", "blue"]);
expect(dfA.col(75).asArray()).toEqual([true, false]);
expect(dfA.col(999).asArray()).toEqual([1, 0]);
expect(dfA.colIndex.keys()).toEqual(new Int32Array([74, 75, 999]));
expect(df.colIndex.keys()).toEqual(new Int32Array([74, 75]));
expect(df.rowIndex.keys()).toEqual(dfA.rowIndex.keys());
});
test("IdentityInt32Index with last", () => {
const df = new Dataframe.Dataframe(
[2, 2],
[["red", "blue"], [true, false]],
null,
null
);
const dfA = df.withCol(2, [1, 0]);
expect(dfA).toBeDefined();
expect(dfA.dims).toEqual([2, 3]);
expect(dfA.icol(0).asArray()).toEqual(["red", "blue"]);
expect(dfA.icol(1).asArray()).toEqual([true, false]);
expect(dfA.icol(2).asArray()).toEqual([1, 0]);
expect(dfA.col(0).asArray()).toEqual(["red", "blue"]);
expect(dfA.col(1).asArray()).toEqual([true, false]);
expect(dfA.col(2).asArray()).toEqual([1, 0]);
expect(dfA.colIndex.keys()).toEqual(new Int32Array([0, 1, 2]));
expect(df.colIndex.keys()).toEqual(new Int32Array([0, 1]));
expect(df.rowIndex.keys()).toEqual(dfA.rowIndex.keys());
});
test("IdentityInt32Index promote", () => {
const df = new Dataframe.Dataframe(
[2, 2],
[["red", "blue"], [true, false]],
null,
null
);
const dfA = df.withCol(99, [1, 0]);
expect(dfA).toBeDefined();
expect(dfA.dims).toEqual([2, 3]);
expect(dfA.icol(0).asArray()).toEqual(["red", "blue"]);
expect(dfA.icol(1).asArray()).toEqual([true, false]);
expect(dfA.icol(2).asArray()).toEqual([1, 0]);
expect(dfA.col(0).asArray()).toEqual(["red", "blue"]);
expect(dfA.col(1).asArray()).toEqual([true, false]);
expect(dfA.col(99).asArray()).toEqual([1, 0]);
expect(dfA.colIndex.keys()).toEqual(new Int32Array([0, 1, 99]));
expect(df.colIndex.keys()).toEqual(new Int32Array([0, 1]));
expect(df.rowIndex.keys()).toEqual(dfA.rowIndex.keys());
});
describe("handle column dimensions correctly", () => {
/*
there are two conditions:
- empty dataframe - will accept an add of any dimensionality
- non-empty dataframe - added column must match row-count dimension
*/
test("empty.withCol", () => {
const edf = Dataframe.Dataframe.empty();
const df = edf.withCol("foo", [1, 2, 3]);
expect(edf).toBeDefined();
expect(df).toBeDefined();
expect(edf).not.toEqual(df);
expect(df.dims).toEqual([3, 1]);
expect(df.icol(0).asArray()).toEqual([1, 2, 3]);
});
test("withCol dimension check", () => {
const dfA = new Dataframe.Dataframe([1, 1], [["a"]]);
expect(() => {
dfA.withCol(1, []);
}).toThrow(RangeError);
});
});
});
describe("dropCol", () => {
test("KeyIndex", () => {
const df = new Dataframe.Dataframe(
[2, 3],
[["red", "blue"], [true, false], [1, 0]],
null,
new Dataframe.KeyIndex(["colors", "bools", "numbers"])
);
const dfA = df.dropCol("colors");
expect(dfA).toBeDefined();
expect(dfA.dims).toEqual([2, 2]);
expect(dfA.icol(0).asArray()).toEqual([true, false]);
expect(dfA.icol(1).asArray()).toEqual([1, 0]);
expect(dfA.col("numbers").asArray()).toEqual([1, 0]);
expect(dfA.colIndex.keys()).toEqual(["bools", "numbers"]);
expect(df.colIndex.keys()).toEqual(["colors", "bools", "numbers"]);
expect(df.rowIndex.keys()).toEqual(dfA.rowIndex.keys());
});
test("IdentityInt32Index drop first", () => {
const df = new Dataframe.Dataframe(
[2, 3],
[["red", "blue"], [true, false], [1, 0]],
null,
null
);
const dfA = df.dropCol(0);
expect(dfA).toBeDefined();
expect(dfA.dims).toEqual([2, 2]);
expect(dfA.icol(0).asArray()).toEqual([true, false]);
expect(dfA.icol(1).asArray()).toEqual([1, 0]);
expect(df.col(1).asArray()).toEqual(dfA.col(1).asArray());
expect(df.col(2).asArray()).toEqual(dfA.col(2).asArray());
expect(dfA.colIndex.keys()).toEqual(new Int32Array([1, 2]));
expect(df.colIndex.keys()).toEqual(new Int32Array([0, 1, 2]));
expect(df.rowIndex.keys()).toEqual(dfA.rowIndex.keys());
});
test("IdentityInt32Index drop last", () => {
const df = new Dataframe.Dataframe(
[2, 3],
[["red", "blue"], [true, false], [1, 0]],
null,
null
);
const dfA = df.dropCol(2);
expect(dfA).toBeDefined();
expect(dfA.dims).toEqual([2, 2]);
expect(dfA.icol(0).asArray()).toEqual(["red", "blue"]);
expect(dfA.icol(1).asArray()).toEqual([true, false]);
expect(df.col(0).asArray()).toEqual(dfA.col(0).asArray());
expect(df.col(1).asArray()).toEqual(dfA.col(1).asArray());
expect(dfA.colIndex.keys()).toEqual(new Int32Array([0, 1]));
expect(df.colIndex.keys()).toEqual(new Int32Array([0, 1, 2]));
expect(df.rowIndex.keys()).toEqual(dfA.rowIndex.keys());
});
test("DenseInt32Index", () => {
const df = new Dataframe.Dataframe(
[2, 3],
[["red", "blue"], [true, false], [1, 0]],
null,
new Dataframe.DenseInt32Index([102, 101, 100])
);
const dfA = df.dropCol(101);
expect(dfA).toBeDefined();
expect(dfA.dims).toEqual([2, 2]);
expect(dfA.icol(0).asArray()).toEqual(["red", "blue"]);
expect(dfA.icol(1).asArray()).toEqual([1, 0]);
expect(dfA.col(100).asArray()).toEqual([1, 0]);
expect(dfA.col(102).asArray()).toEqual(["red", "blue"]);
expect(dfA.colIndex.keys()).toEqual(new Int32Array([102, 100]));
expect(df.colIndex.keys()).toEqual(new Int32Array([102, 101, 100]));
expect(df.rowIndex.keys()).toEqual(dfA.rowIndex.keys());
});
});
});
describe("dataframe col", () => {
@@ -0,0 +1,253 @@
import * as Dataframe from "../../../src/util/dataframe";
function float32Conversion(f) {
return new Float32Array([f])[0];
}
describe("Dataframe column summary", () => {
test("empty column test", () => {
const df = Dataframe.Dataframe.create([0, 1], [[]]);
const summary = df.icol(0).summarize();
expect(summary).toEqual(
expect.objectContaining({
categorical: true,
categories: [],
categoryCounts: new Map(),
numCategories: 0
})
);
});
test("simple test", () => {
const df = 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"
])
);
expect(df.icol(0).summarize()).toEqual(
expect.objectContaining({
categorical: true,
categories: ["n1"],
categoryCounts: new Map([["n1", 1]]),
numCategories: 1
})
);
expect(df.icol(1).summarize()).toEqual(
expect.objectContaining({
categorical: true,
categories: ["hi"],
categoryCounts: new Map([["hi", 1]]),
numCategories: 1
})
);
expect(df.icol(2).summarize()).toEqual(
expect.objectContaining({
categorical: true,
categories: [true],
categoryCounts: new Map([[true, 1]]),
numCategories: 1
})
);
expect(df.icol(3).summarize()).toEqual(
expect.objectContaining({
categorical: false,
min: float32Conversion(39.3),
max: float32Conversion(39.3),
nan: 0,
ninf: 0,
pinf: 0
})
);
expect(df.icol(4).summarize()).toEqual(
expect.objectContaining({
categorical: false,
min: 99,
max: 99,
nan: 0,
ninf: 0,
pinf: 0
})
);
expect(df.icol(5).summarize()).toEqual(
expect.objectContaining({
categorical: true,
categories: [1],
categoryCounts: new Map([[1, 1]]),
numCategories: 1
})
);
});
test("multi test", () => {
const df = 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"
])
);
expect(df.icol(0).summarize()).toEqual(
expect.objectContaining({
categorical: true,
categories: expect.arrayContaining(["n0", "n1", "n2"]),
categoryCounts: new Map([["n0", 1], ["n1", 1], ["n2", 1]]),
numCategories: 3
})
);
expect(df.icol(1).summarize()).toEqual(
expect.objectContaining({
categorical: true,
categories: expect.arrayContaining(["hi", "bye"]),
categoryCounts: new Map([["hi", 2], ["bye", 1]]),
numCategories: 2
})
);
expect(df.icol(2).summarize()).toEqual(
expect.objectContaining({
categorical: true,
categories: expect.arrayContaining([true, false]),
categoryCounts: new Map([[true, 2], [false, 1]]),
numCategories: 2
})
);
expect(df.icol(3).summarize()).toEqual(
expect.objectContaining({
categorical: false,
min: 0,
max: float32Conversion(39.3),
nan: 0,
ninf: 0,
pinf: 0
})
);
expect(df.icol(4).summarize()).toEqual(
expect.objectContaining({
categorical: false,
min: 99,
max: 99,
nan: 0,
ninf: 0,
pinf: 0
})
);
expect(df.icol(5).summarize()).toEqual(
expect.objectContaining({
categorical: true,
categories: expect.arrayContaining([1, false, "0"]),
categoryCounts: new Map([[1, 1], [false, 1], ["0", 1]]),
numCategories: 3
})
);
});
test("non-finite numbers", () => {
const df = 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"
])
);
expect(df.icol(0).summarize()).toEqual(
expect.objectContaining({
categorical: true,
categories: expect.arrayContaining(["n0", "n1", "n2"]),
categoryCounts: new Map([["n0", 1], ["n1", 1], ["n2", 2]]),
numCategories: 3
})
);
expect(df.icol(1).summarize()).toEqual(
expect.objectContaining({
categorical: true,
categories: expect.arrayContaining(["hi", "bye"]),
categoryCounts: new Map([["hi", 2], ["bye", 1]]),
numCategories: 2
})
);
expect(df.icol(2).summarize()).toEqual(
expect.objectContaining({
categorical: true,
categories: expect.arrayContaining([true, false]),
categoryCounts: new Map([[true, 2], [false, 1]]),
numCategories: 2
})
);
expect(df.icol(3).summarize()).toEqual(
expect.objectContaining({
categorical: false,
min: float32Conversion(39.3),
max: float32Conversion(39.3),
nan: 1,
ninf: 1,
pinf: 1
})
);
expect(df.icol(4).summarize()).toEqual(
expect.objectContaining({
categorical: false,
min: 99,
max: 99,
nan: 0,
ninf: 0,
pinf: 0
})
);
expect(df.icol(5).summarize()).toEqual(
expect.objectContaining({
categorical: true,
categories: expect.arrayContaining([1, false, "0"]),
categoryCounts: new Map([[1, 1], [false, 1], ["0", 1]]),
numCategories: 3
})
);
});
});
@@ -1,249 +0,0 @@
import _ from "lodash";
import * as kvCache from "../../../src/util/stateManager/keyvalcache";
/*
This is PRIVATE to keyvalcache and must be kept in sync with
any changs ot that module. Need to Know - to enable error handling test
*/
const cachePrivateKey = "__kvcachekey__";
/*
helper function - promisify setTimeout()
*/
function timeout(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
describe("kvcache API", () => {
/*
test the happy path create/set/get API
*/
test("simple create", () => {
/* with defaults */
const kvc = kvCache.create();
expect(kvc).toBeDefined();
expect(kvc).toEqual(expect.objectContaining({}));
expect(kvCache.get(kvc, "test")).toBeUndefined();
/* with params */
const kvc1 = kvCache.create(/* lowWatermark */ 99, /* minTTL */ 0);
expect(kvc1).toBeDefined();
expect(kvc1).toEqual(expect.objectContaining({}));
});
test("set/get", () => {
/*
- check basic get/set functionality
- check set does not mutate source cache
*/
const keyName = "foo";
const kvc1 = kvCache.create();
expect(kvc1).toBeDefined();
expect(kvCache.get(kvc1, keyName)).toBeUndefined();
const val2 = [2];
const kvc2 = kvCache.set(kvc1, keyName, val2);
expect(kvc2).toBeDefined();
expect(kvc2).not.toBe(kvc1);
expect(kvCache.get(kvc1, keyName)).toBeUndefined();
expect(kvCache.get(kvc2, keyName)).toBe(val2);
const val3 = [3];
const kvc3 = kvCache.set(kvc2, keyName, val3);
expect(kvc3).toBeDefined();
expect(kvc3).not.toBe(kvc1);
expect(kvc3).not.toBe(kvc2);
expect(kvCache.get(kvc1, keyName)).toBeUndefined();
expect(kvCache.get(kvc2, keyName)).toBe(val2);
expect(kvCache.get(kvc3, keyName)).toBe(val3);
});
});
describe("common error handling", () => {
/*
Test common error handlers
*/
test("set() protection from namespace pollution", () => {
/*
Test that set() will not allow use of the private cache key
*/
const kvc = kvCache.create();
expect(() => {
kvCache.set(kvc, cachePrivateKey, {});
}).toThrow();
});
test("create() does not accept bogus config", () => {
expect(() => {
kvCache.create([], {});
}).toThrow();
expect(() => {
kvCache.create(-99, 0);
}).toThrow();
expect(() => {
kvCache.create(100, -1);
}).toThrow();
expect(() => {
kvCache.create(1000, "foobar");
}).toThrow();
expect(() => {
kvCache.create(null, 8);
}).toThrow();
});
});
describe("map", () => {
/*
Test kvCache.map() - create new cache that is a transformation of an
existing cache
*/
test("map of empty cache", () => {
const kvc = kvCache.create();
const callback = jest.fn();
const kvcMapped = kvCache.map(kvc, callback);
expect(callback).not.toHaveBeenCalled();
expect(kvcMapped).toBeDefined();
expect(kvcMapped).not.toBe(kvc); // immutable operation
expect(kvcMapped).toEqual(kvc);
});
test("map of non-empty cache", () => {
const key = "aKey";
const val = [0, 1, 2];
let kvc = kvCache.create();
kvc = kvCache.set(kvc, key, val);
const mockCB = jest.fn().mockImplementation(v => [...v]);
const kvcMapped = kvCache.map(kvc, mockCB);
expect(kvcMapped).toBeDefined();
expect(kvcMapped).not.toBe(kvc); // immutable operation
expect(_.isEqual(kvc, kvcMapped)).toBe(true);
expect(mockCB).toHaveBeenCalledTimes(1);
expect(mockCB).toHaveBeenLastCalledWith(val, key);
});
});
describe("flush", () => {
/*
test various cache flush behavior
*/
test("flush - lowWatermark, disable minTTL", () => {
/*
verify lowWatermark functions correctly
*/
// set lowWatermark to 2, set three times - only the final two
// should remain.
let kvc = kvCache.create(2, 0);
["a", "b", "c"].forEach(k => {
kvc = kvCache.set(kvc, k, []);
});
expect(kvc).toEqual(
expect.objectContaining({
b: expect.arrayContaining([]),
c: expect.arrayContaining([])
})
);
expect(kvc).toEqual(
expect.not.objectContaining({
a: expect.arrayContaining([])
})
);
});
test("flush - minTTL, disable lowWatermark", async () => {
/*
verify minTTL functions correctly
*/
// set minTTL to 1 ms
let kvc = kvCache.create(0, 10);
kvc = kvCache.set(kvc, "a", []);
await timeout(20);
["b", "c"].forEach(k => {
kvc = kvCache.set(kvc, k, []);
});
expect(kvc).toEqual(
expect.objectContaining({
b: expect.arrayContaining([]),
c: expect.arrayContaining([])
})
);
expect(kvc).toEqual(
expect.not.objectContaining({
a: expect.arrayContaining([])
})
);
});
test("flush - minTTL and lowWatermark", async () => {
/*
verify minTTL functions correctly
*/
// set lowwatermark to 3, minTTL to 1 ms
let kvc = kvCache.create(3, 10);
kvc = kvCache.set(kvc, "a", []);
// delay
await timeout(20);
["b", "c"].forEach(k => {
kvc = kvCache.set(kvc, k, []);
});
expect(kvc).toEqual(
expect.objectContaining({
a: expect.arrayContaining([]),
b: expect.arrayContaining([]),
c: expect.arrayContaining([])
})
);
kvc = kvCache.set(kvc, "d", []);
expect(kvc).toEqual(
expect.objectContaining({
b: expect.arrayContaining([]),
c: expect.arrayContaining([]),
d: expect.arrayContaining([])
})
);
expect(kvc).toEqual(
expect.not.objectContaining({
a: expect.arrayContaining([])
})
);
});
test("manual flush", async () => {
let kvc = kvCache.create(1, 10);
["a", "b", "c", "d"].forEach(k => {
kvc = kvCache.set(kvc, k, []);
});
// Before TTL has expired, should have all values in cache.
expect(kvc).toEqual(
expect.objectContaining({
a: expect.arrayContaining([]),
b: expect.arrayContaining([]),
c: expect.arrayContaining([])
})
);
// let TTL expire
await timeout(10);
// manually flush
const postFlushKvc = kvCache.flush(kvc);
expect(postFlushKvc).toBeDefined();
expect(postFlushKvc).not.toBe(kvc);
expect(postFlushKvc).toEqual(
expect.objectContaining({
d: expect.arrayContaining([])
})
);
});
});
@@ -1,291 +0,0 @@
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 = {
annotations: {
obs: [
{ name: "name", type: "string" },
{ name: "nameString", type: "string" },
{ name: "nameBoolean", type: "boolean" },
{ name: "nameFloat32", type: "float32" },
{ name: "nameInt32", type: "int32" },
{
name: "nameCategorical",
type: "categorical",
categories: [true, false, 1, 0, 0.00001, 4383.4833, "test", "", "0"]
}
],
var: [{ name: "name", type: "string" }]
}
};
test("empty test", () => {
const df = Dataframe.Dataframe.empty();
const summary = summarizeAnnotations(schema, df, df.clone());
expect(summary).toEqual(
expect.objectContaining({
obs: {
nameString: {
categorical: true,
categories: [],
categoryCounts: new Map(),
numCategories: 0
},
nameBoolean: {
categorical: true,
categories: [],
categoryCounts: new Map(),
numCategories: 0
},
nameFloat32: {
categorical: false,
range: {
max: undefined,
min: undefined,
nan: 0,
ninf: 0,
pinf: 0
}
},
nameInt32: {
categorical: false,
range: {
max: undefined,
min: undefined,
nan: 0,
ninf: 0,
pinf: 0
}
},
nameCategorical: {
categorical: true,
categories: [],
categoryCounts: new Map(),
numCategories: 0
}
},
var: {}
})
);
});
test("simple test", () => {
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,
obsAnnotations,
varAnnotations
);
expect(summary).toEqual(
expect.objectContaining({
obs: {
nameString: {
categorical: true,
categories: ["hi"],
categoryCounts: new Map([["hi", 1]]),
numCategories: 1
},
nameBoolean: {
categorical: true,
categories: [true],
categoryCounts: new Map([[true, 1]]),
numCategories: 1
},
nameFloat32: {
categorical: false,
range: {
min: float32Conversion(39.3),
max: float32Conversion(39.3),
nan: 0,
ninf: 0,
pinf: 0
}
},
nameInt32: {
categorical: false,
range: { min: 99, max: 99, nan: 0, ninf: 0, pinf: 0 }
},
nameCategorical: {
categorical: true,
categories: [1],
categoryCounts: new Map([[1, 1]]),
numCategories: 1
}
},
var: {}
})
);
});
test("multi test", () => {
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,
obsAnnotations,
varAnnotations
);
expect(summary).toMatchObject(
expect.objectContaining({
obs: {
nameString: {
categorical: true,
categories: expect.arrayContaining(["hi", "bye"]),
categoryCounts: new Map([["hi", 2], ["bye", 1]]),
numCategories: 2
},
nameBoolean: {
categorical: true,
categories: expect.arrayContaining([true, false]),
categoryCounts: new Map([[true, 2], [false, 1]]),
numCategories: 2
},
nameFloat32: {
categorical: false,
range: {
min: 0,
max: float32Conversion(39.3),
nan: 0,
ninf: 0,
pinf: 0
}
},
nameInt32: {
categorical: false,
range: { min: 99, max: 99, nan: 0, ninf: 0, pinf: 0 }
},
nameCategorical: {
categorical: true,
categories: expect.arrayContaining([1, false, "0"]),
categoryCounts: new Map([[1, 1], [false, 1], ["0", 1]]),
numCategories: 3
}
},
var: {}
})
);
});
test("non-finite numbers", () => {
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,
obsAnnotations,
varAnnotations
);
expect(summary).toMatchObject(
expect.objectContaining({
obs: {
nameString: {
categorical: true,
categories: expect.arrayContaining(["hi", "bye"]),
categoryCounts: new Map([["hi", 2], ["bye", 1]]),
numCategories: 2
},
nameBoolean: {
categorical: true,
categories: expect.arrayContaining([true, false]),
categoryCounts: new Map([[true, 2], [false, 1]]),
numCategories: 2
},
nameFloat32: {
categorical: false,
range: {
min: float32Conversion(39.3),
max: float32Conversion(39.3),
nan: 1,
ninf: 1,
pinf: 1
}
},
nameInt32: {
categorical: false,
range: { min: 99, max: 99, nan: 0, ninf: 0, pinf: 0 }
},
nameCategorical: {
categorical: true,
categories: expect.arrayContaining([1, false, "0"]),
categoryCounts: new Map([[1, 1], [false, 1], ["0", 1]]),
numCategories: 3
}
},
var: {}
})
);
});
});
@@ -41,15 +41,13 @@ describe("createUniverseFromResponse", () => {
expect(universe).toBeDefined();
expect(universe).toMatchObject(
expect.objectContaining({
api: "0.2",
nObs,
nVar,
schema: REST.schema.schema,
obsAnnotations: expect.any(Dataframe.Dataframe),
varAnnotations: expect.any(Dataframe.Dataframe),
obsLayout: expect.any(Dataframe.Dataframe),
summary: expect.any(Object),
varDataCache: expect.any(Object)
varData: expect.any(Dataframe.Dataframe)
})
);
@@ -63,5 +61,6 @@ describe("createUniverseFromResponse", () => {
nVar,
REST.schema.schema.annotations.var.length
]);
expect(universe.varData.isEmpty()).toBeTruthy();
});
});
@@ -8,7 +8,6 @@ import {
obsAnnoDimensionName,
layoutDimensionName
} from "../../../src/util/nameCreators";
import * as kvCache from "../../../src/util/stateManager/keyvalcache";
/*
Helper - creates universe, world, corssfilter and dimensionMap from
@@ -55,28 +54,13 @@ describe("createWorldFromEntireUniverse", () => {
expect(world).toMatchObject(
expect.objectContaining({
api: "0.2",
nObs: universe.nObs,
nVar: universe.nVar,
schema: universe.schema,
obsAnnotations: universe.obsAnnotations,
varAnnotations: universe.varAnnotations,
obsLayout: universe.obsLayout,
summary: expect.objectContaining({
obs: _(REST.schema.schema.annotations.obs)
.filter(v => v.name !== "name")
.keyBy("name")
.mapValues(() => expect.any(Object))
.value(),
var: _(REST.schema.schema.annotations.var)
.filter(v => v.name !== "name")
.keyBy("name")
.mapValues(() => expect.any(Object))
.value()
}),
varDataCache: expect.any(Object)
varData: expect.any(Dataframe.Dataframe)
})
);
});
@@ -121,18 +105,13 @@ describe("createWorldFromCurrentSelection", () => {
expect(world).toMatchObject(
expect.objectContaining({
api: "0.2",
nObs: matchingIndices.length,
nVar: universe.nVar,
schema: universe.schema,
obsAnnotations: expect.any(Dataframe.Dataframe),
varAnnotations: universe.varAnnotations,
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)
varData: expect.any(Dataframe.Dataframe)
})
);
@@ -183,60 +162,14 @@ describe("createObsDimensionMap", () => {
});
});
describe("subsetVarData", () => {
test("when world eq universe", () => {
const { universe, world } = defaultBigBang();
/* create a mock varData array for subsetting */
const sourceVarData = new Float32Array(universe.nObs);
/* expect literally the same object back */
const result = World.subsetVarData(world, universe, sourceVarData);
expect(result).toBe(sourceVarData);
});
test("when world neq universe", () => {
const { universe, world, crossfilter, dimensionMap } = defaultBigBang();
/* create a mock varData array for subsetting */
const sourceVarData = Float32Array.from(_.range(universe.nObs));
/* mock a selection */
dimensionMap[obsAnnoDimensionName("field1")].filterRange([0, 5]);
dimensionMap[obsAnnoDimensionName("field3")].filterExact(false);
/* create the world from the selection */
const newWorld = World.createWorldFromCurrentSelection(
universe,
world,
crossfilter
);
expect(newWorld.obsAnnotations.rowIndex.keys()).toEqual(
new Int32Array([0, 2])
);
/* expect a subset */
const result = World.subsetVarData(newWorld, universe, sourceVarData);
expect(result).not.toBe(sourceVarData);
expect(result).toHaveLength(newWorld.nObs);
/* check that we have expected source var content */
expect(result).toMatchObject(new Float32Array([0, 2]));
});
});
describe("createVarDimension", () => {
describe("createVarDataDimension", () => {
/* create default universe */
const { world, crossfilter } = defaultBigBang();
/* create a mock var data cache */
const varDataCache = kvCache.set(
kvCache.create(),
world.varData = world.varData.withCol(
"GENE",
Float32Array.from(_.range(world.nObs))
);
const result = World.createVarDimension(
world,
varDataCache,
crossfilter,
"GENE"
);
const result = World.createVarDataDimension(world, crossfilter, "GENE");
expect(result).toBeInstanceOf(Crossfilter.ScalarDimension);
});