@hoox-sh/pynets 0.2.0 → 0.3.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 +39 -0
- package/README.md +17 -8
- package/dist/browser.js +89 -84
- package/dist/index.js +19482 -12937
- package/package.json +1 -1
- package/src/ast/builder.ts +235 -12
- package/src/ast/nodes.ts +114 -4
- package/src/ast/unparse.ts +192 -17
- package/src/cli/rich.ts +1 -1
- package/src/generated/PinescriptLexer.interp +5 -1
- package/src/generated/PinescriptLexer.tokens +97 -95
- package/src/generated/PinescriptLexer.ts +512 -502
- package/src/generated/PinescriptParser.interp +8 -1
- package/src/generated/PinescriptParser.tokens +97 -95
- package/src/generated/PinescriptParser.ts +1907 -1287
- package/src/generated/PinescriptParserVisitor.ts +36 -1
- package/src/index.ts +1 -0
- package/src/runtime/array.ts +238 -68
- package/src/runtime/compile/collections.ts +187 -34
- package/src/runtime/compile/emit.ts +266 -9
- package/src/runtime/compile/emit_call.ts +425 -35
- package/src/runtime/compile/runtime.ts +62 -2
- package/src/runtime/compile/strategy.ts +87 -6
- package/src/runtime/compile/types.ts +2 -0
- package/src/runtime/drawings.ts +405 -8
- package/src/runtime/interpret.ts +2201 -277
- package/src/runtime/matrix.ts +371 -42
- package/src/runtime/strategy.ts +695 -33
- package/src/runtime/ta.ts +654 -20
- package/src/runtime/timeframe.ts +199 -0
package/src/runtime/matrix.ts
CHANGED
|
@@ -3,9 +3,12 @@
|
|
|
3
3
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
4
4
|
*
|
|
5
5
|
* Tiny Pine `matrix.*` value. `null` is `na`. Get is na-safe (OOB / non-finite → na).
|
|
6
|
-
* Set OOB is a no-op.
|
|
6
|
+
* Set OOB is a no-op. Slots may hold numbers, na, or UDT instances.
|
|
7
|
+
* Python `Matrix` remains the source of truth.
|
|
7
8
|
*/
|
|
8
9
|
|
|
10
|
+
import { UdtInstance } from "./udt.ts";
|
|
11
|
+
|
|
9
12
|
export type Cell = number | null;
|
|
10
13
|
|
|
11
14
|
/** Python has no matrix-size cap; bound elements like `array.set` (1e6). */
|
|
@@ -29,13 +32,176 @@ function fitsElements(rows: number, cols: number): boolean {
|
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
/** Pad with `na` or truncate so the vector has length *n*. */
|
|
32
|
-
function padOrTrunc(values:
|
|
33
|
-
const out:
|
|
35
|
+
function padOrTrunc(values: unknown[] | undefined, n: number): unknown[] {
|
|
36
|
+
const out: unknown[] = [];
|
|
34
37
|
const src = values ?? [];
|
|
35
38
|
for (let i = 0; i < n; i++) out.push(i < src.length ? src[i]! : null);
|
|
36
39
|
return out;
|
|
37
40
|
}
|
|
38
41
|
|
|
42
|
+
function looksLikeUdt(item: unknown): boolean {
|
|
43
|
+
if (item == null || typeof item !== "object") return false;
|
|
44
|
+
if (item instanceof UdtInstance) return true;
|
|
45
|
+
return !Array.isArray(item) && Object.prototype.hasOwnProperty.call(item, "__type__");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function udtFieldNames(item: object): string[] | null {
|
|
49
|
+
if (item instanceof UdtInstance) return item.type.fields.map((f) => f.name);
|
|
50
|
+
const rec = item as Record<string, unknown>;
|
|
51
|
+
if (Object.prototype.hasOwnProperty.call(rec, "__type__")) {
|
|
52
|
+
return Object.keys(rec).filter((k) => k !== "__type__");
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function readUdtField(item: object, name: string): unknown {
|
|
58
|
+
if (item instanceof UdtInstance) return item.get(name);
|
|
59
|
+
return (item as Record<string, unknown>)[name];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function udtFieldKey(item: unknown, sortField: unknown): unknown {
|
|
63
|
+
if (item == null || sortField == null || typeof item !== "object") return item;
|
|
64
|
+
if (typeof sortField === "string") return readUdtField(item, sortField);
|
|
65
|
+
if (typeof sortField === "number" && Number.isFinite(sortField)) {
|
|
66
|
+
const names = udtFieldNames(item);
|
|
67
|
+
if (names == null) return item;
|
|
68
|
+
const idx = Math.trunc(sortField);
|
|
69
|
+
if (idx < 0 || idx >= names.length) return item;
|
|
70
|
+
return readUdtField(item, names[idx]!);
|
|
71
|
+
}
|
|
72
|
+
return item;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function cellSortValue(cell: unknown, sortField: unknown): unknown {
|
|
76
|
+
return sortField != null ? udtFieldKey(cell, sortField) : cell;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function isSortNa(v: unknown): boolean {
|
|
80
|
+
if (v == null) return true;
|
|
81
|
+
if (typeof v === "number") return !Number.isFinite(v);
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function asFiniteNumber(v: unknown): number | null {
|
|
86
|
+
return typeof v === "number" && Number.isFinite(v) ? v : null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Python `isinstance(x, int | float)` — bool counts; `na` / non-finite does not. */
|
|
90
|
+
function numericCell(v: unknown): number | null {
|
|
91
|
+
if (typeof v === "boolean") return v ? 1 : 0;
|
|
92
|
+
return asFiniteNumber(v);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function sumNumeric(cells: readonly unknown[]): number {
|
|
96
|
+
let total = 0;
|
|
97
|
+
for (const x of cells) {
|
|
98
|
+
const n = numericCell(x);
|
|
99
|
+
if (n !== null) total += n;
|
|
100
|
+
}
|
|
101
|
+
return total;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Empty numeric line → 0 (Python `avg_row` / `avg_col`). */
|
|
105
|
+
function avgNumeric(cells: readonly unknown[]): number {
|
|
106
|
+
let total = 0;
|
|
107
|
+
let count = 0;
|
|
108
|
+
for (const x of cells) {
|
|
109
|
+
const n = numericCell(x);
|
|
110
|
+
if (n === null) continue;
|
|
111
|
+
total += n;
|
|
112
|
+
count += 1;
|
|
113
|
+
}
|
|
114
|
+
return count > 0 ? total / count : 0;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function extremumNumeric(cells: readonly unknown[], max: boolean): Cell {
|
|
118
|
+
let best: number | null = null;
|
|
119
|
+
for (const x of cells) {
|
|
120
|
+
const n = numericCell(x);
|
|
121
|
+
if (n === null) continue;
|
|
122
|
+
if (best === null || (max ? n > best : n < best)) best = n;
|
|
123
|
+
}
|
|
124
|
+
return best;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Python `Counter.most_common`: first-seen wins ties. Non-finite is `na`. */
|
|
128
|
+
function modeOf(values: readonly unknown[]): unknown {
|
|
129
|
+
if (values.length === 0) return null;
|
|
130
|
+
const keys: unknown[] = [];
|
|
131
|
+
const counts: number[] = [];
|
|
132
|
+
let bestI = 0;
|
|
133
|
+
let bestN = 0;
|
|
134
|
+
for (const raw of values) {
|
|
135
|
+
const key = typeof raw === "number" && !Number.isFinite(raw) ? null : (raw ?? null);
|
|
136
|
+
let idx = -1;
|
|
137
|
+
for (let i = 0; i < keys.length; i++) {
|
|
138
|
+
if (keys[i] === key) {
|
|
139
|
+
idx = i;
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (idx < 0) {
|
|
144
|
+
idx = keys.length;
|
|
145
|
+
keys.push(key);
|
|
146
|
+
counts.push(1);
|
|
147
|
+
} else {
|
|
148
|
+
counts[idx] = (counts[idx] ?? 0) + 1;
|
|
149
|
+
}
|
|
150
|
+
const n = counts[idx] ?? 0;
|
|
151
|
+
if (n > bestN) {
|
|
152
|
+
bestN = n;
|
|
153
|
+
bestI = idx;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return keys[bestI] ?? null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Sample moments (Python `statistics.variance` / `stdev`, divisor n−1). */
|
|
160
|
+
function sampleMoments(vals: readonly number[]): { variance: number; stdev: number } | null {
|
|
161
|
+
const n = vals.length;
|
|
162
|
+
if (n < 2) return null;
|
|
163
|
+
let sum = 0;
|
|
164
|
+
for (const x of vals) sum += x;
|
|
165
|
+
const mean = sum / n;
|
|
166
|
+
let ss = 0;
|
|
167
|
+
let dev = 0;
|
|
168
|
+
for (const x of vals) {
|
|
169
|
+
const d = x - mean;
|
|
170
|
+
dev += d;
|
|
171
|
+
ss += d * d;
|
|
172
|
+
}
|
|
173
|
+
ss -= (dev * dev) / n;
|
|
174
|
+
if (ss < 0) ss = 0;
|
|
175
|
+
const variance = ss / (n - 1);
|
|
176
|
+
const stdev = Math.sqrt(variance);
|
|
177
|
+
if (!Number.isFinite(variance) || !Number.isFinite(stdev)) return null;
|
|
178
|
+
return { variance, stdev };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function defaultMatrixSortField(rows: readonly unknown[][], column: number): unknown {
|
|
182
|
+
for (const row of rows) {
|
|
183
|
+
const cell = row[column];
|
|
184
|
+
if (cell == null) continue;
|
|
185
|
+
if (looksLikeUdt(cell)) return 0;
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function compareSortKeys(a: unknown, b: unknown): number {
|
|
192
|
+
if (a === b) return 0;
|
|
193
|
+
if (typeof a === "number" && typeof b === "number") {
|
|
194
|
+
if (a < b) return -1;
|
|
195
|
+
if (a > b) return 1;
|
|
196
|
+
return 0;
|
|
197
|
+
}
|
|
198
|
+
const sa = `${String(Object.prototype.toString.call(a))}${String(a)}`;
|
|
199
|
+
const sb = `${String(Object.prototype.toString.call(b))}${String(b)}`;
|
|
200
|
+
if (sa < sb) return -1;
|
|
201
|
+
if (sa > sb) return 1;
|
|
202
|
+
return 0;
|
|
203
|
+
}
|
|
204
|
+
|
|
39
205
|
function cloneGrid(src: number[][]): number[][] {
|
|
40
206
|
const out: number[][] = [];
|
|
41
207
|
for (let i = 0; i < src.length; i++) out.push(src[i]!.slice());
|
|
@@ -346,11 +512,11 @@ function nullspaceUnit(M: number[][]): number[] | null {
|
|
|
346
512
|
}
|
|
347
513
|
|
|
348
514
|
export class PineMatrix {
|
|
349
|
-
private readonly cells:
|
|
515
|
+
private readonly cells: unknown[][] = [];
|
|
350
516
|
private nRows: number;
|
|
351
517
|
private nCols: number;
|
|
352
518
|
|
|
353
|
-
constructor(rows: number, cols: number, initial?:
|
|
519
|
+
constructor(rows: number, cols: number, initial?: unknown) {
|
|
354
520
|
let r = dim(rows);
|
|
355
521
|
let c = dim(cols);
|
|
356
522
|
if (r > 0 && c > Math.floor(MAX_MATRIX_ELEMENTS / r)) {
|
|
@@ -359,9 +525,9 @@ export class PineMatrix {
|
|
|
359
525
|
}
|
|
360
526
|
this.nRows = r;
|
|
361
527
|
this.nCols = c;
|
|
362
|
-
const fill
|
|
528
|
+
const fill = initial === undefined ? null : initial;
|
|
363
529
|
for (let i = 0; i < this.nRows; i++) {
|
|
364
|
-
const row:
|
|
530
|
+
const row: unknown[] = [];
|
|
365
531
|
for (let j = 0; j < this.nCols; j++) row.push(fill);
|
|
366
532
|
this.cells.push(row);
|
|
367
533
|
}
|
|
@@ -380,18 +546,18 @@ export class PineMatrix {
|
|
|
380
546
|
const i = resolveIndex(row, this.nRows);
|
|
381
547
|
const j = resolveIndex(col, this.nCols);
|
|
382
548
|
if (i === null || j === null) return null;
|
|
383
|
-
return this.cells[i]![j]
|
|
549
|
+
return this.cells[i]![j] as Cell;
|
|
384
550
|
}
|
|
385
551
|
|
|
386
552
|
/** `matrix.set` — OOB / non-finite index is a no-op. */
|
|
387
|
-
set(row: number, col: number, value:
|
|
553
|
+
set(row: number, col: number, value: unknown): void {
|
|
388
554
|
const i = resolveIndex(row, this.nRows);
|
|
389
555
|
const j = resolveIndex(col, this.nCols);
|
|
390
556
|
if (i === null || j === null) return;
|
|
391
557
|
this.cells[i]![j] = value;
|
|
392
558
|
}
|
|
393
559
|
|
|
394
|
-
fill(value:
|
|
560
|
+
fill(value: unknown): void {
|
|
395
561
|
for (let i = 0; i < this.nRows; i++) {
|
|
396
562
|
const row = this.cells[i]!;
|
|
397
563
|
for (let j = 0; j < this.nCols; j++) row[j] = value;
|
|
@@ -408,14 +574,14 @@ export class PineMatrix {
|
|
|
408
574
|
|
|
409
575
|
row(i: number): Cell[] {
|
|
410
576
|
const r = resolveIndex(i, this.nRows);
|
|
411
|
-
return r === null ? [] : this.cells[r]!.slice();
|
|
577
|
+
return r === null ? [] : (this.cells[r]!.slice() as Cell[]);
|
|
412
578
|
}
|
|
413
579
|
|
|
414
580
|
col(j: number): Cell[] {
|
|
415
581
|
const c = resolveIndex(j, this.nCols);
|
|
416
582
|
if (c === null) return [];
|
|
417
583
|
const out: Cell[] = [];
|
|
418
|
-
for (let i = 0; i < this.nRows; i++) out.push(this.cells[i]![c]
|
|
584
|
+
for (let i = 0; i < this.nRows; i++) out.push(this.cells[i]![c] as Cell);
|
|
419
585
|
return out;
|
|
420
586
|
}
|
|
421
587
|
|
|
@@ -473,8 +639,8 @@ export class PineMatrix {
|
|
|
473
639
|
if (!this.isSquare()) return null;
|
|
474
640
|
let acc = 0;
|
|
475
641
|
for (let i = 0; i < this.nRows; i++) {
|
|
476
|
-
const v = this.cells[i]![i]
|
|
477
|
-
if (v === null
|
|
642
|
+
const v = asFiniteNumber(this.cells[i]![i]);
|
|
643
|
+
if (v === null) return null;
|
|
478
644
|
acc += v;
|
|
479
645
|
}
|
|
480
646
|
return acc;
|
|
@@ -489,8 +655,8 @@ export class PineMatrix {
|
|
|
489
655
|
for (let i = 0; i < n; i++) {
|
|
490
656
|
const row: number[] = [];
|
|
491
657
|
for (let j = 0; j < n; j++) {
|
|
492
|
-
const v = this.cells[i]![j]
|
|
493
|
-
if (v === null
|
|
658
|
+
const v = asFiniteNumber(this.cells[i]![j]);
|
|
659
|
+
if (v === null) return null;
|
|
494
660
|
row.push(v);
|
|
495
661
|
}
|
|
496
662
|
a.push(row);
|
|
@@ -536,8 +702,8 @@ export class PineMatrix {
|
|
|
536
702
|
const out = new PineMatrix(this.nRows, this.nCols);
|
|
537
703
|
for (let i = 0; i < this.nRows; i++) {
|
|
538
704
|
for (let j = 0; j < this.nCols; j++) {
|
|
539
|
-
const v = this.cells[i]![j]
|
|
540
|
-
if (v === null
|
|
705
|
+
const v = asFiniteNumber(this.cells[i]![j]);
|
|
706
|
+
if (v === null) {
|
|
541
707
|
out.set(i, j, null);
|
|
542
708
|
continue;
|
|
543
709
|
}
|
|
@@ -773,7 +939,7 @@ export class PineMatrix {
|
|
|
773
939
|
* Short rows pad `na`; long rows truncate. 0×0 adopts column count from *values*.
|
|
774
940
|
* Negative / non-finite index or size cap → no-op.
|
|
775
941
|
*/
|
|
776
|
-
addRow(index?: number, values?:
|
|
942
|
+
addRow(index?: number, values?: unknown[]): void {
|
|
777
943
|
const adopt = this.nRows === 0 && this.nCols === 0;
|
|
778
944
|
const cols = adopt ? (values?.length ?? 0) : this.nCols;
|
|
779
945
|
if (!fitsElements(this.nRows + 1, cols)) return;
|
|
@@ -793,7 +959,7 @@ export class PineMatrix {
|
|
|
793
959
|
* Insert a column at *index* (append if omitted / past end).
|
|
794
960
|
* Short cols pad `na`; long cols truncate. 0×0 becomes N×1 from *values*.
|
|
795
961
|
*/
|
|
796
|
-
addCol(index?: number, values?:
|
|
962
|
+
addCol(index?: number, values?: unknown[]): void {
|
|
797
963
|
if (this.nRows === 0 && this.nCols === 0) {
|
|
798
964
|
const n = values?.length ?? 0;
|
|
799
965
|
if (!fitsElements(n, n === 0 ? 0 : 1)) return;
|
|
@@ -891,35 +1057,60 @@ export class PineMatrix {
|
|
|
891
1057
|
for (const row of this.cells) row.reverse();
|
|
892
1058
|
}
|
|
893
1059
|
|
|
894
|
-
/** Sort rows by *column*. `na`
|
|
895
|
-
sort(column = 0, order: "asc" | "desc" = "asc"): void {
|
|
1060
|
+
/** Sort rows by *column*. `na` last. Optional *sortField* keys UDT cells. */
|
|
1061
|
+
sort(column = 0, order: "asc" | "desc" = "asc", sortField?: unknown): void {
|
|
896
1062
|
if (this.nRows === 0) return;
|
|
897
1063
|
const c = resolveIndex(column, this.nCols);
|
|
898
1064
|
if (c === null) return;
|
|
899
1065
|
const desc = order === "desc";
|
|
900
|
-
const
|
|
901
|
-
const
|
|
1066
|
+
const field = sortField ?? defaultMatrixSortField(this.cells, c);
|
|
1067
|
+
const nonNa: unknown[][] = [];
|
|
1068
|
+
const naRows: unknown[][] = [];
|
|
902
1069
|
for (const row of this.cells) {
|
|
903
|
-
const v = row[c]
|
|
904
|
-
if (
|
|
1070
|
+
const v = cellSortValue(row[c], field);
|
|
1071
|
+
if (isSortNa(v)) naRows.push(row);
|
|
905
1072
|
else nonNa.push(row);
|
|
906
1073
|
}
|
|
907
1074
|
nonNa.sort((a, b) => {
|
|
908
|
-
const
|
|
909
|
-
|
|
1075
|
+
const ka = cellSortValue(a[c], field);
|
|
1076
|
+
const kb = cellSortValue(b[c], field);
|
|
1077
|
+
const cmp = compareSortKeys(ka, kb);
|
|
1078
|
+
return desc ? -cmp : cmp;
|
|
910
1079
|
});
|
|
911
1080
|
this.cells.length = 0;
|
|
912
1081
|
for (const row of nonNa) this.cells.push(row);
|
|
913
1082
|
for (const row of naRows) this.cells.push(row);
|
|
914
1083
|
}
|
|
915
1084
|
|
|
1085
|
+
/** Row indices that would sort by *column*; `na` indices last. */
|
|
1086
|
+
sortIndices(column = 0, order: "asc" | "desc" = "asc", sortField?: unknown): number[] {
|
|
1087
|
+
if (this.nRows === 0) return [];
|
|
1088
|
+
const c = resolveIndex(column, this.nCols);
|
|
1089
|
+
if (c === null) return [];
|
|
1090
|
+
const desc = order === "desc";
|
|
1091
|
+
const field = sortField ?? defaultMatrixSortField(this.cells, c);
|
|
1092
|
+
const nonNa: { key: unknown; idx: number }[] = [];
|
|
1093
|
+
const naIdx: number[] = [];
|
|
1094
|
+
for (let i = 0; i < this.nRows; i++) {
|
|
1095
|
+
const v = cellSortValue(this.cells[i]![c], field);
|
|
1096
|
+
if (isSortNa(v)) naIdx.push(i);
|
|
1097
|
+
else nonNa.push({ key: v, idx: i });
|
|
1098
|
+
}
|
|
1099
|
+
nonNa.sort((a, b) => {
|
|
1100
|
+
const cmp = compareSortKeys(a.key, b.key);
|
|
1101
|
+
if (cmp !== 0) return desc ? -cmp : cmp;
|
|
1102
|
+
return a.idx - b.idx;
|
|
1103
|
+
});
|
|
1104
|
+
return nonNa.map((x) => x.idx).concat(naIdx);
|
|
1105
|
+
}
|
|
1106
|
+
|
|
916
1107
|
/** Median of finite cells (Python skips `na`). Empty → `na`. */
|
|
917
1108
|
median(): Cell {
|
|
918
1109
|
const vals: number[] = [];
|
|
919
1110
|
for (let i = 0; i < this.nRows; i++) {
|
|
920
1111
|
for (let j = 0; j < this.nCols; j++) {
|
|
921
|
-
const v = this.cells[i]![j]
|
|
922
|
-
if (v !== null
|
|
1112
|
+
const v = asFiniteNumber(this.cells[i]![j]);
|
|
1113
|
+
if (v !== null) vals.push(v);
|
|
923
1114
|
}
|
|
924
1115
|
}
|
|
925
1116
|
if (vals.length === 0) return null;
|
|
@@ -936,8 +1127,8 @@ export class PineMatrix {
|
|
|
936
1127
|
let bestCount = 0;
|
|
937
1128
|
for (let i = 0; i < this.nRows; i++) {
|
|
938
1129
|
for (let j = 0; j < this.nCols; j++) {
|
|
939
|
-
const v = this.cells[i]![j]
|
|
940
|
-
if (v === null
|
|
1130
|
+
const v = asFiniteNumber(this.cells[i]![j]);
|
|
1131
|
+
if (v === null) continue;
|
|
941
1132
|
const n = (counts.get(v) ?? 0) + 1;
|
|
942
1133
|
counts.set(v, n);
|
|
943
1134
|
if (n > bestCount) {
|
|
@@ -949,6 +1140,114 @@ export class PineMatrix {
|
|
|
949
1140
|
return best;
|
|
950
1141
|
}
|
|
951
1142
|
|
|
1143
|
+
/** Copy of the row. OOB → empty (same as `row`). */
|
|
1144
|
+
copyRow(index: number): Cell[] {
|
|
1145
|
+
return this.row(index);
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
/** Copy of the column. OOB → empty (same as `col`). */
|
|
1149
|
+
copyCol(index: number): Cell[] {
|
|
1150
|
+
return this.col(index);
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
/** Sum of numeric cells. Skips `na`. No numeric cells → 0. OOB → `na`. */
|
|
1154
|
+
sumRow(index: number): Cell {
|
|
1155
|
+
const line = this.lineAt("row", index);
|
|
1156
|
+
return line === null ? null : sumNumeric(line);
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
sumCol(index: number): Cell {
|
|
1160
|
+
const line = this.lineAt("col", index);
|
|
1161
|
+
return line === null ? null : sumNumeric(line);
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
/** Mean of numeric cells. Skips `na`. No numeric cells → 0. OOB → `na`. */
|
|
1165
|
+
avgRow(index: number): Cell {
|
|
1166
|
+
const line = this.lineAt("row", index);
|
|
1167
|
+
return line === null ? null : avgNumeric(line);
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
avgCol(index: number): Cell {
|
|
1171
|
+
const line = this.lineAt("col", index);
|
|
1172
|
+
return line === null ? null : avgNumeric(line);
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
/** Min numeric cell. Skips `na`. None → `na`. OOB → `na`. */
|
|
1176
|
+
minRow(index: number): Cell {
|
|
1177
|
+
const line = this.lineAt("row", index);
|
|
1178
|
+
return line === null ? null : extremumNumeric(line, false);
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
minCol(index: number): Cell {
|
|
1182
|
+
const line = this.lineAt("col", index);
|
|
1183
|
+
return line === null ? null : extremumNumeric(line, false);
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
maxRow(index: number): Cell {
|
|
1187
|
+
const line = this.lineAt("row", index);
|
|
1188
|
+
return line === null ? null : extremumNumeric(line, true);
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
maxCol(index: number): Cell {
|
|
1192
|
+
const line = this.lineAt("col", index);
|
|
1193
|
+
return line === null ? null : extremumNumeric(line, true);
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
/** Most common cell in the row, including `na`. Empty / OOB → `na`. */
|
|
1197
|
+
modeRow(index: number): unknown {
|
|
1198
|
+
const line = this.lineAt("row", index);
|
|
1199
|
+
return line === null ? null : modeOf(line);
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
modeCol(index: number): unknown {
|
|
1203
|
+
const line = this.lineAt("col", index);
|
|
1204
|
+
return line === null ? null : modeOf(line);
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
/** Most common cell, row-major, including `na` (Python `Counter`). Empty → `na`. */
|
|
1208
|
+
modeAll(): unknown {
|
|
1209
|
+
return modeOf(this.flatCells());
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
/** OOB / non-finite index is a no-op. */
|
|
1213
|
+
fillRow(index: number, value: unknown): void {
|
|
1214
|
+
const i = resolveIndex(index, this.nRows);
|
|
1215
|
+
if (i === null) return;
|
|
1216
|
+
const row = this.cells[i]!;
|
|
1217
|
+
for (let j = 0; j < this.nCols; j++) row[j] = value;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
/** OOB / non-finite index is a no-op. */
|
|
1221
|
+
fillCol(index: number, value: unknown): void {
|
|
1222
|
+
const j = resolveIndex(index, this.nCols);
|
|
1223
|
+
if (j === null) return;
|
|
1224
|
+
for (let i = 0; i < this.nRows; i++) this.cells[i]![j] = value;
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
/** Main diagonal, `min(rows, columns)` long. */
|
|
1228
|
+
fillDiagonal(value: unknown): void {
|
|
1229
|
+
const n = Math.min(this.nRows, this.nCols);
|
|
1230
|
+
for (let i = 0; i < n; i++) this.cells[i]![i] = value;
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
reverseRows(): void {
|
|
1234
|
+
this.cells.reverse();
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
reverseCols(): void {
|
|
1238
|
+
for (const row of this.cells) row.reverse();
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
/** Sample stdev of numeric cells (n−1). Skips `na`. Fewer than 2 → `na`. */
|
|
1242
|
+
stdev(): Cell {
|
|
1243
|
+
return sampleMoments(this.numericFlat())?.stdev ?? null;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
/** Sample variance of numeric cells (n−1). Skips `na`. Fewer than 2 → `na`. */
|
|
1247
|
+
variance(): Cell {
|
|
1248
|
+
return sampleMoments(this.numericFlat())?.variance ?? null;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
952
1251
|
/** Every cell is 0 or 1. `na` / other values → false. Empty is true. */
|
|
953
1252
|
isBinary(): boolean {
|
|
954
1253
|
for (let i = 0; i < this.nRows; i++) {
|
|
@@ -965,8 +1264,8 @@ export class PineMatrix {
|
|
|
965
1264
|
for (let i = 0; i < this.nRows; i++) {
|
|
966
1265
|
let total = 0;
|
|
967
1266
|
for (let j = 0; j < this.nCols; j++) {
|
|
968
|
-
const v = this.cells[i]![j]
|
|
969
|
-
if (v === null ||
|
|
1267
|
+
const v = asFiniteNumber(this.cells[i]![j]);
|
|
1268
|
+
if (v === null || v < 0) return false;
|
|
970
1269
|
total += v;
|
|
971
1270
|
}
|
|
972
1271
|
if (Math.abs(total - 1) > 1e-9) return false;
|
|
@@ -994,9 +1293,9 @@ export class PineMatrix {
|
|
|
994
1293
|
const out = new PineMatrix(this.nRows, this.nCols);
|
|
995
1294
|
for (let i = 0; i < this.nRows; i++) {
|
|
996
1295
|
for (let j = 0; j < this.nCols; j++) {
|
|
997
|
-
const a = this.cells[i]![j]
|
|
998
|
-
const b = other.cells[i]![j]
|
|
999
|
-
if (a === null || b === null
|
|
1296
|
+
const a = asFiniteNumber(this.cells[i]![j]);
|
|
1297
|
+
const b = asFiniteNumber(other.cells[i]![j]);
|
|
1298
|
+
if (a === null || b === null) {
|
|
1000
1299
|
out.set(i, j, null);
|
|
1001
1300
|
continue;
|
|
1002
1301
|
}
|
|
@@ -1123,8 +1422,8 @@ export class PineMatrix {
|
|
|
1123
1422
|
for (let i = 0; i < this.nRows; i++) {
|
|
1124
1423
|
const row: number[] = [];
|
|
1125
1424
|
for (let j = 0; j < this.nCols; j++) {
|
|
1126
|
-
const v = this.cells[i]![j]
|
|
1127
|
-
if (v === null
|
|
1425
|
+
const v = asFiniteNumber(this.cells[i]![j]);
|
|
1426
|
+
if (v === null) return null;
|
|
1128
1427
|
row.push(v);
|
|
1129
1428
|
}
|
|
1130
1429
|
out.push(row);
|
|
@@ -1136,8 +1435,8 @@ export class PineMatrix {
|
|
|
1136
1435
|
const out: number[] = [];
|
|
1137
1436
|
for (let i = 0; i < this.nRows; i++) {
|
|
1138
1437
|
for (let j = 0; j < this.nCols; j++) {
|
|
1139
|
-
const v = this.cells[i]![j]
|
|
1140
|
-
if (v === null
|
|
1438
|
+
const v = asFiniteNumber(this.cells[i]![j]);
|
|
1439
|
+
if (v === null) return null;
|
|
1141
1440
|
out.push(v);
|
|
1142
1441
|
}
|
|
1143
1442
|
}
|
|
@@ -1147,7 +1446,37 @@ export class PineMatrix {
|
|
|
1147
1446
|
private flatten(): Cell[] {
|
|
1148
1447
|
const out: Cell[] = [];
|
|
1149
1448
|
for (let i = 0; i < this.nRows; i++) {
|
|
1150
|
-
for (let j = 0; j < this.nCols; j++) out.push(this.cells[i]![j]
|
|
1449
|
+
for (let j = 0; j < this.nCols; j++) out.push(this.cells[i]![j] as Cell);
|
|
1450
|
+
}
|
|
1451
|
+
return out;
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
/** In-range line, or `null` when the index is OOB / non-finite. */
|
|
1455
|
+
private lineAt(axis: "row" | "col", index: number): readonly unknown[] | null {
|
|
1456
|
+
if (axis === "row") {
|
|
1457
|
+
const i = resolveIndex(index, this.nRows);
|
|
1458
|
+
return i === null ? null : this.cells[i]!;
|
|
1459
|
+
}
|
|
1460
|
+
const j = resolveIndex(index, this.nCols);
|
|
1461
|
+
if (j === null) return null;
|
|
1462
|
+
const out: unknown[] = [];
|
|
1463
|
+
for (let i = 0; i < this.nRows; i++) out.push(this.cells[i]![j]);
|
|
1464
|
+
return out;
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
private flatCells(): unknown[] {
|
|
1468
|
+
const out: unknown[] = [];
|
|
1469
|
+
for (let i = 0; i < this.nRows; i++) {
|
|
1470
|
+
for (let j = 0; j < this.nCols; j++) out.push(this.cells[i]![j]);
|
|
1471
|
+
}
|
|
1472
|
+
return out;
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
private numericFlat(): number[] {
|
|
1476
|
+
const out: number[] = [];
|
|
1477
|
+
for (const v of this.flatCells()) {
|
|
1478
|
+
const n = numericCell(v);
|
|
1479
|
+
if (n !== null) out.push(n);
|
|
1151
1480
|
}
|
|
1152
1481
|
return out;
|
|
1153
1482
|
}
|