TS typing for Dataframe (#2382)

* initial TS typing

* first cut at Dataframe TS typing

* more Dataframe typing

* comments

* more Dataframe cleanup

* PR review fixes and improvements
This commit is contained in:
Bruce Martin
2021-08-17 10:43:40 -07:00
committed by GitHub
parent 3fdf5cac9d
commit 45cecad76a
39 changed files with 1298 additions and 1124 deletions
+10 -18
View File
@@ -1,5 +1,6 @@
import quantile from "./quantile";
import { memoize } from "./dataframe/util";
import { Dataframe } from "./dataframe";
import { unassignedCategoryLabel } from "../globals";
import {
createCategorySummaryFromDfCol,
@@ -27,12 +28,10 @@ const getCoordinatesByLabel = (
schema: any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
categoryName: any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
categoryDf: any,
categoryDf: Dataframe,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
layoutChoice: any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
layoutDf: any
layoutDf: Dataframe
) => {
const coordsByCategoryLabel = new Map();
// If the coloredBy is not a categorical col
@@ -50,11 +49,8 @@ const getCoordinatesByLabel = (
schema.annotations.obsByName[categoryName]
);
const {
isUserAnno,
categoryValueIndices,
categoryValueCounts,
} = categorySummary;
const { isUserAnno, categoryValueIndices, categoryValueCounts } =
categorySummary;
// Iterate over all cells
for (let i = 0, len = categoryArray.length; i < len; i += 1) {
@@ -114,12 +110,10 @@ const calcMedianCentroid = (
schema: any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
categoryName: any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
categoryDf: any,
categoryDf: Dataframe,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
layoutChoice: any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
layoutDf: any
layoutDf: Dataframe
) => {
// generate a map describing the coordinates for each label within the given category
const dataMap = getCoordinatesByLabel(
@@ -159,13 +153,11 @@ const hashMedianCentroid = (
schema: any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
categoryName: any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
categoryDf: any,
categoryDf: Dataframe,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
layoutChoice: any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
layoutDf: any
) => {
layoutDf: Dataframe
): string => {
const category = categoryDf.col(categoryName);
const layoutDimNames = layoutChoice.currentDimNames;
const layoutX = layoutDf.col(layoutDimNames[0]);
+5 -8
View File
@@ -10,21 +10,18 @@ objects.
*/
import { memoize } from "./util";
import Dataframe from "./dataframe";
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
function hashDataframe(df: any) {
function hashDataframe(df: Dataframe): string {
if (df.isEmpty()) return "";
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
return df.__columnsAccessor.map((c: any) => c.__id).join(",");
return df.__columnsAccessor.map((c) => c.__id).join(",");
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
function noop(df: any) {
function noop(df: Dataframe): Dataframe {
return df;
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
const dataframeMemo = (capacity = 100) =>
const dataframeMemo = (capacity = 100): ((df: Dataframe) => Dataframe) =>
memoize(noop, hashDataframe, capacity);
export default dataframeMemo;
+249 -224
View File
@@ -1,25 +1,44 @@
import { IdentityInt32Index, isLabelIndex } from "./labelIndex";
// weird cross-dependency that we should clean up someday...
import { callOnceLazy, memoize, __getMemoId } from "./util";
import { isTypedArray, isAnyArray } from "../../common/types/arraytypes";
import {
summarizeContinuous,
isTypedArray,
isAnyArray,
AnyArray,
GenericArrayConstructor,
} from "../../common/types/arraytypes";
import { IdentityInt32Index, LabelIndex, isLabelIndex } from "./labelIndex";
import {
summarizeContinuous as _summarizeContinuous,
summarizeCategorical as _summarizeCategorical,
} from "./summarize";
import {
histogramCategorical as _histogramCategorical,
histogramCategoricalBy as _histogramCategoricalBy,
hashCategorical,
histogramContinuous,
hashCategoricalBy,
histogramContinuous as _histogramContinuous,
histogramContinuousBy as _histogramContinuousBy,
hashContinuous,
hashContinuousBy,
} from "./histogram";
import {
DataframeValue,
DataframeValueArray,
DataframeColumn,
OffsetType,
OffsetArray,
LabelType,
LabelArray,
ContinuousHistogram,
ContinuousHistogramBy,
} from "./types";
/*
Dataframe is an immutable 2D matrix similiar to Python Pandas Dataframe,
Dataframe is an immutable 2D matrix similar to Python Pandas Dataframe,
but (currently) without all of the surrounding support functions.
Data is stored in column-major layout, and each column is monomorphic.
It supports:
* Relatively efficient creation, cloning and subsetting
* Relatively efficient create, clone and subset operations
* Very efficient columnar access (eg, sum down a column), and access
to the underlying column arrays.
* Data access by row/col offset or label. Labels are reasonably well
@@ -27,10 +46,10 @@ It supports:
It does not currently support:
* Views on matrix subset - for currently known access patterns,
it is more effiicent to copy on subsetting, optimizing for access
it is more efficient to copy on subsetting, optimizing for access
speed over memory use.
* JS iterators - they are too slow. Use explicit iteration over
offest or labels.
offset or labels.
Important assumptions embedded in the API:
* Columns are implicitly categorical if they are a JS Array and numeric
@@ -67,46 +86,55 @@ dominant pattern in cellxgene.
Dataframe
**/
interface DataframeConstructor {
new (...args: ConstructorParameters<typeof Dataframe>): Dataframe;
}
export type MapColumnsCallbackFn = (
data: DataframeValueArray,
idx: number,
df: Dataframe
) => DataframeValueArray;
/** @internal */
function raiseIsNotContinuous<R = void>(): R {
throw TypeError("Column is not a continuous data type.");
}
class Dataframe {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
__columns: any;
/** @internal */
__columns: DataframeValueArray[];
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
__columnsAccessor: any;
/** @internal */
__columnsAccessor: DataframeColumn[] = [];
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
__id: any;
__id: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
colIndex: any;
colIndex: LabelIndex;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
dims: any;
dims: [number, number];
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
length: any;
length: number;
rowIndex: LabelIndex;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
rowIndex: any;
/**
Constructors & factories
**/
constructor(
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
dims: any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
columnarData: any,
rowIndex = null,
colIndex = null,
__columnsAccessor = [] // private interface
dims: [number, number],
columnarData: DataframeValueArray[],
rowIndex?: LabelIndex | null,
colIndex?: LabelIndex | null,
__columnsAccessor: (DataframeColumn | null)[] = [] // private interface
) {
/*
The base constructor is relatively hard to use - as an alternative,
see factory methods and clone/slice, below.
Parameters:
* dims - 2D array describing intendend dimensionality: [nRows,nCols].
* dims - 2D array describing intended dimensionality: [nRows,nCols].
* columnarData - JS array, nCols in length, containing array
or TypedArray of length nRows.
* rowIndex/colIndex - null (create default index using offsets as key),
@@ -121,11 +149,9 @@ class Dataframe {
throw new RangeError("Dataframe dimensions must be positive");
}
if (!rowIndex) {
// @ts-expect-error ts-migrate(2322) FIXME: Type 'IdentityInt32Index' is not assignable to typ... Remove this comment to see the full error message
rowIndex = new IdentityInt32Index(nRows);
}
if (!colIndex) {
// @ts-expect-error ts-migrate(2322) FIXME: Type 'IdentityInt32Index' is not assignable to typ... Remove this comment to see the full error message
colIndex = new IdentityInt32Index(nCols);
}
Dataframe.__errorChecks(dims, columnarData, rowIndex, colIndex);
@@ -141,17 +167,13 @@ class Dataframe {
Object.freeze(this);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
/** @internal */
static __errorChecks(
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
dims: any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
columnarData: any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
rowIndex: any,
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
colIndex: any
) {
dims: [number, number],
columnarData: AnyArray[],
rowIndex: LabelIndex,
colIndex: LabelIndex
): void | never {
const [nRows, nCols] = dims;
/* check for expected types */
@@ -189,8 +211,12 @@ class Dataframe {
}
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
static __compileColumn(column: any, getRowByOffset: any, getRowByLabel: any) {
/** @internal */
static __compileColumn(
column: DataframeValueArray,
getRowOffset: (label: LabelType) => OffsetType | -1,
getRowLabel: (offset: number) => LabelType | undefined
): DataframeColumn {
/*
Each column accessor is a function which will lookup data by
index (ie, is equivalent to dataframe.get(row, col), where 'col'
@@ -223,16 +249,17 @@ class Dataframe {
*/
const { length } = column;
const __id = __getMemoId();
const isContinuous = isTypedArray(column);
/* get value by row label */
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
const get = function get(rlabel: any) {
return column[getRowByOffset(rlabel)];
const get = function get(rlabel: LabelType): DataframeValue | undefined {
const idx = getRowOffset(rlabel);
if (idx === -1) return undefined;
return column[idx];
};
/* get value by row offset */
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
const iget = function iget(roffset: any) {
const iget = function iget(roffset: OffsetType) {
return column[roffset];
};
@@ -242,14 +269,12 @@ class Dataframe {
};
/* test for row label inclusion in column */
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
const has = function has(rlabel: any) {
const offset = getRowByOffset(rlabel);
const has = function has(rlabel: LabelType) {
const offset = getRowOffset(rlabel);
return offset >= 0 && offset < length;
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
const ihas = function ihas(offset: any) {
const ihas = function ihas(offset: OffsetType) {
return offset >= 0 && offset < length;
};
@@ -260,84 +285,88 @@ class Dataframe {
NOTE: not found return is DIFFERENT than the default Array.indexOf as
-1 is a plausible Dataframe row/col label.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
const indexOf = function indexOf(value: any) {
const offset = column.indexOf(value);
const _indexOf = function _indexOf(value: DataframeValue) {
let offset: number;
if (isTypedArray(column)) offset = column.indexOf(value as number);
else offset = column.indexOf(value);
if (offset === -1) {
return undefined;
}
return getRowByLabel(offset);
return getRowLabel(offset);
};
/*
Summarize the column data. Lazy eval, memoized
*/
const summarizeCategorical = callOnceLazy(() =>
get.summarizeCategorical = callOnceLazy(() =>
_summarizeCategorical(column)
);
const summarize = callOnceLazy(() =>
isTypedArray(column)
? summarizeContinuous(column)
: summarizeCategorical(column)
);
get.summarizeContinuous = isContinuous
? callOnceLazy(() => _summarizeContinuous(column))
: raiseIsNotContinuous;
/*
Create histogram bins for this column. Memoized.
*/
const _memoHistoCat = memoize(_histogramCategorical, hashCategorical);
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
const histogramCategorical = (by: any) => _memoHistoCat(get, by);
let histogram = null;
if (isTypedArray(column)) {
const mFn = memoize(histogramContinuous, hashContinuous);
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
histogram = (bins: any, domain: any, by: any) =>
mFn(get, bins, domain, by);
} else {
histogram = histogramCategorical;
}
get.histogramContinuous = isContinuous
? (bins: number, domain: [number, number]): ContinuousHistogram =>
memoize(_histogramContinuous, hashContinuous)(get, bins, domain)
: raiseIsNotContinuous;
get.histogramContinuousBy = isContinuous
? (
bins: number,
domain: [number, number],
by: DataframeColumn
): ContinuousHistogramBy =>
memoize(_histogramContinuousBy, hashContinuousBy)(
get,
bins,
domain,
by
)
: raiseIsNotContinuous;
get.histogramCategorical = () =>
memoize(_histogramCategorical, hashCategorical)(get);
get.histogramCategoricalBy = (by: DataframeColumn) =>
memoize(_histogramCategoricalBy, hashCategoricalBy)(get, by);
get.summarize = summarize;
get.summarizeCategorical = summarizeCategorical;
get.histogram = histogram;
get.histogramCategorical = histogramCategorical;
get.asArray = asArray;
get.has = has;
get.ihas = ihas;
get.indexOf = indexOf;
get.indexOf = _indexOf;
get.iget = iget;
get.__id = __id;
get.isContinuous = isContinuous;
Object.freeze(get);
return get;
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
__compile(accessors: any) {
/** @internal */
__compile(accessors: (DataframeColumn | null)[]): void {
/*
Compile data accessors for each column.
Use an existing accessor if provided, else compile a new one.
*/
const getRowByOffset = this.rowIndex.getOffset.bind(this.rowIndex);
const getRowByLabel = this.rowIndex.getLabel.bind(this.rowIndex);
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
this.__columnsAccessor = this.__columns.map((column: any, idx: any) => {
if (accessors[idx]) {
return accessors[idx];
const getRowOffset = this.rowIndex.getOffset.bind(this.rowIndex);
const getRowLabel = this.rowIndex.getLabel.bind(this.rowIndex);
this.__columnsAccessor = this.__columns.map(
(column, idx): DataframeColumn => {
if (accessors[idx]) {
return accessors[idx] as DataframeColumn;
}
return Dataframe.__compileColumn(column, getRowOffset, getRowLabel);
}
return Dataframe.__compileColumn(column, getRowByOffset, getRowByLabel);
});
);
Object.freeze(this.__columnsAccessor);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
clone() {
clone(): Dataframe {
/*
Clone this dataframe
*/
// @ts-expect-error ts-migrate(2351) FIXME: This expression is not constructable.
return new this.constructor(
return new (this.constructor as DataframeConstructor)(
this.dims,
[...this.__columns],
this.rowIndex,
@@ -346,8 +375,11 @@ class Dataframe {
);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
withCol(label: any, colData: any, withRowIndex = null) {
withCol(
label: LabelType,
colData: DataframeValueArray,
withRowIndex?: LabelIndex
): Dataframe {
/*
Create a new DF, which is `this` plus the new column. Example:
const newDf = df.withCol("foo", [1,2,3]);
@@ -362,11 +394,10 @@ class Dataframe {
the rowIndex from `this` will be used (ie, the rowIndex is
unchanged).
*/
let dims;
let rowIndex;
let dims: [number, number];
let rowIndex: LabelIndex | null = null;
if (this.isEmpty()) {
dims = [colData.length, 1];
rowIndex = null;
} else {
dims = [this.dims[0], this.dims[1] + 1];
({ rowIndex } = this);
@@ -380,8 +411,7 @@ class Dataframe {
columns.push(colData);
const colIndex = this.colIndex.withLabel(label);
const columnsAccessor = [...this.__columnsAccessor];
// @ts-expect-error ts-migrate(2351) FIXME: This expression is not constructable.
return new this.constructor(
return new (this.constructor as DataframeConstructor)(
dims,
columns,
rowIndex,
@@ -390,8 +420,10 @@ class Dataframe {
);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
withColsFrom(dataframe: any, labels: any) {
withColsFrom(
dataframe: Dataframe,
labels?: Record<string | number, LabelType> | LabelType[]
): Dataframe {
/*
return a new dataframe containing all columns from both `this` and the
provided dataframe argument.
@@ -417,8 +449,8 @@ class Dataframe {
*/
// resolve the source and dest label names.
let srcLabels;
let dstLabels;
let srcLabels: LabelArray;
let dstLabels: LabelArray;
if (!labels) {
// combine all columns
dstLabels = dataframe.colIndex.labels();
@@ -453,12 +485,9 @@ class Dataframe {
return dataframe;
}
// otherwise, bulid a new dataframe combining columns from both
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
const srcOffsets = srcLabels.map((l: any) =>
dataframe.colIndex.getOffset(l)
);
// otherwise, build a new dataframe combining columns from both
const srcOffsets = Array.from(dataframe.colIndex.getOffsets(srcLabels));
if (srcOffsets.some((i) => i === -1)) throw RangeError("Unknown label.");
// check for label collisions
if (dstLabels.some(this.hasCol, this)) {
@@ -466,22 +495,22 @@ class Dataframe {
}
// const dims = [this.dims[0], this.dims[1] + dataframe.dims[1]];
const dims = [this.dims[0], this.dims[1] + srcOffsets.length];
const dims: [number, number] = [
this.dims[0],
this.dims[1] + srcOffsets.length,
];
const { rowIndex } = this;
const columns = [
const columns: DataframeValueArray[] = [
...this.__columns,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
...srcOffsets.map((i: any) => dataframe.__columns[i]),
...srcOffsets.map((i) => dataframe.__columns[i]),
];
const colIndex = this.colIndex.withLabels(dstLabels);
const columnsAccessor = [
...this.__columnsAccessor,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
...srcOffsets.map((i: any) => dataframe.__columnsAccessor[i]),
...srcOffsets.map((i) => dataframe.__columnsAccessor[i]),
];
// @ts-expect-error ts-migrate(2351) FIXME: This expression is not constructable.
return new this.constructor(
return new (this.constructor as DataframeConstructor)(
dims,
columns,
rowIndex,
@@ -490,15 +519,12 @@ class Dataframe {
);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
withColsFromAll(dataframes = []) {
withColsFromAll(dataframes: Dataframe[] = []): Dataframe {
dataframes = Array.isArray(dataframes) ? dataframes : [dataframes];
// @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.
return dataframes.reduce((acc, df) => acc.withColsFrom(df), this);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
dropCol(label: any) {
dropCol(label: LabelType): Dataframe {
/*
Create a new dataframe, omitting one columns.
@@ -517,15 +543,15 @@ class Dataframe {
return Dataframe.empty();
}
const dims = [this.dims[0], this.dims[1] - 1];
const dims: [number, number] = [this.dims[0], this.dims[1] - 1];
const coffset = this.colIndex.getOffset(label);
if (coffset === -1) throw new RangeError("Unknown label.");
const columns = [...this.__columns];
columns.splice(coffset, 1);
const colIndex = this.colIndex.dropLabel(label);
const columnsAccessor = [...this.__columnsAccessor];
columnsAccessor.splice(coffset, 1);
// @ts-expect-error ts-migrate(2351) FIXME: This expression is not constructable.
return new this.constructor(
return new (this.constructor as DataframeConstructor)(
dims,
columns,
this.rowIndex,
@@ -534,12 +560,12 @@ class Dataframe {
);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
renameCol(oldLabel: any, newLabel: any) {
renameCol(oldLabel: LabelType, newLabel: LabelType): Dataframe {
/*
Accelerator for dropping a column and then adding it again with a new label
*/
const coffset = this.colIndex.getOffset(oldLabel);
if (coffset === -1) throw new RangeError("Unknown label.");
const colIndex = this.colIndex.dropLabel(oldLabel).withLabel(newLabel);
const columns = [...this.__columns];
@@ -550,8 +576,7 @@ class Dataframe {
columnsAccessor.push(columnsAccessor[coffset]);
columnsAccessor.splice(coffset, 1);
// @ts-expect-error ts-migrate(2351) FIXME: This expression is not constructable.
return new this.constructor(
return new (this.constructor as DataframeConstructor)(
this.dims,
columns,
this.rowIndex,
@@ -560,20 +585,21 @@ class Dataframe {
);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
replaceColData(label: any, newColData: any) {
replaceColData(label: LabelType, newColData: DataframeValueArray): Dataframe {
/*
Accelerator for dropping a column then adding it again with same
label and different values.
*/
const coffset = this.colIndex.getOffset(label);
if (coffset === -1) throw RangeError("Unknown column label.");
const columns = [...this.__columns];
columns[coffset] = newColData;
const columnsAccessor = [...this.__columnsAccessor];
const columnsAccessor: (DataframeColumn | null)[] = [
...this.__columnsAccessor,
];
columnsAccessor[coffset] = null;
// @ts-expect-error ts-migrate(2351) FIXME: This expression is not constructable.
return new this.constructor(
return new (this.constructor as DataframeConstructor)(
this.dims,
columns,
this.rowIndex,
@@ -582,20 +608,19 @@ class Dataframe {
);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
static empty(rowIndex = null, colIndex = null) {
const dims = [
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
static empty(rowIndex?: LabelIndex, colIndex?: LabelIndex): Dataframe {
const dims: [number, number] = [
rowIndex ? rowIndex.size() : 0,
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
colIndex ? colIndex.size() : 0,
];
if (dims[0] && dims[1]) throw new Error("not an empty dataframe");
return new Dataframe(dims, new Array(dims[1]), rowIndex, colIndex);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
static create(dims: any, columnarData: any) {
static create(
dims: [number, number],
columnarData: DataframeValueArray[]
): Dataframe {
/*
Create a dataframe from raw columnar data. All column arrays
must have the same length. Identity indexing will be used.
@@ -603,12 +628,15 @@ class Dataframe {
Example:
const df = Dataframe.create([2,2], [new Uint32Array(2), new Float32Array(2)]);
*/
return new Dataframe(dims, columnarData, null, null);
return new Dataframe(dims, columnarData);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
__subset(newRowIndex: any, newColIndex: any) {
const dims = [...this.dims];
/** @internal */
__subset(
newRowIndex: LabelIndex | null,
newColIndex: LabelIndex | null
): Dataframe {
const dims: [number, number] = [...this.dims];
/* subset columns */
let { __columns, colIndex, __columnsAccessor } = this;
@@ -617,8 +645,10 @@ class Dataframe {
__columns = new Array(colOffsets.length);
__columnsAccessor = new Array(colOffsets.length);
for (let i = 0, l = colOffsets.length; i < l; i += 1) {
__columns[i] = this.__columns[colOffsets[i]];
__columnsAccessor[i] = this.__columnsAccessor[colOffsets[i]];
const colOffset = colOffsets[i];
if (colOffset === -1) throw new RangeError("Unexpected column offset.");
__columns[i] = this.__columns[colOffset];
__columnsAccessor[i] = this.__columnsAccessor[colOffset];
}
colIndex = newColIndex;
dims[1] = colOffsets.length;
@@ -627,11 +657,14 @@ class Dataframe {
let { rowIndex } = this;
if (newRowIndex) {
const rowOffsets = this.rowIndex.getOffsets(newRowIndex.labels());
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
__columns = __columns.map((col: any) => {
const newCol = new col.constructor(rowOffsets.length);
__columns = __columns.map((col) => {
const newCol = new (col.constructor as GenericArrayConstructor<
typeof col
>)(rowOffsets.length);
for (let i = 0, l = rowOffsets.length; i < l; i += 1) {
newCol[i] = col[rowOffsets[i]];
const rowOffset = rowOffsets[i];
if (rowOffset === -1) throw new RangeError("Unexpected row offset.");
newCol[i] = col[rowOffset];
}
return newCol;
});
@@ -650,8 +683,11 @@ class Dataframe {
);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
subset(rowLabels: any, colLabels = null, withRowIndex = null) {
subset(
rowLabels: LabelArray | null,
colLabels: LabelArray | null,
withRowIndex?: LabelIndex | null
): Dataframe {
/*
Subset by row/col labels.
@@ -673,8 +709,11 @@ class Dataframe {
return this.__subset(rowIndex, colIndex);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
isubset(rowOffsets: any, colOffsets = null, withRowIndex = null) {
isubset(
rowOffsets: OffsetArray | null,
colOffsets: OffsetArray | null,
withRowIndex?: LabelIndex | null
): Dataframe {
/*
Subset by row/col offset.
@@ -683,14 +722,14 @@ class Dataframe {
indexing. If withRowIndex is a label index object, it will be used
for the new dataframe.
*/
let rowIndex = null;
let rowIndex: LabelIndex | null = null;
if (withRowIndex) {
rowIndex = withRowIndex;
} else if (rowOffsets) {
rowIndex = this.rowIndex.isubset(rowOffsets);
}
let colIndex = null;
let colIndex: LabelIndex | null = null;
if (colOffsets) {
colIndex = this.colIndex.isubset(colOffsets);
}
@@ -698,8 +737,11 @@ class Dataframe {
return this.__subset(rowIndex, colIndex);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
isubsetMask(rowMask: any, colMask = null, withRowIndex = null) {
isubsetMask(
rowMask: Uint8Array | boolean[] | null,
colMask: Uint8Array | boolean[] | null,
withRowIndex?: LabelIndex | null
): Dataframe {
/*
Subset on row/column based upon a truthy/falsey array (a mask).
@@ -711,15 +753,16 @@ class Dataframe {
const [nRows, nCols] = this.dims;
if (
(rowMask && rowMask.length !== nRows) ||
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
(colMask && colMask.length !== nCols)
) {
throw new RangeError("boolean arrays must match row/col dimensions");
}
/* convert masks to lists - method wastes space, but is fast */
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
const toList = (mask: any, maxSize: any) => {
const toList = (
mask: Uint8Array | boolean[] | null | undefined,
maxSize: number
) => {
if (!mask) {
return null;
}
@@ -735,7 +778,6 @@ class Dataframe {
};
const rowOffsets = toList(rowMask, nRows);
const colOffsets = toList(colMask, nCols);
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'Int32Array | null' is not assign... Remove this comment to see the full error message
return this.isubset(rowOffsets, colOffsets, withRowIndex);
}
@@ -743,14 +785,12 @@ class Dataframe {
Data access with row/col.
**/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
columns() {
columns(): DataframeColumn[] {
/* return all column accessors as an array, in offset order */
return [...this.__columnsAccessor];
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
col(columnLabel: any) {
col(columnLabel: LabelType): DataframeColumn {
/*
Return accessor bound to a column. Allows random row access
based upon the row indexing. Returns undefined if the
@@ -767,39 +807,44 @@ class Dataframe {
See __compile() for the functions available in a column accessor.
*/
const coff = this.colIndex.getOffset(columnLabel);
if (coff === -1) throw RangeError("Unknown label.");
return this.__columnsAccessor[coff];
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
icol(columnOffset: any) {
icol(columnOffset: OffsetType): DataframeColumn {
/*
Return column accessor by offset.
*/
return Number.isInteger(columnOffset)
? this.__columnsAccessor[columnOffset]
: undefined;
if (
Number.isInteger(columnOffset) &&
columnOffset >= 0 &&
columnOffset < this.__columnsAccessor.length
) {
return this.__columnsAccessor[columnOffset];
}
throw new RangeError("Unknown offset.");
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
at(r: any, c: any) {
at(r: LabelType, c: LabelType): DataframeValue {
/*
Access a single value, for a row/col label pair.
For performance reasons, there are no bounds or existance
For performance reasons, there are no bounds or existence
checks on labels, and no defined behavior when these are supplied.
May return undefined, throw an Error, or do something else for
non-existant labels. If you want predictable out-of-bounds
non-existent labels. If you want predictable out-of-bounds
behavior, use has(), eg,
const myVal = df.has(r,l) ? df.at(r,l) : undefined;
*/
const coff = this.colIndex.getOffset(c);
const roff = this.rowIndex.getOffset(r);
if (coff === undefined || roff === undefined)
throw new RangeError("Unknown row or column label.");
return this.__columns[coff][roff];
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
iat(r: any, c: any) {
iat(r: OffsetType, c: OffsetType): DataframeValue {
/*
Access a single value, for a row/col offset (integer) position.
@@ -809,11 +854,12 @@ class Dataframe {
const myVal = df.ihas(r, c) ? df.iat(r, c) : undefined;
*/
return this.__columns[c][r];
if (c >= 0 && c < this.dims[1] && r >= 0 && r < this.dims[0])
return this.__columns[c][r];
throw new RangeError("Unknown row or column index.");
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
has(r: any, c: any) {
has(r: LabelType, c: LabelType): boolean {
/*
Test if row/col labels exist in the dataframe - returns true/false
*/
@@ -823,8 +869,7 @@ class Dataframe {
return coff >= 0 && coff < nCols && roff >= 0 && roff < nRows;
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
ihas(r: any, c: any) {
ihas(r: number, c: number): boolean {
/*
Test if row/col offset (integer) position exists in the
dataframe - returns true/false
@@ -840,16 +885,23 @@ class Dataframe {
);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
hasCol(c: any) {
hasCol(c: LabelType): boolean {
/*
Test if col label exists - return true/false
*/
return !!this.col(c);
const coff = this.colIndex.getOffset(c);
return coff !== -1;
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
isEmpty() {
ihasCol(i: number): boolean {
/*
Test if col offset exists - return true/false
*/
const [, nCols] = this.dims;
return i >= 0 && i < nCols;
}
isEmpty(): boolean {
/*
Return true if this is an empty dataframe, ie, has dimensions [0,0]
*/
@@ -864,24 +916,20 @@ class Dataframe {
add these as useful.
****/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
mapColumns(callback: any) {
mapColumns(callback: MapColumnsCallbackFn): Dataframe {
/*
map all columns in the dataframe, returning a new dataframe comprised of the
return values, with the same index as the original dataframe.
callback MUST not modify the column, but instead return a mutated copy.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
const columns = this.__columns.map((colData: any, colIdx: any) =>
const columns = this.__columns.map((colData, colIdx) =>
callback(colData, colIdx, this)
);
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
const columnsAccessor = columns.map((c: any, idx: any) =>
this.__columns[idx] === c ? this.__columnsAccessor[idx] : undefined
const columnsAccessor: (DataframeColumn | null)[] = columns.map((c, idx) =>
this.__columns[idx] === c ? this.__columnsAccessor[idx] : null
);
// @ts-expect-error ts-migrate(2351) FIXME: This expression is not constructable.
return new this.constructor(
return new (this.constructor as DataframeConstructor)(
this.dims,
columns,
this.rowIndex,
@@ -889,29 +937,6 @@ class Dataframe {
columnsAccessor
);
}
/*
Map & reduce of column or row
TODO remainder of map/reduce functions: mapCol, mapRow, reduceRow, ...
*/
/* comment out until we have a use for this
reduceCol(clabel, callback, initialValue) {
const coff = this.colIndex.getOffset(clabel);
const column = this.__columns[coff];
let start = 0;
let acc = initialValue;
if (initialValue === undefined) {
acc = column[0];
start = 1;
}
for (let i = start, l = column.length; i < l; i += 1) {
acc = callback(acc, column[i]);
}
return acc;
}
*/
}
export default Dataframe;
+50 -70
View File
@@ -1,16 +1,27 @@
/*
Dataframe histogram
*/
import { isTypedArray } from "../../common/types/arraytypes";
import { NumberArray } from "../../common/types/arraytypes";
import {
DataframeColumn,
ContinuousHistogram,
ContinuousHistogramBy,
CategoricalHistogram,
CategoricalHistogramBy,
} from "./types";
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
function _histogramContinuous(column: any, bins: any, min: any, max: any) {
export function histogramContinuous(
column: DataframeColumn,
bins: number,
domain: [number, number]
): ContinuousHistogram {
const valBins = new Array(bins).fill(0);
if (!column) {
return valBins;
}
const [min, max] = domain;
const binWidth = (max - min) / bins;
const colArray = column.asArray();
const colArray: NumberArray = column.asArray() as NumberArray;
for (let r = 0, len = colArray.length; r < len; r += 1) {
const val = colArray[r];
if (val <= max && val >= min) {
@@ -22,25 +33,20 @@ function _histogramContinuous(column: any, bins: any, min: any, max: any) {
return valBins;
}
function _histogramContinuousBy(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
column: any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
bins: any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
min: any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
max: any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
by: any
) {
export function histogramContinuousBy(
column: DataframeColumn,
bins: number,
domain: [number, number],
by: DataframeColumn
): ContinuousHistogramBy {
const byMap = new Map();
if (!column || !by) {
return byMap;
}
const [min, max] = domain;
const binWidth = (max - min) / bins;
const byArray = by.asArray();
const colArray = column.asArray();
const colArray = column.asArray() as NumberArray;
for (let r = 0, len = colArray.length; r < len; r += 1) {
const byBin = byArray[r];
let valBins = byMap.get(byBin);
@@ -58,8 +64,9 @@ function _histogramContinuousBy(
return byMap;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
function _histogramCategorical(column: any) {
export function histogramCategorical(
column: DataframeColumn
): CategoricalHistogram {
const valMap = new Map();
if (!column) {
return valMap;
@@ -76,8 +83,10 @@ function _histogramCategorical(column: any) {
return valMap;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
function _histogramCategoricalBy(column: any, by: any) {
export function histogramCategoricalBy(
column: DataframeColumn,
by: DataframeColumn
): CategoricalHistogramBy {
const byMap = new Map();
if (!column || !by) {
return byMap;
@@ -101,67 +110,38 @@ function _histogramCategoricalBy(column: any, by: any) {
return byMap;
}
/*
Count category occupancy. Optional group-by category.
*/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
export function histogramCategorical(column: any, by: any) {
if (by && isTypedArray(by)) {
throw new Error("Group by column must be categorical");
}
return by
? _histogramCategoricalBy(column, by)
: _histogramCategorical(column);
}
/*
Memoization hash for histogramCategorical()
*/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
export function hashCategorical(column: any, by: any) {
if (by) {
return `${column.__id}:${by.__id}`;
}
export function hashCategorical(column: DataframeColumn): string {
return `${column.__id}:`;
}
/*
Bin counts for continuous/scalar values, with optional group-by category.
Values outside domain are ignored.
*/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
export function histogramContinuous(
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
column: any,
bins = 40,
domain = [0, 1],
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
by: any
) {
if (by && isTypedArray(by)) {
throw new Error("Group by column must be categorical");
}
const [min, max] = domain;
return by
? _histogramContinuousBy(column, bins, min, max, by)
: _histogramContinuous(column, bins, min, max);
export function hashCategoricalBy(
column: DataframeColumn,
by: DataframeColumn
): string {
return `${column.__id}:${by.__id}`;
}
/*
Memoization hash for histogramContinuous
*/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
export function hashContinuous(
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
column: any,
bins = "",
domain = [0, 0],
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
by: any
) {
column: DataframeColumn,
bins: number,
domain: [number, number]
): string {
const [min, max] = domain;
if (by) {
return `${column.__id}:${bins}:${min}:${max}:${by.__id}`;
}
return `${column.__id}::${bins}:${min}:${max}`;
}
export function hashContinuousBy(
column: DataframeColumn,
bins: number,
domain: [number, number],
by: DataframeColumn
): string {
const [min, max] = domain;
return `${column.__id}:${bins}:${min}:${max}:${by.__id}`;
}
+13
View File
@@ -6,3 +6,16 @@ export {
isLabelIndex,
} from "./labelIndex";
export { default as dataframeMemo } from "./cache";
export type {
LabelType,
DataframeValue,
DataframeValueArray,
DataframeColumn,
ContinuousHistogram,
ContinuousHistogramBy,
CategoricalHistogram,
CategoricalHistogramBy,
ContinuousColumnSummary,
CategoricalColumnSummary,
} from "./types";
export type { LabelIndex } from "./labelIndex";
+234 -240
View File
@@ -1,4 +1,5 @@
/* eslint-disable max-classes-per-file -- Classes are interrelated*/
/**
Label indexing - map a label to & from an integer offset. See Dataframe
for how this is used.
@@ -6,45 +7,78 @@ for how this is used.
import { rangeFill as fillRange } from "../range";
import { __getMemoId } from "./util";
import { OffsetArray, LabelType, LabelArray, GenericLabelArray } from "./types";
/*
Private utility functions
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
function extent(tarr: any) {
let min = 0x7fffffff;
let max = ~min; // eslint-disable-line no-bitwise -- Establishes 0 of same size
for (let i = 0, l = tarr.length; i < l; i += 1) {
const v = tarr[i];
if (v < min) {
min = v;
}
if (v > max) {
max = v;
}
export abstract class LabelIndexBase {
readonly __id: string; // memoization helper
constructor(id: string) {
this.__id = id;
}
return [min, max];
abstract labels(): LabelArray;
/**
* Look up the offset for the label.
*
* @param label - label to look up
* @returns - offset number or -1 if not found.
*/
abstract getOffset(label: LabelType): number;
getOffsets(labels: LabelArray): Int32Array {
// labels to offsets
const result = new Int32Array(labels.length);
for (let i = 0; i < labels.length; i += 1) {
result[i] = this.getOffset(labels[i]);
}
return result;
}
/**
* Look up the label for the offset.
*
* @param offset - offset to look up
* @returns - label or undefined if not found.
*/
abstract getLabel(offset: number): LabelType | undefined;
getLabels(offsets: OffsetArray): (LabelType | undefined)[] {
// offsets to labels
const result = new Array(offsets.length);
for (let i = 0; i < offsets.length; i += 1) {
result[i] = this.getLabel(offsets[i]);
}
return result;
}
abstract size(): number;
abstract subset(labels: LabelArray): LabelIndexBase;
abstract isubset(offsets: OffsetArray): LabelIndexBase;
abstract isubsetMask(mask: Uint8Array | boolean[]): LabelIndexBase;
abstract withLabel(label: LabelType): LabelIndexBase;
abstract withLabels(labels: LabelArray): LabelIndexBase;
abstract dropLabel(label: LabelType): LabelIndexBase;
}
class IdentityInt32Index {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
maxOffset: any;
export class IdentityInt32Index extends LabelIndexBase {
readonly maxOffset: number;
/*
identity/noop index, with small assumptions that labels are int32
*/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
constructor(maxOffset: any) {
constructor(maxOffset: number) {
super(`IdentityInt32Index_${maxOffset}`);
this.maxOffset = maxOffset;
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
get __id() {
return `IdentityInt32Index_${this.maxOffset}`;
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
labels() {
labels(): LabelArray {
// memoize
const k = fillRange(new Int32Array(this.maxOffset));
this.labels = function labels() {
@@ -53,77 +87,79 @@ class IdentityInt32Index {
return k;
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
getOffset(i: any) {
getOffset(label: LabelType): number {
// label to offset
return Number.isInteger(i) && i >= 0 && i < this.maxOffset ? i : undefined;
return Number.isInteger(label) && label >= 0 && label < this.maxOffset
? (label as number)
: -1;
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
getOffsets(arr: any) {
// labels to offsets
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
return arr.map((i: any) => this.getOffset(i));
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
getLabel(i: any) {
getLabel(offset: number): number | undefined {
// offset to label
return Number.isInteger(i) && i >= 0 && i < this.maxOffset ? i : undefined;
return Number.isInteger(offset) && offset >= 0 && offset < this.maxOffset
? offset
: undefined;
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
getLabels(arr: any) {
// offsets to labels
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
return arr.map((i: any) => this.getLabel(i));
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
size() {
size(): number {
return this.maxOffset;
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
__promote(labelArray: any) {
/** @internal */
__promote(labelArray: LabelArray, allInts: boolean): LabelIndexBase {
/*
time/space decision - based on the resulting density
*/
const [minLabel, maxLabel] = extent(labelArray);
if (minLabel === 0 && maxLabel === labelArray.length - 1)
return new IdentityInt32Index(labelArray.length);
if (labelArray.length === 0) return new KeyIndex(Array.from(labelArray));
if (allInts) {
const [minLabel, maxLabel] = extent(
labelArray as GenericLabelArray<number> // safe, as allInts is true
);
if (minLabel === 0 && maxLabel === labelArray.length - 1)
return new IdentityInt32Index(labelArray.length);
const labelSpaceSize = maxLabel - minLabel + 1;
const density = labelSpaceSize / this.maxOffset;
/* 0.1 is a magic number, that needs testing to optimize */
if (density < 0.1) {
return new KeyIndex(labelArray);
const labelSpaceSize = maxLabel - minLabel + 1;
const density = labelSpaceSize / this.maxOffset;
/* 0.1 is a magic number which needs testing to optimize */
if (density < 0.1) {
return new KeyIndex(Array.from(labelArray));
}
return new DenseInt32Index(labelArray as GenericLabelArray<number>, [
minLabel,
maxLabel,
]);
}
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'number[]' is not assignable to p... Remove this comment to see the full error message
return new DenseInt32Index(labelArray, [minLabel, maxLabel]);
return new KeyIndex(Array.from(labelArray));
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
subset(labels: any) {
subset(labels: LabelArray): LabelIndexBase {
/* validate subset */
const { maxOffset } = this;
for (let i = 0, l = labels.length; i < l; i += 1) {
const label = labels[i];
if (!Number.isInteger(label) || label < 0 || label >= maxOffset)
throw new RangeError(`offset or label: ${label}`);
throw new RangeError(`label: ${label}`);
}
return this.__promote(labels);
return this.__promote(labels, true);
}
/* identity index - labels are offsets */
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
isubset(offsets: any) {
return this.subset(offsets);
isubset(offsets: OffsetArray): LabelIndexBase {
/* validate isubset */
const { maxOffset } = this;
for (let i = 0, l = offsets.length; i < l; i += 1) {
const offset = offsets[i];
if (!Number.isInteger(offset) || offset < 0 || offset >= maxOffset)
throw new RangeError(`offset: ${offset}`);
}
if (!(offsets instanceof Int32Array)) {
offsets = new Int32Array(offsets);
}
return this.__promote(offsets, true);
}
/* identity index - labels are offsets */
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
isubsetMask(mask: any) {
isubsetMask(mask: Uint8Array | boolean[]): LabelIndexBase {
let count = 0;
if (mask.length !== this.maxOffset) {
throw new RangeError("mask has invalid length for index");
@@ -139,54 +175,42 @@ class IdentityInt32Index {
return this.subset(labels);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
withLabel(label: any) {
withLabel(label: LabelType): LabelIndexBase {
if (label === this.maxOffset) {
return new IdentityInt32Index(label + 1);
}
return this.__promote([...this.labels(), label]);
return this.__promote([...this.labels(), label], Number.isInteger(label));
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
withLabels(labels: any) {
return this.__promote([...this.labels(), ...labels]);
withLabels(labels: LabelArray): LabelIndexBase {
return this.__promote(
[...this.labels(), ...labels],
labels.every(Number.isInteger)
);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
dropLabel(label: any) {
dropLabel(label: LabelType): LabelIndexBase {
if (!Number.isInteger(label) || label < 0 || label > this.maxOffset - 1)
throw new RangeError("Invalid label.");
if (label === this.maxOffset - 1) {
return new IdentityInt32Index(label);
}
const labelArray = [...this.labels()];
labelArray.splice(labelArray.indexOf(label), 1);
return this.__promote(labelArray);
labelArray.splice(labelArray.indexOf(label as number), 1);
return this.__promote(labelArray, true);
}
}
class DenseInt32Index {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
__id: any;
export class DenseInt32Index extends LabelIndexBase {
getLabel: (offset: number) => number | undefined;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
getLabel: any;
getOffset: (label: LabelType) => number;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
getLabels: any;
index: Int32Array;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
getOffset: any;
minLabel: number;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
getOffsets: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
index: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
minLabel: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
rindex: any;
rindex: Int32Array;
/*
DenseInt32Index indexes integer labels, and uses Int32Array typed arrays
@@ -194,17 +218,16 @@ class DenseInt32Index {
of the forward index labels must be known a priori (so that the index
array can be pre-allocated).
*/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
constructor(labels: any, labelRange = null) {
if (labels.constructor !== Int32Array) {
labels = new Int32Array(labels);
}
constructor(
labels: GenericLabelArray<number>,
labelRange?: [number, number]
) {
super(__getMemoId());
const int32Labels =
labels instanceof Int32Array ? labels : new Int32Array(labels);
if (!labelRange) {
// @ts-expect-error ts-migrate(2322) FIXME: Type 'number[]' is not assignable to type 'null'.
labelRange = extent(labels);
labelRange = extent(int32Labels);
}
// @ts-expect-error ts-migrate(2488) FIXME: Type 'null' must have a '[Symbol.iterator]()' meth... Remove this comment to see the full error message
const [minLabel, maxLabel] = labelRange;
const labelSpaceSize = maxLabel - minLabel + 1;
const index = new Int32Array(labelSpaceSize).fill(-1);
@@ -214,82 +237,70 @@ class DenseInt32Index {
}
this.minLabel = minLabel;
this.rindex = labels;
this.rindex = int32Labels;
this.index = index;
this.__id = __getMemoId();
this.__compile();
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
__compile() {
const { minLabel, index, rindex } = this;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
this.getOffset = function getOffset(l: any) {
if (!Number.isInteger(l)) return undefined;
const offset = index[l - minLabel];
return offset === -1 ? undefined : offset;
this.getOffset = function getOffset(label: LabelType) {
if (!Number.isInteger(label)) return -1;
const lblIdx: number = <number>label - minLabel;
if (lblIdx < 0 || lblIdx >= index.length) return -1;
const offset = index[lblIdx];
return offset;
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
this.getOffsets = function getOffsets(arr: any) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
return arr.map((i: any) => this.getOffset(i));
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
this.getLabel = function getLabel(i: any) {
return Number.isInteger(i) ? rindex[i] : undefined;
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
this.getLabels = function getLabels(arr: any) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
return arr.map((i: any) => this.getLabel(i));
this.getLabel = function getLabel(offset: number) {
return Number.isInteger(offset) ? labels[offset] : undefined;
};
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
labels() {
labels(): LabelArray {
return this.rindex;
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
size() {
size(): number {
return this.rindex.length;
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
__promote(labelArray: any) {
/** @internal */
__promote(labelArray: LabelArray, allInts: boolean): LabelIndexBase {
/*
time/space decision - if we are going to use less than 10% of the
dense index space, switch to a KeyIndex (which is slower, but uses
less memory for sparse label spaces).
*/
const [minLabel, maxLabel] = extent(labelArray);
const labelSpaceSize = maxLabel - minLabel + 1;
const density = labelSpaceSize / this.rindex.length;
/* 0.1 is a magic number, that needs testing to optimize */
if (density < 0.1) {
return new KeyIndex(labelArray);
if (labelArray.length === 0) return new KeyIndex(Array.from(labelArray));
if (allInts) {
if (!(labelArray instanceof Int32Array)) {
labelArray = new Int32Array(labelArray as number[]);
}
const [minLabel, maxLabel] = extent(
labelArray as GenericLabelArray<number> // safe, as allInts is true
);
const labelSpaceSize = maxLabel - minLabel + 1;
const density = labelSpaceSize / this.rindex.length;
/* 0.1 is a magic number, that needs testing to optimize */
if (density < 0.1) {
return new KeyIndex(Array.from(labelArray));
}
return new DenseInt32Index(labelArray as GenericLabelArray<number>, [
minLabel,
maxLabel,
]);
}
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'number[]' is not assignable to p... Remove this comment to see the full error message
return new DenseInt32Index(labelArray, [minLabel, maxLabel]);
return new KeyIndex(Array.from(labelArray));
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
subset(labels: any) {
subset(labels: LabelArray): LabelIndexBase {
/* validate subset */
for (let i = 0, l = labels.length; i < l; i += 1) {
const label = labels[i];
const offset = this.getOffset(label);
if (offset === undefined || offset === -1)
throw new RangeError(`unknown label: ${label}`);
const label = labels[i]; // if not a number, getOffset will error
const offset = this.getOffset(label as number);
if (offset === -1) throw new RangeError(`unknown label: ${label}`);
}
return this.__promote(labels);
return this.__promote(labels as GenericLabelArray<number>, true);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
isubset(offsets: any) {
isubset(offsets: OffsetArray): LabelIndexBase {
/* validate subset */
const { rindex } = this;
const maxOffset = rindex.length;
@@ -300,11 +311,10 @@ class DenseInt32Index {
throw new RangeError(`out of bounds offset: ${offset}`);
labels[i] = rindex[offset];
}
return this.__promote(labels);
return this.__promote(labels, true);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
isubsetMask(mask: any) {
isubsetMask(mask: Uint8Array | boolean[]): LabelIndexBase {
const { rindex } = this;
if (mask.length !== rindex.length)
throw new RangeError("mask has invalid length for index");
@@ -317,62 +327,55 @@ class DenseInt32Index {
}
}
labels = labels.slice(0, count);
return this.__promote(labels);
return this.__promote(labels, true);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
withLabel(label: any) {
return this.__promote([...this.labels(), label]);
withLabel(label: LabelType): LabelIndexBase {
return this.__promote([...this.labels(), label], Number.isInteger(label));
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
withLabels(labels: any) {
return this.__promote([...this.labels(), ...labels]);
withLabels(labels: LabelArray): LabelIndexBase {
return this.__promote(
[...this.labels(), ...labels],
labels.every(Number.isInteger)
);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
dropLabel(label: any) {
dropLabel(label: LabelType): LabelIndexBase {
if (!Number.isInteger(label)) throw new RangeError("Invalid label.");
const labelArray = [...this.labels()];
labelArray.splice(labelArray.indexOf(label), 1);
return this.__promote(labelArray);
labelArray.splice(labelArray.indexOf(label as number), 1);
return this.__promote(
new Int32Array(labelArray as GenericLabelArray<number>),
true
);
}
}
class KeyIndex {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
__id: any;
export class KeyIndex extends LabelIndexBase {
getLabel: (offset: number) => LabelType | undefined;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
getLabel: any;
getOffset: (label: LabelType) => number | -1;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
getLabels: any;
index: Map<string | number, number>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
getOffset: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
getOffsets: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
index: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
rindex: any;
rindex: (string | number)[];
/*
KeyIndex indexes arbitrary JS primitive types, and uses a Map()
as its core data structure.
*/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
constructor(labels: any) {
const index = new Map();
constructor(labels: Array<string | number>) {
super(__getMemoId());
const index = new Map<string | number, number>();
if (labels === undefined) {
labels = [];
}
if (!Array.isArray(labels)) {
labels = Array.from(labels);
}
const rindex = labels;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
labels.forEach((v: any, i: any) => {
labels.forEach((v, i) => {
index.set(v, i);
});
@@ -383,48 +386,27 @@ class KeyIndex {
this.index = index;
this.rindex = rindex;
this.__id = __getMemoId();
this.__compile();
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
__compile() {
const { index, rindex } = this;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
this.getOffset = function getOffset(k: any) {
return index.get(k);
this.getOffset = function getOffset(label: LabelType) {
const offset = index.get(label);
if (offset === undefined) return -1;
return offset;
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
this.getOffsets = function getOffsets(arr: any) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
return arr.map((l: any) => this.getOffset(l));
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
this.getLabel = function getLabel(i: any) {
return Number.isInteger(i) ? rindex[i] : undefined;
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
this.getLabels = function getLabels(arr: any) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
return arr.map((i: any) => this.getLabel(i));
this.getLabel = function getLabel(offset: number) {
return Number.isInteger(offset) ? rindex[offset] : undefined;
};
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
labels() {
labels(): LabelArray {
return this.rindex;
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
size() {
size(): number {
return this.rindex.length;
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
subset(labels: any) {
subset(labels: (string | number)[]): LabelIndexBase {
/* validate subset */
for (let i = 0, l = labels.length; i < l; i += 1) {
const label = labels[i];
@@ -437,8 +419,7 @@ class KeyIndex {
return new KeyIndex(labels);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
isubset(offsets: any) {
isubset(offsets: OffsetArray): LabelIndexBase {
const { rindex } = this;
const maxOffset = rindex.length;
const labels = new Array(offsets.length);
@@ -452,8 +433,7 @@ class KeyIndex {
return new KeyIndex(labels);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
isubsetMask(mask: any) {
isubsetMask(mask: Uint8Array | boolean[]): LabelIndexBase {
const { rindex } = this;
if (mask.length !== rindex.length)
throw new RangeError("mask has invalid length for index");
@@ -469,18 +449,15 @@ class KeyIndex {
return new KeyIndex(labels);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
withLabel(label: any) {
withLabel(label: LabelType): LabelIndexBase {
return new KeyIndex([...this.rindex, label]);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
withLabels(labels: any) {
withLabels(labels: LabelArray): LabelIndexBase {
return new KeyIndex([...this.rindex, ...labels]);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
dropLabel(label: any) {
dropLabel(label: LabelType): LabelIndexBase {
const idx = this.rindex.indexOf(label);
const labelArray = [...this.rindex];
labelArray.splice(idx, 1);
@@ -488,14 +465,31 @@ class KeyIndex {
}
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
function isLabelIndex(i: any) {
export type LabelIndex = LabelIndexBase;
export function isLabelIndex(i: unknown): i is LabelIndex {
return (
i instanceof LabelIndexBase ||
i instanceof IdentityInt32Index ||
i instanceof DenseInt32Index ||
i instanceof KeyIndex
);
}
export { DenseInt32Index, IdentityInt32Index, KeyIndex, isLabelIndex };
/** @internal */
function extent(tarr: GenericLabelArray<number>): [number, number] {
let min = 0x7fffffff;
let max = ~min; // eslint-disable-line no-bitwise -- Establishes 0 of same size
for (let i = 0, l = tarr.length; i < l; i += 1) {
const v = tarr[i];
if (v < min) {
min = v;
}
if (v > max) {
max = v;
}
}
return [min, max];
}
/* eslint-enable max-classes-per-file -- enable*/
+40 -40
View File
@@ -5,55 +5,56 @@ TODO / XXX: for scalar/continuous data, this uses a naive method
of computing quantiles. Would be good to switch from sort to
partition at some point.
*/
import {
AnyArray,
GenericArrayConstructor,
} from "../../common/types/arraytypes";
import { ContinuousColumnSummary, CategoricalColumnSummary } from "./types";
import quantile from "../quantile";
import { sortArray } from "../typedCrossfilter/sort";
// [ 0, 0.01, 0.02, ..., 1.0]
// @ts-expect-error ts-migrate(6133) FIXME: 'v' is declared but its value is never read.
const centileNames = new Array(101).fill(0).map((v, idx) => idx / 100);
const centileNames = new Array(101).fill(0).map((_v, idx) => idx / 100);
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
export function summarizeContinuous(col: any) {
let min;
let max;
export function summarizeContinuous(col: AnyArray): ContinuousColumnSummary {
let nan = 0;
let pinf = 0;
let ninf = 0;
let percentiles;
if (col) {
// -Inf < finite < Inf < NaN
const sortedCol = sortArray(new col.constructor(col));
// count non-finites, which are at each end of sorted data
for (let i = sortedCol.length - 1; i >= 0; i -= 1) {
if (!Number.isNaN(sortedCol[i])) {
nan = sortedCol.length - i - 1;
break;
}
}
for (let i = 0, l = sortedCol.length; i < l; i += 1) {
if (sortedCol[i] !== Number.NEGATIVE_INFINITY) {
ninf = i;
break;
}
}
for (let i = sortedCol.length - nan - 1; i >= 0; i -= 1) {
if (sortedCol[i] !== Number.POSITIVE_INFINITY) {
pinf = sortedCol.length - i - nan - 1;
break;
}
}
// -Inf < finite < Inf < NaN
const sortedCol = sortArray(
new (col.constructor as GenericArrayConstructor<typeof col>)(col)
);
// compute percentiles on finite data ONLY
const sortedColFiniteOnly = sortedCol.slice(
ninf,
sortedCol.length - nan - pinf
);
percentiles = quantile(centileNames, sortedColFiniteOnly, true);
min = percentiles[0];
max = percentiles[100];
// count non-finites, which are at each end of sorted data
for (let i = sortedCol.length - 1; i >= 0; i -= 1) {
if (!Number.isNaN(sortedCol[i])) {
nan = sortedCol.length - i - 1;
break;
}
}
for (let i = 0, l = sortedCol.length; i < l; i += 1) {
if (sortedCol[i] !== Number.NEGATIVE_INFINITY) {
ninf = i;
break;
}
}
for (let i = sortedCol.length - nan - 1; i >= 0; i -= 1) {
if (sortedCol[i] !== Number.POSITIVE_INFINITY) {
pinf = sortedCol.length - i - nan - 1;
break;
}
}
// compute percentiles on finite data ONLY
const sortedColFiniteOnly = sortedCol.slice(
ninf,
sortedCol.length - nan - pinf
);
const percentiles = quantile(centileNames, sortedColFiniteOnly, true);
const min = percentiles[0];
const max = percentiles[100];
return {
categorical: false,
min,
@@ -65,8 +66,7 @@ export function summarizeContinuous(col: any) {
};
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
export function summarizeCategorical(col: any) {
export function summarizeCategorical(col: AnyArray): CategoricalColumnSummary {
const categoryCounts = new Map();
if (col) {
for (let r = 0, l = col.length; r < l; r += 1) {
+139
View File
@@ -0,0 +1,139 @@
import { TypedArray } from "../../common/types/arraytypes";
export type LabelType = number | string;
type CommonProps<A, B> = {
[K in keyof A & keyof B]: A[K] | B[K];
};
export type GenericLabelArray<T> = CommonProps<Array<T>, Int32Array>;
export type LabelArray = GenericLabelArray<number | string>;
export type OffsetType = number;
export type OffsetArray =
| Int8Array
| Uint8Array
| Int16Array
| Uint16Array
| Int32Array
| Uint32Array
| number[];
export type ContinuousColumnSummary = {
categorical: false;
min: number;
max: number;
nan: number;
pinf: number;
ninf: number;
percentiles: number[];
};
export type CategoricalColumnSummary = {
categorical: true;
categories: (number | string | boolean)[];
categoryCounts: Map<number | string | boolean, number>;
numCategories: number;
};
export type ColumnSummary = ContinuousColumnSummary | CategoricalColumnSummary;
export type ContinuousHistogram = number[];
export type ContinuousHistogramBy = Map<DataframeValue, ContinuousHistogram>;
export type CategoricalHistogram = Map<DataframeValue, number>;
export type CategoricalHistogramBy = Map<DataframeValue, CategoricalHistogram>;
export type DataframeValue = number | string | boolean;
export type DataframeValueArray = DataframeValue[] | TypedArray;
export type DataframeColumnGetter = (
label: LabelType
) => DataframeValue | undefined;
/**
* Interface representing a Dataframe column. Eg, returned by
* Dataframe.col().
*/
export interface DataframeColumn extends DataframeColumnGetter {
/**
* __id is unique per Dataframe and DataframeColumn, and is used as a memoization key.
*/
readonly __id: string;
/**
* Boolean indicating if the underlying data supports continuous operations, eg,
* summarizeContinuous.
*/
isContinuous: boolean;
/**
* Return underlying column data as an array-like object.
*/
asArray: () => DataframeValueArray;
/**
* Continuous data summary. Will throw if !isContinuous.
*/
summarizeContinuous: () => ContinuousColumnSummary;
/**
* Categorical data summary.
*/
summarizeCategorical: () => CategoricalColumnSummary;
/**
* Continuous bin/histogram. Will throw if !isContinuous.
* @param bins - array of bin boundary fractions, in range [0., 1.]
* @param domain - data domain [min, max]
*/
histogramContinuous: (
bins: number,
domain: [number, number]
) => ContinuousHistogram;
/**
* Continuous bin/histogram, grouped by another categorical column. Will throw if !isContinuous.
* @param bins - array of bin boundary fractions, in range [0., 1.]
* @param domain - data domain [min, max]
* @param by - group by categorical column
*/
histogramContinuousBy: (
bins: number,
domain: [number, number],
by: DataframeColumn
) => ContinuousHistogramBy;
/**
* Categorical bin/histogram.
*/
histogramCategorical: () => CategoricalHistogram;
/**
* Categorical bin/histogram grouped by another column.
* @param by - group by categorical column
*/
histogramCategoricalBy: (by: DataframeColumn) => CategoricalHistogramBy;
/**
* Return true if the column contains the row label.
*/
has: (rlabel: LabelType) => boolean;
/**
* Return true if the column contains the row offset. Identical to
* (offset >= 0 && offset < dataframe.length)
*/
ihas: (offset: OffsetType) => boolean;
/**
* Return index of the value, as a _label_. Returns undefined if
* not present. *NOTE*: unlike Array.indexOf, does not return an
* offset.
*/
indexOf: (value: DataframeValue) => LabelType | undefined;
/**
* Return the value at the given offset, or undefined if not present.
*/
iget: (offset: OffsetType) => DataframeValue | undefined;
}
+20 -16
View File
@@ -2,19 +2,19 @@
Private utility code for dataframe
*/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
export function callOnceLazy(f: any) {
export function callOnceLazy<
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- a legitimate use of any.
T extends (...args: any[]) => any = (...args: any[]) => any
>(fn: T): (...args: Parameters<T>) => ReturnType<T> {
/*
call function once, and save the result, regardless of arguments (this is not
the same as typical memoization).
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
let value: any;
let value: ReturnType<T>;
let calledOnce = false;
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
const result = function result(...args: any[]) {
const result = function result(...args: Parameters<T>): ReturnType<T> {
if (!calledOnce) {
value = f(...args);
value = fn(...args);
calledOnce = true;
}
return value;
@@ -22,8 +22,14 @@ export function callOnceLazy(f: any) {
return result;
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
export function memoize(fn: any, hashFn: any, maxResultsCached = -1) {
export function memoize<
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- a legitimate use of any.
T extends (...args: any[]) => any = (...args: any[]) => any
>(
fn: T,
hashFn: (...args: Parameters<T>) => string,
maxResultsCached = -1
): (...args: Parameters<T>) => ReturnType<T> {
/*
function memoization, with user-provided hash. hashFn must return a
key which will be unique as a Map key (ie, obeys "sameValueZero" algorithm
@@ -31,8 +37,7 @@ export function memoize(fn: any, hashFn: any, maxResultsCached = -1) {
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map#Key_equality
*/
const cache = new Map();
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
const wrap = function wrap(...args: any[]) {
const wrap = function wrap(...args: Parameters<T>): ReturnType<T> {
const key = hashFn(...args);
if (cache.has(key)) {
return cache.get(key);
@@ -57,12 +62,11 @@ export function memoize(fn: any, hashFn: any, maxResultsCached = -1) {
}
/**
memoization helpers - just a global counter.
**/
*memoization helpers - just a global counter.
*/
let __DataframeMemoId__ = 0;
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
export function __getMemoId() {
export function __getMemoId(): string {
const id = __DataframeMemoId__;
__DataframeMemoId__ += 1;
return id;
return id.toString();
}
+14 -5
View File
@@ -12,17 +12,26 @@ Arguments:
*/
import { NumberArray, TypedArrayConstructor } from "../common/types/arraytypes";
import { sortArray } from "./typedCrossfilter/sort";
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'quantArr' implicitly has an 'any' type.
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
export default function quantile(quantArr, tarr, sorted = false) {
export default function quantile(
quantArr: number[],
tarr: NumberArray,
sorted = false
): number[] {
/*
start with the naive (sort) implementation. Later, use a faster partition
*/
const arr = sorted ? tarr : sortArray(new tarr.constructor(tarr)); // copy
if (tarr.length === 0) {
return new Array(quantArr.length).fill(0);
}
const Ctor: TypedArrayConstructor = tarr.constructor as TypedArrayConstructor;
const arr = sorted ? tarr : sortArray(new Ctor(tarr)); // copy
const len = arr.length;
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'q' implicitly has an 'any' type.
return quantArr.map((q) => {
if (q === 1) {
return arr[len - 1];
@@ -4,6 +4,7 @@ See also reducers/annotations.js
*/
import { Schema } from "../../common/types/schema";
import { Dataframe, LabelType } from "../dataframe";
/*
There are a number of state constraints assumed throughout the
@@ -57,9 +58,12 @@ export function isUserAnnotation(annoMatrix, name) {
return _isUserAnnotation(annoMatrix.schema, name);
}
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'df' implicitly has an 'any' type.
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
export function allHaveLabelByMask(df, colName, label, mask) {
export function allHaveLabelByMask(
df: Dataframe,
colName: LabelType,
label: string,
mask: Uint8Array
): boolean {
// return true if all rows as indicated by mask have the colname set to label.
// False if not.
const col = df.col(colName);
+12 -9
View File
@@ -7,6 +7,7 @@ import memoize from "memoize-one";
import * as globals from "../../globals";
import parseRGB from "../parseRGB";
import { range } from "../range";
import { Dataframe, LabelType } from "../dataframe";
/*
given a color mode & accessor, generate an annoMatrix query that will
@@ -95,18 +96,19 @@ Returns:
}
*/
function _createColorTable(
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
colorMode: any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
colorByAccessor: any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
colorByData: any,
colorMode: string | null,
colorByAccessor: LabelType | null,
colorByData: Dataframe | null,
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
schema: any,
userColors = null
) {
if (colorMode === null || colorByData === null)
return defaultColors(schema.dataframe.nObs);
switch (colorMode) {
case "color by categorical metadata": {
if (colorByAccessor === null) return defaultColors(schema.dataframe.nObs);
const data = colorByData.col(colorByAccessor).asArray();
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
if (userColors && colorByAccessor in userColors) {
@@ -115,18 +117,19 @@ function _createColorTable(
return createColorsByCategoricalMetadata(data, colorByAccessor, schema);
}
case "color by continuous metadata": {
if (colorByAccessor === null) return defaultColors(schema.dataframe.nObs);
const col = colorByData.col(colorByAccessor);
const { min, max } = col.summarize();
const { min, max } = col.summarizeContinuous();
return createColorsByContinuousMetadata(col.asArray(), min, max);
}
case "color by expression": {
const col = colorByData.icol(0);
const { min, max } = col.summarize();
const { min, max } = col.summarizeContinuous();
return createColorsByContinuousMetadata(col.asArray(), min, max);
}
case "color by geneset mean expression": {
const col = colorByData.icol(0);
const { min, max } = col.summarize();
const { min, max } = col.summarizeContinuous();
return createColorsByContinuousMetadata(col.asArray(), min, max);
}
default: {
+21 -27
View File
@@ -1,6 +1,10 @@
import { flatbuffers } from "flatbuffers";
import { NetEncoding } from "./matrix_generated";
import { isTypedArray, isFloatTypedArray } from "../../common/types/arraytypes";
import {
TypedArray,
isTypedArray,
isFloatTypedArray,
} from "../../common/types/arraytypes";
import {
Dataframe,
IdentityInt32Index,
@@ -94,14 +98,13 @@ function encodeTypedArray(builder: any, uType: any, uData: any) {
return builder.endObject();
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
export function encodeMatrixFBS(df: any) {
export function encodeMatrixFBS(df: Dataframe): Uint8Array {
/*
encode the dataframe as an FBS Matrix
*/
/* row indexing not supported currently */
if (df.rowIndex.constructor !== IdentityInt32Index) {
if (!(df.rowIndex instanceof IdentityInt32Index)) {
throw new Error("FBS does not support row index encoding at this time");
}
@@ -115,11 +118,9 @@ export function encodeMatrixFBS(df: any) {
let encColumns;
if (shape[0] > 0 && shape[1] > 0) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
const columns = df.columns().map((col: any) => col.asArray());
const columns = df.columns().map((col) => col.asArray());
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
const cols = columns.map((carr: any) => {
const cols = columns.map((carr) => {
let uType;
let tarr;
if (isTypedArray(carr)) {
@@ -179,8 +180,7 @@ export function encodeMatrixFBS(df: any) {
return builder.asUint8Array();
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
function promoteTypedArray(o: any) {
function promoteTypedArray(o: TypedArray) {
/*
Decide what internal data type to use for the data returned from
the server.
@@ -212,8 +212,9 @@ function promoteTypedArray(o: any) {
return new TypedArrayCtor(o);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any -- - FIXME: disabled temporarily on migrate to TS.
export function matrixFBSToDataframe(arrayBuffers: any) {
export function matrixFBSToDataframe(
arrayBuffers: ArrayBuffer | ArrayBuffer[]
): Dataframe {
/*
Convert array of Matrix FBS to a Dataframe.
@@ -229,37 +230,30 @@ export function matrixFBSToDataframe(arrayBuffers: any) {
arrayBuffers = [arrayBuffers];
}
if (arrayBuffers.length === 0) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
return (Dataframe as any).Dataframe.empty();
return Dataframe.empty();
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
const fbs = arrayBuffers.map((ab: any) => decodeMatrixFBS(ab, true)); // leave in place
const fbs = arrayBuffers.map((ab) => decodeMatrixFBS(ab, true)); // leave in place
/* check that all FBS have same row dimensionality */
const { nRows } = fbs[0];
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
fbs.forEach((b: any) => {
fbs.forEach((b) => {
if (b.nRows !== nRows)
throw new Error("FBS with inconsistent dimensionality");
});
const columns = fbs // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
.map((fb: any) =>
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
fb.columns.map((c: any) => {
const columns = fbs
.map((fb) =>
fb.columns.map((c) => {
if (isFloatTypedArray(c) || Array.isArray(c)) return c;
return promoteTypedArray(c);
})
)
.flat();
// colIdx may be TypedArray or Array
const colIdx = fbs // eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
.map((b: any) =>
Array.isArray(b.colIdx) ? b.colIdx : Array.from(b.colIdx)
)
const colIdx = fbs
.map((b) => (Array.isArray(b.colIdx) ? b.colIdx : Array.from(b.colIdx)))
.flat();
const nCols = columns.length;
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'KeyIndex' is not assignable to p... Remove this comment to see the full error message
const df = new Dataframe([nRows, nCols], columns, null, new KeyIndex(colIdx));
return df;
}
@@ -37,6 +37,7 @@ Views can be interogated for their type with the following:
import { clip, isubsetMask, isubset } from "../../annoMatrix";
import { memoize } from "../dataframe/util";
import { Dataframe, LabelIndex } from "../dataframe";
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'annoMatrix' implicitly has an 'any' typ... Remove this comment to see the full error message
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
@@ -101,7 +102,7 @@ export function _userResetSubsetAnnoMatrix(annoMatrix) {
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'annoMatrix' implicitly has an 'any' typ... Remove this comment to see the full error message
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
export function _setEmbeddingSubset(annoMatrix, embeddingDf) {
export function _setEmbeddingSubset(annoMatrix, embeddingDf: Dataframe) {
/*
Set the embedding subset view. Only create a subset view for the embedding
when it is needed, ie, there are NaN values in the embeddings.
@@ -141,8 +142,10 @@ export function _setEmbeddingSubset(annoMatrix, embeddingDf) {
return annoMatrix;
}
// @ts-expect-error ts-migrate(6133) FIXME: 'baseRowIndex' is declared but its value is never ... Remove this comment to see the full error message
function _getEmbeddingRowOffsets(baseRowIndex, embeddingDf) {
function _getEmbeddingRowOffsets(
_baseRowIndex: LabelIndex,
embeddingDf: Dataframe
) {
/*
given a dataframe containing an embedding:
- if the embedding contains no NaN coordinates, return null
@@ -167,17 +170,16 @@ function _getEmbeddingRowOffsets(baseRowIndex, embeddingDf) {
return offsets.subarray(0, numOffsets);
}
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'embeddingDf' implicitly has an 'any' ty... Remove this comment to see the full error message
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
export function _getDiscreteCellEmbeddingRowIndex(embeddingDf) {
export function _getDiscreteCellEmbeddingRowIndex(
embeddingDf: Dataframe
): LabelIndex {
const idx = _getEmbeddingRowOffsets(embeddingDf.rowIndex, embeddingDf);
if (idx === null) return embeddingDf.rowIndex;
return embeddingDf.rowIndex.isubset(idx);
}
export const getDiscreteCellEmbeddingRowIndex = memoize(
_getDiscreteCellEmbeddingRowIndex,
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'df' implicitly has an 'any' type.
(df) => df.__id
(df: Dataframe) => df.__id
);
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'annoMatrix' implicitly has an 'any' typ... Remove this comment to see the full error message
@@ -12,9 +12,8 @@ import { makeSortIndex } from "./util";
import { isAnyArray } from "../../common/types/arraytypes";
class NotImplementedError extends Error {
// eslint-disable-next-line @typescript-eslint/no-explicit-any --- FIXME: disabled temporarily on migrate to TS.
constructor(...params: any[]) {
super(...params);
constructor(msg: string) {
super(msg);
// Maintains proper stack trace for where our error was thrown (only available on V8)
if (Error.captureStackTrace) {
@@ -63,8 +62,7 @@ export default class ImmutableTypedCrossfilter {
Object.preventExtensions(this);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types --- FIXME: disabled temporarily on migrate to TS.
size() {
size(): number {
return this.data.length;
}