df-script 1.4.0 → 1.5.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/README.md +83 -34
- package/dist/api.d.ts +16 -8
- package/dist/api.js +11 -3
- package/dist/columnExpressions/ColumnExpr.d.ts +26 -364
- package/dist/columnExpressions/ColumnExpr.js +199 -3
- package/dist/columnExpressions/ExprBase.d.ts +8 -11
- package/dist/columnExpressions/ExprBase.js +20 -61
- package/dist/columnExpressions/constants.d.ts +2 -0
- package/dist/columnExpressions/constants.js +3 -1
- package/dist/columnExpressions/functions/coalesce.js +8 -4
- package/dist/columnExpressions/functions/element.d.ts +5 -0
- package/dist/columnExpressions/functions/element.js +11 -0
- package/dist/columnExpressions/functions/implode.d.ts +3 -0
- package/dist/columnExpressions/functions/implode.js +7 -0
- package/dist/columnExpressions/functions/lit.d.ts +3 -1
- package/dist/columnExpressions/functions/lit.js +8 -13
- package/dist/columnExpressions/functions/repeat.d.ts +47 -0
- package/dist/columnExpressions/functions/repeat.js +131 -0
- package/dist/columnExpressions/functions/seq_range.d.ts +31 -0
- package/dist/columnExpressions/functions/seq_range.js +91 -0
- package/dist/columnExpressions/functions/when.js +8 -4
- package/dist/columnExpressions/index.d.ts +5 -2
- package/dist/columnExpressions/index.js +5 -48
- package/dist/columnExpressions/mixins/AggregationExpr.d.ts +25 -50
- package/dist/columnExpressions/mixins/AggregationExpr.js +69 -165
- package/dist/columnExpressions/mixins/ArithmeticExpr.d.ts +41 -66
- package/dist/columnExpressions/mixins/ArithmeticExpr.js +126 -138
- package/dist/columnExpressions/mixins/BooleanExpr.d.ts +40 -0
- package/dist/columnExpressions/mixins/BooleanExpr.js +67 -0
- package/dist/columnExpressions/mixins/ComparisonExpr.d.ts +31 -60
- package/dist/columnExpressions/mixins/ComparisonExpr.js +231 -370
- package/dist/columnExpressions/mixins/ListExpr.d.ts +15 -39
- package/dist/columnExpressions/mixins/ListExpr.js +124 -148
- package/dist/columnExpressions/mixins/LogicalExpr.d.ts +7 -33
- package/dist/columnExpressions/mixins/LogicalExpr.js +48 -49
- package/dist/columnExpressions/mixins/ManipulationExpr.d.ts +6 -31
- package/dist/columnExpressions/mixins/ManipulationExpr.js +61 -25
- package/dist/columnExpressions/mixins/NumericExpr.d.ts +92 -0
- package/dist/columnExpressions/mixins/NumericExpr.js +259 -0
- package/dist/columnExpressions/mixins/StringExpr.d.ts +5 -30
- package/dist/columnExpressions/mixins/StringExpr.js +37 -49
- package/dist/columnExpressions/mixins/StructExpr.d.ts +16 -0
- package/dist/columnExpressions/mixins/StructExpr.js +139 -0
- package/dist/columnExpressions/mixins/TemporalExpr.d.ts +4 -30
- package/dist/columnExpressions/mixins/TemporalExpr.js +28 -29
- package/dist/columnExpressions/mixins/WindowExpr.d.ts +27 -49
- package/dist/columnExpressions/mixins/WindowExpr.js +144 -274
- package/dist/columnExpressions/utils.d.ts +4 -0
- package/dist/columnExpressions/utils.js +45 -0
- package/dist/dataframe/constants.d.ts +3 -0
- package/dist/dataframe/constants.js +4 -1
- package/dist/dataframe/dataframe.d.ts +27 -10
- package/dist/dataframe/dataframe.js +461 -220
- package/dist/dataframe/grouped/grouped.d.ts +2 -3
- package/dist/dataframe/grouped/grouped.js +17 -16
- package/dist/dataframe/types.d.ts +65 -1
- package/dist/dataframe/utils.d.ts +11 -3
- package/dist/dataframe/utils.js +101 -9
- package/dist/datatypes/DataType.d.ts +2 -1
- package/dist/datatypes/DataType.js +29 -0
- package/dist/datatypes/index.d.ts +2 -2
- package/dist/datatypes/types.d.ts +37 -36
- package/dist/datatypes/types.js +15 -11
- package/dist/exceptions/index.d.ts +2 -0
- package/dist/exceptions/index.js +4 -1
- package/dist/exceptions/utils.d.ts +1 -0
- package/dist/exceptions/utils.js +19 -0
- package/dist/functions/concat.d.ts +1 -2
- package/dist/functions/concat.js +24 -12
- package/dist/functions/index.d.ts +3 -1
- package/dist/functions/index.js +7 -15
- package/dist/functions/read_csv.d.ts +8 -0
- package/dist/functions/read_csv.js +53 -0
- package/dist/functions/read_json.d.ts +10 -0
- package/dist/functions/read_json.js +21 -0
- package/dist/functions/transpose.d.ts +7 -0
- package/dist/functions/transpose.js +84 -0
- package/dist/index.js +6 -1
- package/dist/types.d.ts +31 -5
- package/dist/utils/csv.d.ts +77 -0
- package/dist/utils/csv.js +313 -0
- package/dist/utils/date.js +5 -6
- package/dist/utils/guards.d.ts +0 -2
- package/dist/utils/guards.js +0 -31
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +2 -0
- package/dist/utils/json.d.ts +120 -2
- package/dist/utils/json.js +204 -20
- package/dist/utils/list.d.ts +173 -1
- package/dist/utils/list.js +255 -22
- package/dist/utils/number.d.ts +61 -28
- package/dist/utils/number.js +248 -70
- package/dist/utils/string.d.ts +5 -0
- package/dist/utils/string.js +89 -0
- package/package.json +5 -1
package/dist/utils/number.js
CHANGED
|
@@ -1,32 +1,39 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.BIGINT_RANGES = exports.INT_RANGES =
|
|
3
|
+
exports.BIGINT_RANGES = exports.INT_RANGES = void 0;
|
|
4
4
|
exports.isValidNumber = isValidNumber;
|
|
5
5
|
exports.toValidNumber = toValidNumber;
|
|
6
|
-
exports.
|
|
6
|
+
exports.formatNumber = formatNumber;
|
|
7
7
|
exports.isValidFloat = isValidFloat;
|
|
8
8
|
exports.toValidFloat = toValidFloat;
|
|
9
9
|
exports.isValidInt = isValidInt;
|
|
10
|
-
exports.isValidBigInt = isValidBigInt;
|
|
11
10
|
exports.toValidInt = toValidInt;
|
|
11
|
+
exports.isValidBigInt = isValidBigInt;
|
|
12
12
|
exports.toValidBigInt = toValidBigInt;
|
|
13
13
|
exports.isValidDecimal = isValidDecimal;
|
|
14
14
|
exports.toValidDecimal = toValidDecimal;
|
|
15
|
+
exports.clamp = clamp;
|
|
15
16
|
exports.mulberry32 = mulberry32;
|
|
17
|
+
exports.computeMedian = computeMedian;
|
|
18
|
+
exports.computeQuantile = computeQuantile;
|
|
19
|
+
exports.computeMode = computeMode;
|
|
20
|
+
const guards_1 = require("./guards");
|
|
21
|
+
const list_1 = require("./list");
|
|
16
22
|
const NUMERIC_CLEAN_REGEX = /[,\s_]/g;
|
|
17
23
|
const VALID_DECIMAL_REGEX = /^[+-]?\d+(?:\.\d+)?$/;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
function isValidNumber(v, options) {
|
|
25
|
+
if (typeof v !== "number")
|
|
26
|
+
return false;
|
|
27
|
+
if (options && options.allowNonFiniteNumbers)
|
|
28
|
+
return true;
|
|
29
|
+
return !Number.isNaN(v) && Number.isFinite(v);
|
|
23
30
|
}
|
|
24
|
-
function toValidNumber(v) {
|
|
31
|
+
function toValidNumber(v, options) {
|
|
25
32
|
if (v == null)
|
|
26
33
|
return null;
|
|
27
34
|
if (typeof v === "symbol")
|
|
28
35
|
return null;
|
|
29
|
-
if (isValidNumber(v)) {
|
|
36
|
+
if (isValidNumber(v, options)) {
|
|
30
37
|
return v;
|
|
31
38
|
}
|
|
32
39
|
if (typeof v === "boolean") {
|
|
@@ -34,51 +41,104 @@ function toValidNumber(v) {
|
|
|
34
41
|
}
|
|
35
42
|
if (typeof v === "bigint") {
|
|
36
43
|
const n = Number(v);
|
|
37
|
-
return isValidNumber(n) ? n : null;
|
|
44
|
+
return isValidNumber(n, options) ? n : null;
|
|
38
45
|
}
|
|
39
46
|
if (v instanceof Date) {
|
|
40
47
|
const t = v.getTime();
|
|
41
|
-
return isValidNumber(t) ? t : null;
|
|
48
|
+
return isValidNumber(t, options) ? t : null;
|
|
42
49
|
}
|
|
43
50
|
if (typeof v === "string") {
|
|
44
51
|
const clean = v.trim().replace(NUMERIC_CLEAN_REGEX, "");
|
|
45
52
|
if (clean === "")
|
|
46
53
|
return null;
|
|
47
54
|
const n = Number(clean);
|
|
48
|
-
|
|
55
|
+
if (Number.isNaN(n)) {
|
|
56
|
+
if (options?.allowNonFiniteNumbers && clean.toLowerCase() === "nan") {
|
|
57
|
+
return NaN;
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
return isValidNumber(n, options) ? n : null;
|
|
49
62
|
}
|
|
50
63
|
return null;
|
|
51
64
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Formats any numeric value (number, bigint, numeric string, Date, etc.) for output,
|
|
67
|
+
* handling NaN, Infinity, scientific notation, precision, and accounting formats.
|
|
68
|
+
*/
|
|
69
|
+
function formatNumber({ locale = "en-US", accountingNegatives = false, fallback = "NaN", ...intlOpts } = {}) {
|
|
70
|
+
if (intlOpts.useGrouping === undefined) {
|
|
71
|
+
intlOpts.useGrouping = false;
|
|
72
|
+
}
|
|
73
|
+
if (intlOpts.maximumFractionDigits === undefined && intlOpts.maximumSignificantDigits === undefined) {
|
|
74
|
+
intlOpts.maximumFractionDigits = 20;
|
|
75
|
+
}
|
|
76
|
+
const formatter = new Intl.NumberFormat(locale, intlOpts);
|
|
77
|
+
return (value) => {
|
|
78
|
+
if (typeof value === "bigint") {
|
|
79
|
+
if (accountingNegatives && value < 0n) {
|
|
80
|
+
return `(${formatter.format(-value)})`;
|
|
81
|
+
}
|
|
82
|
+
return formatter.format(value);
|
|
83
|
+
}
|
|
84
|
+
const num = toValidNumber(value, { allowNonFiniteNumbers: true });
|
|
85
|
+
if (num === null || Number.isNaN(num)) {
|
|
86
|
+
return fallback;
|
|
87
|
+
}
|
|
88
|
+
if (!Number.isFinite(num)) {
|
|
89
|
+
if (accountingNegatives && num === -Infinity) {
|
|
90
|
+
return "(Infinity)";
|
|
91
|
+
}
|
|
92
|
+
return String(num);
|
|
93
|
+
}
|
|
94
|
+
if (accountingNegatives && num < 0) {
|
|
95
|
+
return `(${formatter.format(Math.abs(num))})`;
|
|
96
|
+
}
|
|
97
|
+
return formatter.format(num);
|
|
98
|
+
};
|
|
59
99
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
|
|
100
|
+
function isValidFloat(v, { floatPrecision, allowNonFiniteNumbers = false, floatScientific = false } = {}) {
|
|
101
|
+
let num;
|
|
102
|
+
if (typeof v === "number") {
|
|
103
|
+
num = v;
|
|
104
|
+
}
|
|
105
|
+
else if (floatScientific && typeof v === "string") {
|
|
106
|
+
const parsed = parseFloat(v);
|
|
107
|
+
if (Number.isNaN(parsed))
|
|
108
|
+
return false;
|
|
109
|
+
num = parsed;
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
if (floatPrecision === "Float32") {
|
|
115
|
+
num = Math.fround(num);
|
|
116
|
+
}
|
|
117
|
+
if (!allowNonFiniteNumbers && (Number.isNaN(num) || !Number.isFinite(num))) {
|
|
67
118
|
return false;
|
|
68
|
-
if (precision) {
|
|
69
|
-
const limits = exports.FLOAT_RANGES[precision];
|
|
70
|
-
return v >= limits.min && v <= limits.max;
|
|
71
119
|
}
|
|
72
120
|
return true;
|
|
73
121
|
}
|
|
74
|
-
function toValidFloat(v,
|
|
75
|
-
|
|
122
|
+
function toValidFloat(v, { floatPrecision = "Float64", allowNonFiniteNumbers = true, floatScientific = false } = {}) {
|
|
123
|
+
let num = toValidNumber(v, { allowNonFiniteNumbers });
|
|
124
|
+
if (num === null && floatScientific && typeof v === "string") {
|
|
125
|
+
const parsed = parseFloat(v);
|
|
126
|
+
if (isValidNumber(parsed, { allowNonFiniteNumbers })) {
|
|
127
|
+
num = parsed;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
76
130
|
if (num === null)
|
|
77
131
|
return null;
|
|
78
|
-
|
|
132
|
+
if (floatPrecision === "Float32") {
|
|
133
|
+
num = Math.fround(num);
|
|
134
|
+
}
|
|
135
|
+
if (!allowNonFiniteNumbers && !Number.isFinite(num)) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
return num;
|
|
79
139
|
}
|
|
80
140
|
// ============================================================================
|
|
81
|
-
// /** Integer
|
|
141
|
+
// /** Integer Functions */
|
|
82
142
|
// ============================================================================
|
|
83
143
|
exports.INT_RANGES = {
|
|
84
144
|
Int8: { min: -128, max: 127 },
|
|
@@ -88,10 +148,6 @@ exports.INT_RANGES = {
|
|
|
88
148
|
UInt16: { min: 0, max: 65535 },
|
|
89
149
|
UInt32: { min: 0, max: 4294967295 }
|
|
90
150
|
};
|
|
91
|
-
exports.BIGINT_RANGES = {
|
|
92
|
-
Int64: { min: -9223372036854775808n, max: 9223372036854775807n },
|
|
93
|
-
UInt64: { min: 0n, max: 18446744073709551615n }
|
|
94
|
-
};
|
|
95
151
|
function isValidInt(v, range) {
|
|
96
152
|
if (!isValidNumber(v))
|
|
97
153
|
return false;
|
|
@@ -102,20 +158,11 @@ function isValidInt(v, range) {
|
|
|
102
158
|
const limits = typeof range === "string" ? exports.INT_RANGES[range] : range;
|
|
103
159
|
return v >= limits.min && v <= limits.max;
|
|
104
160
|
}
|
|
105
|
-
function
|
|
106
|
-
if (typeof v !== "bigint")
|
|
107
|
-
return false;
|
|
108
|
-
if (!range)
|
|
109
|
-
return true;
|
|
110
|
-
const limits = typeof range === "string" ? exports.BIGINT_RANGES[range] : range;
|
|
111
|
-
return v >= limits.min && v <= limits.max;
|
|
112
|
-
}
|
|
113
|
-
function toValidInt(v, range = "Int32", opts = {}) {
|
|
161
|
+
function toValidInt(v, { range = "Int32", coerce = "truncate" } = {}) {
|
|
114
162
|
const num = toValidNumber(v);
|
|
115
163
|
if (num === null)
|
|
116
164
|
return null;
|
|
117
165
|
let n = num;
|
|
118
|
-
const coerce = opts.coerce || "truncate";
|
|
119
166
|
switch (coerce) {
|
|
120
167
|
case "round":
|
|
121
168
|
n = Math.round(n);
|
|
@@ -131,9 +178,24 @@ function toValidInt(v, range = "Int32", opts = {}) {
|
|
|
131
178
|
break;
|
|
132
179
|
}
|
|
133
180
|
const limits = typeof range === "string" ? exports.INT_RANGES[range] : range;
|
|
134
|
-
return clamp(
|
|
181
|
+
return clamp(n, { min: limits.min, max: limits.max });
|
|
182
|
+
}
|
|
183
|
+
// ============================================================================
|
|
184
|
+
// /** BigInt Functions */
|
|
185
|
+
// ============================================================================
|
|
186
|
+
exports.BIGINT_RANGES = {
|
|
187
|
+
Int64: { min: -9223372036854775808n, max: 9223372036854775807n },
|
|
188
|
+
UInt64: { min: 0n, max: 18446744073709551615n }
|
|
189
|
+
};
|
|
190
|
+
function isValidBigInt(v, range) {
|
|
191
|
+
if (typeof v !== "bigint")
|
|
192
|
+
return false;
|
|
193
|
+
if (!range)
|
|
194
|
+
return true;
|
|
195
|
+
const limits = typeof range === "string" ? exports.BIGINT_RANGES[range] : range;
|
|
196
|
+
return v >= limits.min && v <= limits.max;
|
|
135
197
|
}
|
|
136
|
-
function toValidBigInt(v, range = "Int64") {
|
|
198
|
+
function toValidBigInt(v, { range = "Int64", truncate = false } = {}) {
|
|
137
199
|
if (v == null)
|
|
138
200
|
return null;
|
|
139
201
|
if (typeof v === "symbol")
|
|
@@ -150,64 +212,104 @@ function toValidBigInt(v, range = "Int64") {
|
|
|
150
212
|
if (clean === "")
|
|
151
213
|
return null;
|
|
152
214
|
if (!VALID_DECIMAL_REGEX.test(clean)) {
|
|
153
|
-
const num = toValidNumber(
|
|
215
|
+
const num = toValidNumber(clean);
|
|
154
216
|
if (num === null)
|
|
155
217
|
return null;
|
|
218
|
+
if (!truncate && !Number.isInteger(num))
|
|
219
|
+
return null;
|
|
156
220
|
bigintVal = BigInt(Math.trunc(num));
|
|
157
221
|
}
|
|
158
222
|
else {
|
|
159
|
-
|
|
223
|
+
if (!truncate && clean.includes("."))
|
|
224
|
+
return null;
|
|
225
|
+
const dotIdx = clean.indexOf(".");
|
|
226
|
+
bigintVal = BigInt(dotIdx !== -1 ? clean.slice(0, dotIdx) : clean);
|
|
160
227
|
}
|
|
161
228
|
}
|
|
162
229
|
else {
|
|
163
230
|
const num = toValidNumber(v);
|
|
164
231
|
if (num === null)
|
|
165
232
|
return null;
|
|
233
|
+
if (!truncate && !Number.isInteger(num))
|
|
234
|
+
return null;
|
|
166
235
|
bigintVal = BigInt(Math.trunc(num));
|
|
167
236
|
}
|
|
168
237
|
const limits = typeof range === "string" ? exports.BIGINT_RANGES[range] : range;
|
|
169
|
-
return clamp(
|
|
238
|
+
return clamp(bigintVal, { min: limits.min, max: limits.max });
|
|
239
|
+
}
|
|
240
|
+
function getDecimalMaxVal(precision, scale) {
|
|
241
|
+
const integerDigits = precision - scale;
|
|
242
|
+
const maxVal = Math.pow(10, integerDigits) - Math.pow(10, -scale);
|
|
243
|
+
return maxVal > 0 ? maxVal : null;
|
|
170
244
|
}
|
|
171
|
-
function
|
|
245
|
+
function roundToScale(v, scale) {
|
|
246
|
+
const str = v.toString();
|
|
247
|
+
if (str.includes("e")) {
|
|
248
|
+
const factor = Math.pow(10, scale);
|
|
249
|
+
return Math.round(v * factor) / factor;
|
|
250
|
+
}
|
|
251
|
+
return Number(Math.round(Number(str + "e" + scale)) + "e-" + scale);
|
|
252
|
+
}
|
|
253
|
+
function isValidDecimal(v, { precision, scale = 0 } = {}) {
|
|
172
254
|
if (!isValidNumber(v))
|
|
173
255
|
return false;
|
|
174
|
-
const { precision, scale = 0 } = opts;
|
|
175
256
|
if (precision !== undefined) {
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
if (integerDigits > 0 && Math.abs(v) > maxVal) {
|
|
257
|
+
const maxVal = getDecimalMaxVal(precision, scale);
|
|
258
|
+
if (maxVal !== null && Math.abs(v) > maxVal) {
|
|
179
259
|
return false;
|
|
180
260
|
}
|
|
181
261
|
}
|
|
182
262
|
if (scale !== undefined) {
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
if (dotIdx !== -1 && str.slice(dotIdx + 1).length > scale) {
|
|
263
|
+
const rounded = roundToScale(v, scale);
|
|
264
|
+
if (Math.abs(v - rounded) > 1e-12) {
|
|
186
265
|
return false;
|
|
187
266
|
}
|
|
188
267
|
}
|
|
189
268
|
return true;
|
|
190
269
|
}
|
|
191
|
-
function toValidDecimal(v,
|
|
270
|
+
function toValidDecimal(v, { precision, scale } = {}) {
|
|
192
271
|
const num = toValidNumber(v);
|
|
193
272
|
if (num === null)
|
|
194
273
|
return null;
|
|
195
|
-
const { precision, scale } = opts;
|
|
196
274
|
let n = num;
|
|
197
275
|
if (scale !== undefined) {
|
|
198
|
-
|
|
199
|
-
n = Math.round(n * multiplier) / multiplier;
|
|
276
|
+
n = roundToScale(n, scale);
|
|
200
277
|
}
|
|
201
278
|
if (precision !== undefined) {
|
|
202
|
-
const scaleVal = scale
|
|
203
|
-
const
|
|
204
|
-
if (
|
|
205
|
-
|
|
206
|
-
n = clamp({ val: n, min: -maxVal, max: maxVal });
|
|
279
|
+
const scaleVal = scale ?? 0;
|
|
280
|
+
const maxVal = getDecimalMaxVal(precision, scaleVal);
|
|
281
|
+
if (maxVal !== null) {
|
|
282
|
+
n = clamp(n, { min: -maxVal, max: maxVal });
|
|
207
283
|
}
|
|
208
284
|
}
|
|
209
285
|
return n;
|
|
210
286
|
}
|
|
287
|
+
// ============================================================================
|
|
288
|
+
// /** Math & Stats Functions */
|
|
289
|
+
// ============================================================================
|
|
290
|
+
function clamp(val, { min = null, max = null, safe = true } = {}) {
|
|
291
|
+
if (min !== null && max !== null && min > max) {
|
|
292
|
+
return min;
|
|
293
|
+
}
|
|
294
|
+
let v = val;
|
|
295
|
+
if (safe && typeof v === "number") {
|
|
296
|
+
if (Number.isNaN(v)) {
|
|
297
|
+
// NaN can't be compared — coerce to the nearest boundary, preferring min
|
|
298
|
+
v = min !== null ? min : (max !== null ? max : val);
|
|
299
|
+
}
|
|
300
|
+
else if (v === Infinity) {
|
|
301
|
+
v = max !== null ? max : val;
|
|
302
|
+
}
|
|
303
|
+
else if (v === -Infinity) {
|
|
304
|
+
v = min !== null ? min : val;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (min !== null && v < min)
|
|
308
|
+
return min;
|
|
309
|
+
if (max !== null && v > max)
|
|
310
|
+
return max;
|
|
311
|
+
return v;
|
|
312
|
+
}
|
|
211
313
|
/**
|
|
212
314
|
* Creates a seedable pseudo-random number generator using the Mulberry32 PRNG algorithm.
|
|
213
315
|
* Returns a function that generates a pseudo-random float in the range [0, 1).
|
|
@@ -221,3 +323,79 @@ function mulberry32(seed) {
|
|
|
221
323
|
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
222
324
|
};
|
|
223
325
|
}
|
|
326
|
+
function getSortedValidNumbers(values) {
|
|
327
|
+
const len = values.length;
|
|
328
|
+
let validCount = 0;
|
|
329
|
+
const nums = new Float64Array(len);
|
|
330
|
+
for (let i = 0; i < len; i++) {
|
|
331
|
+
const val = values[i];
|
|
332
|
+
// Only include actual numbers that aren't NaN (but preserve Infinity for bounds)
|
|
333
|
+
if (typeof val === "number" && !Number.isNaN(val)) {
|
|
334
|
+
nums[validCount++] = val;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
if (validCount === 0)
|
|
338
|
+
return null;
|
|
339
|
+
const validNums = nums.subarray(0, validCount);
|
|
340
|
+
validNums.sort();
|
|
341
|
+
return validNums;
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Computes the median of a numeric array, filtering out non-numeric and NaN values.
|
|
345
|
+
* Returns null if no valid numbers remain.
|
|
346
|
+
*/
|
|
347
|
+
function computeMedian(values) {
|
|
348
|
+
const validNums = getSortedValidNumbers(values);
|
|
349
|
+
if (!validNums)
|
|
350
|
+
return null;
|
|
351
|
+
const len = validNums.length;
|
|
352
|
+
const mid = Math.floor(len / 2);
|
|
353
|
+
return len % 2 !== 0 ? validNums[mid] : (validNums[mid - 1] + validNums[mid]) / 2;
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Computes the quantile of a numeric array using linear interpolation, filtering out non-numeric and NaN values.
|
|
357
|
+
* q must be in [0, 1]. Returns null if no valid numbers remain or q is out of bounds.
|
|
358
|
+
*/
|
|
359
|
+
function computeQuantile(values, q) {
|
|
360
|
+
if (q < 0 || q > 1)
|
|
361
|
+
return null;
|
|
362
|
+
const validNums = getSortedValidNumbers(values);
|
|
363
|
+
if (!validNums)
|
|
364
|
+
return null;
|
|
365
|
+
const len = validNums.length;
|
|
366
|
+
const idx = q * (len - 1);
|
|
367
|
+
const low = Math.floor(idx);
|
|
368
|
+
const high = Math.ceil(idx);
|
|
369
|
+
if (low === high)
|
|
370
|
+
return validNums[low];
|
|
371
|
+
return validNums[low] + (idx - low) * (validNums[high] - validNums[low]);
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Computes the mode(s) of an array, filtering out null/undefined values.
|
|
375
|
+
* Returns an array of the most frequent values, sorted, or null if empty/no mode.
|
|
376
|
+
*/
|
|
377
|
+
function computeMode(values) {
|
|
378
|
+
if (!(0, guards_1.isArrayOrTypedArray)(values) || values.length === 0)
|
|
379
|
+
return null;
|
|
380
|
+
const counts = new Map();
|
|
381
|
+
const len = values.length;
|
|
382
|
+
let max = 0;
|
|
383
|
+
let modes = [];
|
|
384
|
+
for (let i = 0; i < len; i++) {
|
|
385
|
+
const val = values[i];
|
|
386
|
+
if (val == null)
|
|
387
|
+
continue;
|
|
388
|
+
const c = (counts.get(val) ?? 0) + 1;
|
|
389
|
+
counts.set(val, c);
|
|
390
|
+
if (c > max) {
|
|
391
|
+
max = c;
|
|
392
|
+
modes = [val];
|
|
393
|
+
}
|
|
394
|
+
else if (c === max) {
|
|
395
|
+
modes.push(val);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
if (modes.length === 0)
|
|
399
|
+
return null;
|
|
400
|
+
return (0, list_1.sortList)(modes);
|
|
401
|
+
}
|
package/dist/utils/string.d.ts
CHANGED
|
@@ -50,3 +50,8 @@ export type StripCharsOptions = {
|
|
|
50
50
|
* Strips characters from the start, end, or both ends of a string.
|
|
51
51
|
*/
|
|
52
52
|
export declare function stripChars(str: string | null | undefined, characters?: string | RegExp | null, { mode, returnStringOnNull, maxScanStart, maxScanEnd, maxMatchesStart, maxMatchesEnd, trimFirst, stringOptions: { literal, caseInsensitive } }?: StripCharsOptions): string | null;
|
|
53
|
+
export declare function isBlankString(v: unknown): v is string;
|
|
54
|
+
export declare function toCanonicalString(val: any, { depth, maxDepth }?: {
|
|
55
|
+
depth?: number;
|
|
56
|
+
maxDepth?: number;
|
|
57
|
+
}): string;
|
package/dist/utils/string.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.stripChars = stripChars;
|
|
4
|
+
exports.isBlankString = isBlankString;
|
|
5
|
+
exports.toCanonicalString = toCanonicalString;
|
|
6
|
+
const guards_1 = require("./guards");
|
|
7
|
+
const date_1 = require("./date");
|
|
4
8
|
/**
|
|
5
9
|
* Strips characters from the start, end, or both ends of a string.
|
|
6
10
|
*/
|
|
@@ -118,3 +122,88 @@ function stripChars(str, characters = null, { mode = "both", returnStringOnNull
|
|
|
118
122
|
}
|
|
119
123
|
return (returnStringOnNull || result !== "") ? result : null;
|
|
120
124
|
}
|
|
125
|
+
function isBlankString(v) {
|
|
126
|
+
if (typeof v === "string") {
|
|
127
|
+
return v.trim().length === 0;
|
|
128
|
+
}
|
|
129
|
+
if (v instanceof String) {
|
|
130
|
+
return v.valueOf().trim().length === 0;
|
|
131
|
+
}
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
function toCanonicalString(val, { depth = 0, maxDepth = 50 } = {}) {
|
|
135
|
+
if (depth > maxDepth) {
|
|
136
|
+
return "v:circular";
|
|
137
|
+
}
|
|
138
|
+
if (val === null) {
|
|
139
|
+
return "v:null";
|
|
140
|
+
}
|
|
141
|
+
if (val === undefined) {
|
|
142
|
+
return "v:undefined";
|
|
143
|
+
}
|
|
144
|
+
if (val instanceof Date) {
|
|
145
|
+
return (0, date_1.isValidDateObj)(val) ? `d:${val.getTime()}` : "d:invalid";
|
|
146
|
+
}
|
|
147
|
+
if ((0, guards_1.isTypedArray)(val)) {
|
|
148
|
+
return `u:${val.constructor.name}:${val.toString()}`;
|
|
149
|
+
}
|
|
150
|
+
if (Array.isArray(val)) {
|
|
151
|
+
const len = val.length;
|
|
152
|
+
const parts = new Array(len);
|
|
153
|
+
const nextOpt = { depth: depth + 1, maxDepth };
|
|
154
|
+
for (let i = 0; i < len; i++) {
|
|
155
|
+
parts[i] = toCanonicalString(val[i], nextOpt);
|
|
156
|
+
}
|
|
157
|
+
return `a:[${parts.join(",")}]`;
|
|
158
|
+
}
|
|
159
|
+
if (val instanceof Set) {
|
|
160
|
+
const arr = Array.from(val);
|
|
161
|
+
const len = arr.length;
|
|
162
|
+
const parts = new Array(len);
|
|
163
|
+
const nextOpt = { depth: depth + 1, maxDepth };
|
|
164
|
+
for (let i = 0; i < len; i++) {
|
|
165
|
+
parts[i] = toCanonicalString(arr[i], nextOpt);
|
|
166
|
+
}
|
|
167
|
+
parts.sort();
|
|
168
|
+
return `set:[${parts.join(",")}]`;
|
|
169
|
+
}
|
|
170
|
+
if (val instanceof Map) {
|
|
171
|
+
const keys = Array.from(val.keys());
|
|
172
|
+
const len = keys.length;
|
|
173
|
+
const parts = new Array(len);
|
|
174
|
+
const nextOpt = { depth: depth + 1, maxDepth };
|
|
175
|
+
for (let i = 0; i < len; i++) {
|
|
176
|
+
const k = keys[i];
|
|
177
|
+
parts[i] = `${toCanonicalString(k, nextOpt)}:${toCanonicalString(val.get(k), nextOpt)}`;
|
|
178
|
+
}
|
|
179
|
+
parts.sort();
|
|
180
|
+
return `map:{${parts.join(",")}}`;
|
|
181
|
+
}
|
|
182
|
+
if (typeof val === "object" && typeof val.toJSON === "function") {
|
|
183
|
+
return `j:${toCanonicalString(val.toJSON(), { depth: depth + 1, maxDepth })}`;
|
|
184
|
+
}
|
|
185
|
+
if ((0, guards_1.isPlainObj)(val)) {
|
|
186
|
+
const keys = Object.keys(val).sort();
|
|
187
|
+
const len = keys.length;
|
|
188
|
+
const parts = new Array(len);
|
|
189
|
+
const nextOpt = { depth: depth + 1, maxDepth };
|
|
190
|
+
for (let i = 0; i < len; i++) {
|
|
191
|
+
const k = keys[i];
|
|
192
|
+
parts[i] = `${k}:${toCanonicalString(val[k], nextOpt)}`;
|
|
193
|
+
}
|
|
194
|
+
return `o:{${parts.join(",")}}`;
|
|
195
|
+
}
|
|
196
|
+
if (val instanceof RegExp) {
|
|
197
|
+
return `r:${val.toString()}`;
|
|
198
|
+
}
|
|
199
|
+
if (typeof val === "function") {
|
|
200
|
+
return `f:${val.toString()}`;
|
|
201
|
+
}
|
|
202
|
+
if (typeof val === "string") {
|
|
203
|
+
return `s:${val}`;
|
|
204
|
+
}
|
|
205
|
+
if (typeof val === "symbol") {
|
|
206
|
+
return `y:${val.toString()}`;
|
|
207
|
+
}
|
|
208
|
+
return `${typeof val}:${val}`;
|
|
209
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "df-script",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "A zero-dependency, high-performance, expression-based DataFrame library for TypeScript/JavaScript.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -26,6 +26,10 @@
|
|
|
26
26
|
],
|
|
27
27
|
"author": "Trent Morris",
|
|
28
28
|
"license": "MIT",
|
|
29
|
+
"funding": {
|
|
30
|
+
"type": "custom",
|
|
31
|
+
"url": "https://github.com/trentamorris/df-script/blob/main/DONATIONS.md"
|
|
32
|
+
},
|
|
29
33
|
"devDependencies": {
|
|
30
34
|
"typescript": "^5.4.5",
|
|
31
35
|
"tsx": "^4.15.5",
|