pond-ts 0.20.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +104 -1
- package/dist/batch/aggregate-columns.d.ts +29 -0
- package/dist/batch/aggregate-columns.d.ts.map +1 -1
- package/dist/batch/aggregate-columns.js +63 -0
- package/dist/batch/aggregate-columns.js.map +1 -1
- package/dist/batch/operators/collapse.d.ts +42 -0
- package/dist/batch/operators/collapse.d.ts.map +1 -0
- package/dist/batch/operators/collapse.js +83 -0
- package/dist/batch/operators/collapse.js.map +1 -0
- package/dist/batch/operators/cumulative.d.ts +40 -0
- package/dist/batch/operators/cumulative.d.ts.map +1 -0
- package/dist/batch/operators/cumulative.js +74 -0
- package/dist/batch/operators/cumulative.js.map +1 -0
- package/dist/batch/operators/diff-rate.d.ts +52 -0
- package/dist/batch/operators/diff-rate.d.ts.map +1 -0
- package/dist/batch/operators/diff-rate.js +99 -0
- package/dist/batch/operators/diff-rate.js.map +1 -0
- package/dist/batch/operators/fill.d.ts +66 -0
- package/dist/batch/operators/fill.d.ts.map +1 -0
- package/dist/batch/operators/fill.js +219 -0
- package/dist/batch/operators/fill.js.map +1 -0
- package/dist/batch/operators/map.d.ts +56 -0
- package/dist/batch/operators/map.d.ts.map +1 -0
- package/dist/batch/operators/map.js +109 -0
- package/dist/batch/operators/map.js.map +1 -0
- package/dist/batch/operators/shift.d.ts +31 -0
- package/dist/batch/operators/shift.d.ts.map +1 -0
- package/dist/batch/operators/shift.js +56 -0
- package/dist/batch/operators/shift.js.map +1 -0
- package/dist/batch/time-series.d.ts +36 -5
- package/dist/batch/time-series.d.ts.map +1 -1
- package/dist/batch/time-series.js +244 -387
- package/dist/batch/time-series.js.map +1 -1
- package/dist/columnar/index.d.ts +1 -1
- package/dist/columnar/index.d.ts.map +1 -1
- package/dist/columnar/index.js +1 -1
- package/dist/columnar/index.js.map +1 -1
- package/dist/columnar/view.d.ts +57 -1
- package/dist/columnar/view.d.ts.map +1 -1
- package/dist/columnar/view.js +88 -0
- package/dist/columnar/view.js.map +1 -1
- package/dist/reducers/stdev.d.ts.map +1 -1
- package/dist/reducers/stdev.js +81 -59
- package/dist/reducers/stdev.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { float64ColumnFromArray, withColumnReplaced, withRowRange, } from '../../columnar/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* **Step 4 — column-native `diff` / `rate` / `pctChange` (extracted
|
|
4
|
+
* operator).** Successive differences per target column, computed
|
|
5
|
+
* straight off the columnar store: read each target's cells
|
|
6
|
+
* (storage-agnostic `read(i)`), fold the per-row difference, replace
|
|
7
|
+
* the column — no `series.events` materialization, no per-row
|
|
8
|
+
* `Event`. Non-target columns + the key axis pass through untouched
|
|
9
|
+
* (`withColumnReplaced` references the unchanged columns + keys
|
|
10
|
+
* zero-copy).
|
|
11
|
+
*
|
|
12
|
+
* Matches the row path's semantics exactly:
|
|
13
|
+
* - Row 0 has no predecessor, so each target is `undefined` there.
|
|
14
|
+
* - Row `i ≥ 1`: with both `prev` and `curr` defined numbers,
|
|
15
|
+
* `delta = curr - prev`; `diff` emits `delta`, `rate` emits
|
|
16
|
+
* `delta / dt` (`dt` = `(begin[i] − begin[i−1]) / 1000` seconds,
|
|
17
|
+
* `undefined` when `dt === 0`), `pctChange` emits `delta / prev`
|
|
18
|
+
* (`undefined` when `prev === 0`). If either side is missing /
|
|
19
|
+
* non-numeric, the output is `undefined`. A stored `NaN` is a
|
|
20
|
+
* defined number (`typeof raw === 'number'`), so it participates.
|
|
21
|
+
*
|
|
22
|
+
* **`drop`.** `drop: false` (default) keeps the predecessor-less
|
|
23
|
+
* first row (its targets `undefined`, its other columns + key
|
|
24
|
+
* intact). `drop: true` removes that row entirely — every column
|
|
25
|
+
* **and** the key slice to `[1, n)` via `withRowRange`, the row-range
|
|
26
|
+
* substrate built for exactly this. The full-length result is built
|
|
27
|
+
* first (the `drop: false` shape); `drop: true` just appends the
|
|
28
|
+
* trailing one-row slice, so the two paths share all the fold logic.
|
|
29
|
+
*
|
|
30
|
+
* Returns the reshaped store + the output schema (targets widened to
|
|
31
|
+
* optional `number`). The result-schema cast is the single trust
|
|
32
|
+
* boundary; the `TimeSeries` method wraps the store via
|
|
33
|
+
* `#fromTrustedStore`.
|
|
34
|
+
*
|
|
35
|
+
* A non-numeric target name is unreachable through the typed surface
|
|
36
|
+
* (`NumericColumnNameForSchema<S>`); defeating that constraint makes
|
|
37
|
+
* the all-`undefined` replacement column `kind: 'number'`, so
|
|
38
|
+
* `withColumnReplaced`'s kind guard throws a `RangeError` naming the
|
|
39
|
+
* column — fail-fast over the old path's silent all-`undefined`.
|
|
40
|
+
*/
|
|
41
|
+
export function diffRateOp(store, schema, mode, cols, drop) {
|
|
42
|
+
if (cols.length === 0) {
|
|
43
|
+
throw new Error(`${mode}() requires at least one column name`);
|
|
44
|
+
}
|
|
45
|
+
const targetSet = new Set(cols);
|
|
46
|
+
const outSchema = Object.freeze(schema.map((col, i) => i === 0 || !targetSet.has(col.name)
|
|
47
|
+
? col
|
|
48
|
+
: { ...col, kind: 'number', required: false }));
|
|
49
|
+
const n = store.length;
|
|
50
|
+
// For `rate`, the elapsed-seconds divisor depends only on the row
|
|
51
|
+
// index, not the target — precompute it once rather than per
|
|
52
|
+
// (target, row). dt[0] is unused (row 0 has no predecessor).
|
|
53
|
+
let dt;
|
|
54
|
+
if (mode === 'rate' && n > 0) {
|
|
55
|
+
dt = new Float64Array(n);
|
|
56
|
+
for (let i = 1; i < n; i += 1) {
|
|
57
|
+
dt[i] = (store.beginAt(i) - store.beginAt(i - 1)) / 1000;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
let result = store;
|
|
61
|
+
for (const name of cols) {
|
|
62
|
+
const col = store.columns.get(name);
|
|
63
|
+
const out = new Array(n);
|
|
64
|
+
if (n > 0)
|
|
65
|
+
out[0] = undefined; // first row: no predecessor
|
|
66
|
+
for (let i = 1; i < n; i += 1) {
|
|
67
|
+
const prev = col.read(i - 1);
|
|
68
|
+
const curr = col.read(i);
|
|
69
|
+
if (typeof curr === 'number' && typeof prev === 'number') {
|
|
70
|
+
const delta = curr - prev;
|
|
71
|
+
if (mode === 'pctChange') {
|
|
72
|
+
out[i] = prev !== 0 ? delta / prev : undefined;
|
|
73
|
+
}
|
|
74
|
+
else if (mode === 'rate') {
|
|
75
|
+
const d = dt[i];
|
|
76
|
+
out[i] = d !== 0 ? delta / d : undefined;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
out[i] = delta;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
out[i] = undefined;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
result = withColumnReplaced(result, name, float64ColumnFromArray(out));
|
|
87
|
+
}
|
|
88
|
+
// drop:true removes the predecessor-less first row from every column
|
|
89
|
+
// + the key. (n === 0 has no row to drop; the guard keeps the empty
|
|
90
|
+
// store untouched rather than slicing [1, 0).)
|
|
91
|
+
if (drop && n > 0) {
|
|
92
|
+
result = withRowRange(result, 1, n);
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
store: result,
|
|
96
|
+
schema: outSchema,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=diff-rate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff-rate.js","sourceRoot":"","sources":["../../../src/batch/operators/diff-rate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { type ColumnarStore } from '../../columnar/index.js';
|
|
2
|
+
import type { FillStrategy } from '../../schema/reshape.js';
|
|
3
|
+
import type { ScalarValue, SeriesSchema } from '../../schema/index.js';
|
|
4
|
+
/**
|
|
5
|
+
* A fill strategy resolved to its operator-ready form: a built-in
|
|
6
|
+
* strategy keyword, or a literal value to place in every gap cell.
|
|
7
|
+
* (The method resolves the public `FillStrategy | FillMapping` input
|
|
8
|
+
* into a per-column map of these before delegating.)
|
|
9
|
+
*/
|
|
10
|
+
export type ResolvedFillSpec = {
|
|
11
|
+
mode: FillStrategy;
|
|
12
|
+
} | {
|
|
13
|
+
mode: 'literal';
|
|
14
|
+
value: ScalarValue;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* **Step 4 — column-native `fill` (extracted operator).** Gap-fills
|
|
18
|
+
* value columns straight off the columnar store: read each target's
|
|
19
|
+
* cells (storage-agnostic `read(i)`), walk contiguous `undefined`
|
|
20
|
+
* runs, apply the per-column strategy, and replace only the columns
|
|
21
|
+
* that actually changed — no `series.events` materialization, no
|
|
22
|
+
* per-row `Event`. Untouched columns + the key axis pass through by
|
|
23
|
+
* reference.
|
|
24
|
+
*
|
|
25
|
+
* Matches the row path's semantics exactly:
|
|
26
|
+
* - **Gap walk.** Each maximal run of `undefined` cells is one gap.
|
|
27
|
+
* `linear` needs both neighbors, `hold` needs a left neighbor,
|
|
28
|
+
* `bfill` needs a right neighbor; `zero` / `literal` need none. A
|
|
29
|
+
* gap missing its required neighbor is left unfilled.
|
|
30
|
+
* - **`limit` / `maxGap`** are all-or-nothing per gap: a gap longer
|
|
31
|
+
* than `limit` cells, or whose temporal span exceeds `maxGap` ms,
|
|
32
|
+
* is skipped entirely. The span uses both neighbors for interior
|
|
33
|
+
* gaps, the available neighbor + the gap edge for edge gaps.
|
|
34
|
+
* - **Strategies.** `hold` carries the left value, `bfill` the right;
|
|
35
|
+
* `zero` writes 0; `literal` writes the supplied value; `linear`
|
|
36
|
+
* interpolates by time (`tspan === 0` ⇒ the left value). `zero` and
|
|
37
|
+
* `linear` are **numeric-only** — on a non-numeric column they
|
|
38
|
+
* silently skip (the column is left untouched, not rebuilt).
|
|
39
|
+
*
|
|
40
|
+
* Unlike the numeric folds (`cumulative`, `diff`), `fill` preserves
|
|
41
|
+
* each column's **kind** — `hold` / `bfill` / `literal` apply to any
|
|
42
|
+
* kind — so a changed column is rebuilt with the kind-appropriate
|
|
43
|
+
* builder (`buildFilledColumn`). A `literal` whose runtime type
|
|
44
|
+
* doesn't match the column kind throws a `RangeError` naming the
|
|
45
|
+
* column when it would be placed (gap-dependent).
|
|
46
|
+
*
|
|
47
|
+
* **This throw is a deliberate behavior change, not parity.** The old
|
|
48
|
+
* events path did NOT throw on a kind-mismatched literal: it stored
|
|
49
|
+
* the literal in the event cache *and* coerced it into the numeric
|
|
50
|
+
* buffer, yielding an internally-inconsistent series (`.get()`
|
|
51
|
+
* returned the string, `.column().sum()` returned `NaN`). Column-
|
|
52
|
+
* native has a single representation, so that dual-view inconsistency
|
|
53
|
+
* is unreproducible — failing fast on the nonsensical input (a kind-
|
|
54
|
+
* mismatched literal is only writable because `FillMapping` values
|
|
55
|
+
* are the broad `FillStrategy | ScalarValue`) is the principled
|
|
56
|
+
* replacement.
|
|
57
|
+
*
|
|
58
|
+
* The schema is unchanged (fill never widens or drops); the cast is
|
|
59
|
+
* the single trust boundary, and the `TimeSeries.fill` method wraps
|
|
60
|
+
* the store via `#fromTrustedStore`.
|
|
61
|
+
*/
|
|
62
|
+
export declare function fillOp<S extends SeriesSchema>(store: ColumnarStore<S>, schema: S, specs: ReadonlyMap<string, ResolvedFillSpec>, limit: number | undefined, maxGapMs: number | undefined): {
|
|
63
|
+
store: ColumnarStore<S>;
|
|
64
|
+
schema: S;
|
|
65
|
+
};
|
|
66
|
+
//# sourceMappingURL=fill.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fill.d.ts","sourceRoot":"","sources":["../../../src/batch/operators/fill.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,aAAa,EAOnB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAEvE;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,GACtB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,WAAW,CAAA;CAAE,CAAC;AAmC5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,wBAAgB,MAAM,CAAC,CAAC,SAAS,YAAY,EAC3C,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,EACvB,MAAM,EAAE,CAAC,EACT,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,gBAAgB,CAAC,EAC5C,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC3B;IAAE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC;IAAC,MAAM,EAAE,CAAC,CAAA;CAAE,CA6IxC"}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { arrayColumnFromArray, booleanColumnFromArray, float64ColumnFromArray, stringColumnFromArray, withColumnReplaced, } from '../../columnar/index.js';
|
|
2
|
+
/** Does `value`'s runtime type match the column's declared kind? */
|
|
3
|
+
function valueMatchesKind(value, kind) {
|
|
4
|
+
switch (kind) {
|
|
5
|
+
case 'number':
|
|
6
|
+
return typeof value === 'number';
|
|
7
|
+
case 'string':
|
|
8
|
+
return typeof value === 'string';
|
|
9
|
+
case 'boolean':
|
|
10
|
+
return typeof value === 'boolean';
|
|
11
|
+
case 'array':
|
|
12
|
+
return Array.isArray(value);
|
|
13
|
+
default:
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** Rebuilds a filled value array into a column of the given kind. */
|
|
18
|
+
function buildFilledColumn(kind, values) {
|
|
19
|
+
switch (kind) {
|
|
20
|
+
case 'number':
|
|
21
|
+
return float64ColumnFromArray(values);
|
|
22
|
+
case 'string':
|
|
23
|
+
return stringColumnFromArray(values);
|
|
24
|
+
case 'boolean':
|
|
25
|
+
return booleanColumnFromArray(values);
|
|
26
|
+
case 'array':
|
|
27
|
+
// ArrayValue cells; the cast is the kind dispatch's trust point.
|
|
28
|
+
return arrayColumnFromArray(values);
|
|
29
|
+
default:
|
|
30
|
+
throw new TypeError(`fill: unsupported column kind '${kind}'`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* **Step 4 — column-native `fill` (extracted operator).** Gap-fills
|
|
35
|
+
* value columns straight off the columnar store: read each target's
|
|
36
|
+
* cells (storage-agnostic `read(i)`), walk contiguous `undefined`
|
|
37
|
+
* runs, apply the per-column strategy, and replace only the columns
|
|
38
|
+
* that actually changed — no `series.events` materialization, no
|
|
39
|
+
* per-row `Event`. Untouched columns + the key axis pass through by
|
|
40
|
+
* reference.
|
|
41
|
+
*
|
|
42
|
+
* Matches the row path's semantics exactly:
|
|
43
|
+
* - **Gap walk.** Each maximal run of `undefined` cells is one gap.
|
|
44
|
+
* `linear` needs both neighbors, `hold` needs a left neighbor,
|
|
45
|
+
* `bfill` needs a right neighbor; `zero` / `literal` need none. A
|
|
46
|
+
* gap missing its required neighbor is left unfilled.
|
|
47
|
+
* - **`limit` / `maxGap`** are all-or-nothing per gap: a gap longer
|
|
48
|
+
* than `limit` cells, or whose temporal span exceeds `maxGap` ms,
|
|
49
|
+
* is skipped entirely. The span uses both neighbors for interior
|
|
50
|
+
* gaps, the available neighbor + the gap edge for edge gaps.
|
|
51
|
+
* - **Strategies.** `hold` carries the left value, `bfill` the right;
|
|
52
|
+
* `zero` writes 0; `literal` writes the supplied value; `linear`
|
|
53
|
+
* interpolates by time (`tspan === 0` ⇒ the left value). `zero` and
|
|
54
|
+
* `linear` are **numeric-only** — on a non-numeric column they
|
|
55
|
+
* silently skip (the column is left untouched, not rebuilt).
|
|
56
|
+
*
|
|
57
|
+
* Unlike the numeric folds (`cumulative`, `diff`), `fill` preserves
|
|
58
|
+
* each column's **kind** — `hold` / `bfill` / `literal` apply to any
|
|
59
|
+
* kind — so a changed column is rebuilt with the kind-appropriate
|
|
60
|
+
* builder (`buildFilledColumn`). A `literal` whose runtime type
|
|
61
|
+
* doesn't match the column kind throws a `RangeError` naming the
|
|
62
|
+
* column when it would be placed (gap-dependent).
|
|
63
|
+
*
|
|
64
|
+
* **This throw is a deliberate behavior change, not parity.** The old
|
|
65
|
+
* events path did NOT throw on a kind-mismatched literal: it stored
|
|
66
|
+
* the literal in the event cache *and* coerced it into the numeric
|
|
67
|
+
* buffer, yielding an internally-inconsistent series (`.get()`
|
|
68
|
+
* returned the string, `.column().sum()` returned `NaN`). Column-
|
|
69
|
+
* native has a single representation, so that dual-view inconsistency
|
|
70
|
+
* is unreproducible — failing fast on the nonsensical input (a kind-
|
|
71
|
+
* mismatched literal is only writable because `FillMapping` values
|
|
72
|
+
* are the broad `FillStrategy | ScalarValue`) is the principled
|
|
73
|
+
* replacement.
|
|
74
|
+
*
|
|
75
|
+
* The schema is unchanged (fill never widens or drops); the cast is
|
|
76
|
+
* the single trust boundary, and the `TimeSeries.fill` method wraps
|
|
77
|
+
* the store via `#fromTrustedStore`.
|
|
78
|
+
*/
|
|
79
|
+
export function fillOp(store, schema, specs, limit, maxGapMs) {
|
|
80
|
+
const n = store.length;
|
|
81
|
+
if (n === 0 || specs.size === 0) {
|
|
82
|
+
return { store, schema };
|
|
83
|
+
}
|
|
84
|
+
const colKind = new Map();
|
|
85
|
+
for (let i = 1; i < schema.length; i += 1) {
|
|
86
|
+
colKind.set(schema[i].name, schema[i].kind);
|
|
87
|
+
}
|
|
88
|
+
// Times are needed only for `linear` and `maxGap`; build once, lazily.
|
|
89
|
+
let times;
|
|
90
|
+
const getTimes = () => {
|
|
91
|
+
if (times === undefined) {
|
|
92
|
+
times = new Array(n);
|
|
93
|
+
for (let i = 0; i < n; i += 1)
|
|
94
|
+
times[i] = store.beginAt(i);
|
|
95
|
+
}
|
|
96
|
+
return times;
|
|
97
|
+
};
|
|
98
|
+
let result = store;
|
|
99
|
+
for (const [name, spec] of specs) {
|
|
100
|
+
const col = store.columns.get(name);
|
|
101
|
+
if (col === undefined)
|
|
102
|
+
continue;
|
|
103
|
+
const kind = colKind.get(name);
|
|
104
|
+
// `zero` / `linear` are numeric-only: skip non-numeric columns
|
|
105
|
+
// entirely (leave the original column, no rebuild).
|
|
106
|
+
if ((spec.mode === 'zero' || spec.mode === 'linear') && kind !== 'number') {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const values = new Array(n);
|
|
110
|
+
for (let i = 0; i < n; i += 1)
|
|
111
|
+
values[i] = col.read(i);
|
|
112
|
+
let changed = false;
|
|
113
|
+
let i = 0;
|
|
114
|
+
while (i < n) {
|
|
115
|
+
if (values[i] !== undefined) {
|
|
116
|
+
i += 1;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const start = i;
|
|
120
|
+
while (i < n && values[i] === undefined)
|
|
121
|
+
i += 1;
|
|
122
|
+
const end = i; // exclusive
|
|
123
|
+
const length = end - start;
|
|
124
|
+
const hasPrev = start > 0;
|
|
125
|
+
const hasNext = end < n;
|
|
126
|
+
let strategyOk;
|
|
127
|
+
switch (spec.mode) {
|
|
128
|
+
case 'linear':
|
|
129
|
+
strategyOk = hasPrev && hasNext;
|
|
130
|
+
break;
|
|
131
|
+
case 'hold':
|
|
132
|
+
strategyOk = hasPrev;
|
|
133
|
+
break;
|
|
134
|
+
case 'bfill':
|
|
135
|
+
strategyOk = hasNext;
|
|
136
|
+
break;
|
|
137
|
+
default:
|
|
138
|
+
strategyOk = true; // zero, literal
|
|
139
|
+
}
|
|
140
|
+
if (!strategyOk)
|
|
141
|
+
continue;
|
|
142
|
+
if (limit !== undefined && length > limit)
|
|
143
|
+
continue;
|
|
144
|
+
if (maxGapMs !== undefined) {
|
|
145
|
+
const t = getTimes();
|
|
146
|
+
let span;
|
|
147
|
+
if (hasPrev && hasNext) {
|
|
148
|
+
span = t[end] - t[start - 1];
|
|
149
|
+
}
|
|
150
|
+
else if (hasPrev) {
|
|
151
|
+
span = t[end - 1] - t[start - 1];
|
|
152
|
+
}
|
|
153
|
+
else if (hasNext) {
|
|
154
|
+
span = t[end] - t[start];
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
span = 0;
|
|
158
|
+
}
|
|
159
|
+
if (span > maxGapMs)
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
switch (spec.mode) {
|
|
163
|
+
case 'hold': {
|
|
164
|
+
const v = values[start - 1];
|
|
165
|
+
for (let j = start; j < end; j += 1)
|
|
166
|
+
values[j] = v;
|
|
167
|
+
changed = true;
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
case 'bfill': {
|
|
171
|
+
const v = values[end];
|
|
172
|
+
for (let j = start; j < end; j += 1)
|
|
173
|
+
values[j] = v;
|
|
174
|
+
changed = true;
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
case 'zero': {
|
|
178
|
+
for (let j = start; j < end; j += 1)
|
|
179
|
+
values[j] = 0;
|
|
180
|
+
changed = true;
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
case 'literal': {
|
|
184
|
+
// Gap-dependent kind check: throws exactly when a kind-broken
|
|
185
|
+
// literal would be placed (matching the old SeriesStore-intake
|
|
186
|
+
// error), with a clearer column-named message.
|
|
187
|
+
if (!valueMatchesKind(spec.value, kind)) {
|
|
188
|
+
throw new RangeError(`fill: literal value ${JSON.stringify(spec.value)} for column '${name}' does not match its kind '${kind}'`);
|
|
189
|
+
}
|
|
190
|
+
for (let j = start; j < end; j += 1)
|
|
191
|
+
values[j] = spec.value;
|
|
192
|
+
changed = true;
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
case 'linear': {
|
|
196
|
+
const before = values[start - 1];
|
|
197
|
+
const after = values[end];
|
|
198
|
+
const t = getTimes();
|
|
199
|
+
const t0 = t[start - 1];
|
|
200
|
+
const t1 = t[end];
|
|
201
|
+
const tspan = t1 - t0;
|
|
202
|
+
for (let j = start; j < end; j += 1) {
|
|
203
|
+
values[j] =
|
|
204
|
+
tspan === 0
|
|
205
|
+
? before
|
|
206
|
+
: before + (after - before) * ((t[j] - t0) / tspan);
|
|
207
|
+
}
|
|
208
|
+
changed = true;
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (changed) {
|
|
214
|
+
result = withColumnReplaced(result, name, buildFilledColumn(kind, values));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return { store: result, schema };
|
|
218
|
+
}
|
|
219
|
+
//# sourceMappingURL=fill.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fill.js","sourceRoot":"","sources":["../../../src/batch/operators/fill.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { type ColumnarStore } from '../../columnar/index.js';
|
|
2
|
+
import type { SeriesSchema } from '../../schema/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* A per-cell value transform: `(value) => newValue`, where the output
|
|
5
|
+
* is the **same kind** as the input (number→number, string→string,
|
|
6
|
+
* …). The `TimeSeries.mapColumns` method types each column's mapper
|
|
7
|
+
* against that column's value type; the operator erases to
|
|
8
|
+
* `(value: unknown) => unknown` at the trust boundary.
|
|
9
|
+
*/
|
|
10
|
+
export type ColumnMapper = (value: unknown) => unknown;
|
|
11
|
+
/**
|
|
12
|
+
* **Step 4 — column-native `mapColumns` (extracted operator).** Applies
|
|
13
|
+
* a per-cell value transform to one or more columns, straight off the
|
|
14
|
+
* columnar store: read each target's cells (storage-agnostic
|
|
15
|
+
* `read(i)`), apply the mapper to each **defined** value, rebuild the
|
|
16
|
+
* column — no `series.events` materialization, no per-row `Event`.
|
|
17
|
+
* Non-mapped columns + the key axis pass through by reference.
|
|
18
|
+
*
|
|
19
|
+
* Semantics:
|
|
20
|
+
* - **Missing cells carry.** The mapper is called only on defined
|
|
21
|
+
* values; a missing (`undefined`) cell stays missing (the mapper is
|
|
22
|
+
* not invoked).
|
|
23
|
+
* - **Numeric results must be finite.** For a `number` column, a mapper
|
|
24
|
+
* result of `NaN` or `±Infinity` throws a `RangeError` at write —
|
|
25
|
+
* matching construction intake (`assertCellKind`), which rejects
|
|
26
|
+
* non-finite numbers. This keeps packed numeric columns NaN-free, so
|
|
27
|
+
* the columnar fast-path and the row-path reducers can never diverge
|
|
28
|
+
* on the same bucket (the bug audit v2 §1.3 reproduced). A stored
|
|
29
|
+
* `NaN` *is* a defined value, so the mapper is still invoked on it —
|
|
30
|
+
* use that to clean it (map `NaN` to a finite number, or to
|
|
31
|
+
* `undefined` for a missing cell). (Array columns are not
|
|
32
|
+
* element-checked here; they don't feed the numeric reducers.)
|
|
33
|
+
* - **Same kind, schema-stable.** The mapper returns the column's own
|
|
34
|
+
* kind (the method's type enforces `(value: T) => T`), so the output
|
|
35
|
+
* column keeps its kind and the schema is unchanged. The result is
|
|
36
|
+
* rebuilt with the kind-appropriate builder.
|
|
37
|
+
*
|
|
38
|
+
* The schema is returned unchanged; the cast is the single trust
|
|
39
|
+
* boundary, and the `TimeSeries.mapColumns` method wraps the store via
|
|
40
|
+
* `#fromTrustedStore`.
|
|
41
|
+
*
|
|
42
|
+
* A mapper that — only by defeating the `(value: T) => T` type —
|
|
43
|
+
* returns `undefined`, or a value of the wrong kind (e.g. a string
|
|
44
|
+
* from a numeric mapper via an `as` cast), produces a cell the
|
|
45
|
+
* same-kind builder can't store: `columnFromValuesByKind` coerces it
|
|
46
|
+
* to missing, so the cell reads back as a gap the declared schema may
|
|
47
|
+
* not advertise. Both are type-illegal inputs, not handled specially.
|
|
48
|
+
* (A non-finite *number* from a numeric mapper is the one same-kind
|
|
49
|
+
* case that throws rather than coerces — it would corrupt the packed
|
|
50
|
+
* column, not read back as a gap.)
|
|
51
|
+
*/
|
|
52
|
+
export declare function mapOp<S extends SeriesSchema>(store: ColumnarStore<S>, schema: S, spec: ReadonlyMap<string, ColumnMapper>): {
|
|
53
|
+
store: ColumnarStore<S>;
|
|
54
|
+
schema: S;
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=map.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"map.d.ts","sourceRoot":"","sources":["../../../src/batch/operators/map.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,aAAa,EAOnB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE1D;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;AAyBvD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,wBAAgB,KAAK,CAAC,CAAC,SAAS,YAAY,EAC1C,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,EACvB,MAAM,EAAE,CAAC,EACT,IAAI,EAAE,WAAW,CAAC,MAAM,EAAE,YAAY,CAAC,GACtC;IAAE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC;IAAC,MAAM,EAAE,CAAC,CAAA;CAAE,CAqDxC"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { arrayColumnFromArray, booleanColumnFromArray, float64ColumnFromArray, stringColumnFromArray, withColumnReplaced, } from '../../columnar/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Rebuilds a mapped value array into a column of the given kind.
|
|
4
|
+
*
|
|
5
|
+
* NB: this is the same kind→builder dispatch as `fillOp`'s
|
|
6
|
+
* `buildFilledColumn`. Two callers now (fill + map) — a candidate for
|
|
7
|
+
* a shared `columnFromValuesByKind` helper in a follow-up; kept local
|
|
8
|
+
* here to keep this PR focused on the new operator.
|
|
9
|
+
*/
|
|
10
|
+
function columnFromValuesByKind(kind, values) {
|
|
11
|
+
switch (kind) {
|
|
12
|
+
case 'number':
|
|
13
|
+
return float64ColumnFromArray(values);
|
|
14
|
+
case 'string':
|
|
15
|
+
return stringColumnFromArray(values);
|
|
16
|
+
case 'boolean':
|
|
17
|
+
return booleanColumnFromArray(values);
|
|
18
|
+
case 'array':
|
|
19
|
+
return arrayColumnFromArray(values);
|
|
20
|
+
default:
|
|
21
|
+
throw new TypeError(`mapColumns: unsupported column kind '${kind}'`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* **Step 4 — column-native `mapColumns` (extracted operator).** Applies
|
|
26
|
+
* a per-cell value transform to one or more columns, straight off the
|
|
27
|
+
* columnar store: read each target's cells (storage-agnostic
|
|
28
|
+
* `read(i)`), apply the mapper to each **defined** value, rebuild the
|
|
29
|
+
* column — no `series.events` materialization, no per-row `Event`.
|
|
30
|
+
* Non-mapped columns + the key axis pass through by reference.
|
|
31
|
+
*
|
|
32
|
+
* Semantics:
|
|
33
|
+
* - **Missing cells carry.** The mapper is called only on defined
|
|
34
|
+
* values; a missing (`undefined`) cell stays missing (the mapper is
|
|
35
|
+
* not invoked).
|
|
36
|
+
* - **Numeric results must be finite.** For a `number` column, a mapper
|
|
37
|
+
* result of `NaN` or `±Infinity` throws a `RangeError` at write —
|
|
38
|
+
* matching construction intake (`assertCellKind`), which rejects
|
|
39
|
+
* non-finite numbers. This keeps packed numeric columns NaN-free, so
|
|
40
|
+
* the columnar fast-path and the row-path reducers can never diverge
|
|
41
|
+
* on the same bucket (the bug audit v2 §1.3 reproduced). A stored
|
|
42
|
+
* `NaN` *is* a defined value, so the mapper is still invoked on it —
|
|
43
|
+
* use that to clean it (map `NaN` to a finite number, or to
|
|
44
|
+
* `undefined` for a missing cell). (Array columns are not
|
|
45
|
+
* element-checked here; they don't feed the numeric reducers.)
|
|
46
|
+
* - **Same kind, schema-stable.** The mapper returns the column's own
|
|
47
|
+
* kind (the method's type enforces `(value: T) => T`), so the output
|
|
48
|
+
* column keeps its kind and the schema is unchanged. The result is
|
|
49
|
+
* rebuilt with the kind-appropriate builder.
|
|
50
|
+
*
|
|
51
|
+
* The schema is returned unchanged; the cast is the single trust
|
|
52
|
+
* boundary, and the `TimeSeries.mapColumns` method wraps the store via
|
|
53
|
+
* `#fromTrustedStore`.
|
|
54
|
+
*
|
|
55
|
+
* A mapper that — only by defeating the `(value: T) => T` type —
|
|
56
|
+
* returns `undefined`, or a value of the wrong kind (e.g. a string
|
|
57
|
+
* from a numeric mapper via an `as` cast), produces a cell the
|
|
58
|
+
* same-kind builder can't store: `columnFromValuesByKind` coerces it
|
|
59
|
+
* to missing, so the cell reads back as a gap the declared schema may
|
|
60
|
+
* not advertise. Both are type-illegal inputs, not handled specially.
|
|
61
|
+
* (A non-finite *number* from a numeric mapper is the one same-kind
|
|
62
|
+
* case that throws rather than coerces — it would corrupt the packed
|
|
63
|
+
* column, not read back as a gap.)
|
|
64
|
+
*/
|
|
65
|
+
export function mapOp(store, schema, spec) {
|
|
66
|
+
const n = store.length;
|
|
67
|
+
if (n === 0 || spec.size === 0) {
|
|
68
|
+
return { store, schema };
|
|
69
|
+
}
|
|
70
|
+
const colKind = new Map();
|
|
71
|
+
for (let i = 1; i < schema.length; i += 1) {
|
|
72
|
+
colKind.set(schema[i].name, schema[i].kind);
|
|
73
|
+
}
|
|
74
|
+
let result = store;
|
|
75
|
+
for (const [name, fn] of spec) {
|
|
76
|
+
const col = store.columns.get(name);
|
|
77
|
+
if (col === undefined)
|
|
78
|
+
continue;
|
|
79
|
+
const kind = colKind.get(name);
|
|
80
|
+
// Numeric columns reject a non-finite (NaN / ±Infinity) mapper result at
|
|
81
|
+
// write, matching construction intake (assertCellKind). A packed NaN would
|
|
82
|
+
// otherwise be unreachable-by-assumption for the reduce kernels and diverge
|
|
83
|
+
// the fast path from the row path (audit v2 §1.3). Hoisted out of the loop
|
|
84
|
+
// so non-numeric columns pay nothing.
|
|
85
|
+
const rejectNonFinite = kind === 'number';
|
|
86
|
+
const out = new Array(n);
|
|
87
|
+
for (let i = 0; i < n; i += 1) {
|
|
88
|
+
const v = col.read(i);
|
|
89
|
+
if (v === undefined) {
|
|
90
|
+
out[i] = undefined;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const mapped = fn(v);
|
|
94
|
+
if (rejectNonFinite &&
|
|
95
|
+
typeof mapped === 'number' &&
|
|
96
|
+
!Number.isFinite(mapped)) {
|
|
97
|
+
throw new RangeError(`mapColumns: the mapper for column '${name}' returned a non-finite ` +
|
|
98
|
+
`number (${mapped}) at row ${i}. Numeric columns reject NaN and ` +
|
|
99
|
+
`±Infinity at write, consistent with intake — a packed NaN would ` +
|
|
100
|
+
`diverge the fast-path and row-path reducers on the same data. ` +
|
|
101
|
+
`Map to a finite number, or to undefined for a missing cell.`);
|
|
102
|
+
}
|
|
103
|
+
out[i] = mapped;
|
|
104
|
+
}
|
|
105
|
+
result = withColumnReplaced(result, name, columnFromValuesByKind(kind, out));
|
|
106
|
+
}
|
|
107
|
+
return { store: result, schema };
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=map.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"map.js","sourceRoot":"","sources":["../../../src/batch/operators/map.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type ColumnarStore } from '../../columnar/index.js';
|
|
2
|
+
import type { SeriesSchema } from '../../schema/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* **Step 4 — column-native `shift` (extracted operator).** Shifts each
|
|
5
|
+
* target numeric column's values by `n` rows, straight off the
|
|
6
|
+
* columnar store: row `i` takes the value at row `i − n` (storage-
|
|
7
|
+
* agnostic `col.read`), or `undefined` when that source row is out of
|
|
8
|
+
* range — no `series.events` materialization, no per-row `Event`.
|
|
9
|
+
* Non-target columns + the key axis pass through by reference.
|
|
10
|
+
*
|
|
11
|
+
* `n > 0` shifts forward (a lag: the first `n` rows pad to
|
|
12
|
+
* `undefined`); `n < 0` shifts backward (a lead: the last `|n|` rows
|
|
13
|
+
* pad); `n === 0` is identity. `n` beyond the row count pads the whole
|
|
14
|
+
* column. The padding is why targets widen to optional `number` in the
|
|
15
|
+
* output schema.
|
|
16
|
+
*
|
|
17
|
+
* Returns the reshaped store + the output schema; the result-schema
|
|
18
|
+
* cast is the single trust boundary, and the `TimeSeries.shift` method
|
|
19
|
+
* wraps the store via `#fromTrustedStore`.
|
|
20
|
+
*
|
|
21
|
+
* A non-numeric target is unreachable through the typed surface
|
|
22
|
+
* (`NumericColumnNameForSchema<S>`); defeating that constraint makes the
|
|
23
|
+
* `Float64Column` replacement collide with the existing column's kind, so
|
|
24
|
+
* `withColumnReplaced`'s kind guard throws a `RangeError` naming the column
|
|
25
|
+
* — fail-fast, matching `cumulativeOp` / `diffRateOp`.
|
|
26
|
+
*/
|
|
27
|
+
export declare function shiftOp<S extends SeriesSchema, OutSchema extends SeriesSchema>(store: ColumnarStore<S>, schema: S, cols: readonly string[], n: number): {
|
|
28
|
+
store: ColumnarStore<OutSchema>;
|
|
29
|
+
schema: OutSchema;
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=shift.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shift.d.ts","sourceRoot":"","sources":["../../../src/batch/operators/shift.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,aAAa,EAInB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE1D;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,OAAO,CAAC,CAAC,SAAS,YAAY,EAAE,SAAS,SAAS,YAAY,EAC5E,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,EACvB,MAAM,EAAE,CAAC,EACT,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,CAAC,EAAE,MAAM,GACR;IAAE,KAAK,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;IAAC,MAAM,EAAE,SAAS,CAAA;CAAE,CAmCxD"}
|