@trebco/treb 30.12.0 → 30.15.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/dist/treb-export-worker.mjs +1 -1
- package/dist/treb-spreadsheet.mjs +12 -12
- package/dist/treb.d.ts +7 -1
- package/package.json +1 -1
- package/treb-calculator/src/complex-math.ts +39 -0
- package/treb-calculator/src/functions/base-functions.ts +55 -139
- package/treb-calculator/src/functions/beta.ts +244 -0
- package/treb-calculator/src/functions/statistics-functions.ts +388 -4
- package/treb-calculator/src/utilities.ts +10 -1
- package/treb-embed/src/embedded-spreadsheet.ts +13 -0
- package/treb-grid/src/types/grid.ts +1 -1
|
@@ -22,9 +22,61 @@
|
|
|
22
22
|
import type { FunctionMap } from '../descriptors';
|
|
23
23
|
import * as Utils from '../utilities';
|
|
24
24
|
import { ValueError, ArgumentError, NAError } from '../function-error';
|
|
25
|
-
import { type Complex, type UnionValue, ValueType, type CellValue } from 'treb-base-types';
|
|
25
|
+
import { type Complex, type UnionValue, ValueType, type CellValue, ComplexOrReal } from 'treb-base-types';
|
|
26
26
|
import * as ComplexMath from '../complex-math';
|
|
27
27
|
|
|
28
|
+
import { BetaCDF, BetaPDF, InverseBeta, LnGamma } from './beta';
|
|
29
|
+
|
|
30
|
+
/** error function (for gaussian distribution) */
|
|
31
|
+
const erf = (x: number): number => {
|
|
32
|
+
|
|
33
|
+
const a1 = 0.254829592;
|
|
34
|
+
const a2 = -0.284496736;
|
|
35
|
+
const a3 = 1.421413741;
|
|
36
|
+
const a4 = -1.453152027;
|
|
37
|
+
const a5 = 1.061405429;
|
|
38
|
+
const p = 0.3275911;
|
|
39
|
+
|
|
40
|
+
x = Math.abs(x);
|
|
41
|
+
const t = 1 / (1 + p * x);
|
|
42
|
+
return 1 - ((((((a5 * t + a4) * t) + a3) * t + a2) * t) + a1) * t * Math.exp(-1 * x * x);
|
|
43
|
+
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const sqrt2pi = Math.sqrt(2 * Math.PI);
|
|
47
|
+
|
|
48
|
+
const norm_dist = (x: number, mean: number, stdev: number, cumulative: boolean) => {
|
|
49
|
+
|
|
50
|
+
let value = 0;
|
|
51
|
+
|
|
52
|
+
if (cumulative) {
|
|
53
|
+
const sign = (x < mean) ? -1 : 1;
|
|
54
|
+
value = 0.5 * (1.0 + sign * erf((Math.abs(x - mean)) / (stdev * Math.sqrt(2))));
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
value = Math.exp(-1/2 * Math.pow((x - mean) / stdev, 2)) / (stdev * sqrt2pi);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return value;
|
|
61
|
+
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** imprecise but reasonably fast normsinv function */
|
|
65
|
+
const inverse_normal = (q: number): number => {
|
|
66
|
+
|
|
67
|
+
if (q === 0.50) {
|
|
68
|
+
return 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const p = (q < 1.0 && q > 0.5) ? (1 - q) : q;
|
|
72
|
+
const t = Math.sqrt(Math.log(1.0 / Math.pow(p, 2.0)));
|
|
73
|
+
const x = t - (2.515517 + 0.802853 * t + 0.010328 * Math.pow(t, 2.0)) /
|
|
74
|
+
(1.0 + 1.432788 * t + 0.189269 * Math.pow(t, 2.0) + 0.001308 * Math.pow(t, 3.0));
|
|
75
|
+
|
|
76
|
+
return (q > 0.5 ? x : -x);
|
|
77
|
+
|
|
78
|
+
};
|
|
79
|
+
|
|
28
80
|
const Median = (data: number[]) => {
|
|
29
81
|
const n = data.length;
|
|
30
82
|
if (n % 2) {
|
|
@@ -68,10 +120,13 @@ const InterpolatedQuartiles = (data: number[], include_median = true) => {
|
|
|
68
120
|
|
|
69
121
|
};
|
|
70
122
|
|
|
123
|
+
/**
|
|
124
|
+
* this is a Lanczos approximation. could be cleaned up.
|
|
125
|
+
* @param z
|
|
126
|
+
* @returns
|
|
127
|
+
*/
|
|
71
128
|
const Gamma = (z: Complex): Complex => {
|
|
72
129
|
|
|
73
|
-
// this is a Lanczos approximation. could be cleaned up.
|
|
74
|
-
|
|
75
130
|
const coefficients = [
|
|
76
131
|
0.99999999999980993,
|
|
77
132
|
676.5203681218851,
|
|
@@ -87,6 +142,9 @@ const Gamma = (z: Complex): Complex => {
|
|
|
87
142
|
// generally speaking I'm against operator overloading but
|
|
88
143
|
// it would be a big help for complex math
|
|
89
144
|
|
|
145
|
+
// how about a class based on the complex type? then you could
|
|
146
|
+
// use class methods on values. would be a little cleaner?
|
|
147
|
+
|
|
90
148
|
const pi = Math.PI;
|
|
91
149
|
const sin = ComplexMath.Sin;
|
|
92
150
|
const div = ComplexMath.Divide;
|
|
@@ -144,6 +202,295 @@ export const Variance = (data: number[], sample = false) => {
|
|
|
144
202
|
|
|
145
203
|
export const StatisticsFunctionLibrary: FunctionMap = {
|
|
146
204
|
|
|
205
|
+
Slope: {
|
|
206
|
+
arguments: [
|
|
207
|
+
{ name: 'known_y' },
|
|
208
|
+
{ name: 'known_x' },
|
|
209
|
+
],
|
|
210
|
+
fn: (y: CellValue[][], x: CellValue[][]) => {
|
|
211
|
+
|
|
212
|
+
const flat_x = Utils.FlattenNumbers(x);
|
|
213
|
+
const flat_y = Utils.FlattenNumbers(y);
|
|
214
|
+
|
|
215
|
+
if (flat_x.length !== flat_y.length) {
|
|
216
|
+
return ArgumentError();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
let sum_x = 0;
|
|
220
|
+
let sum_y = 0;
|
|
221
|
+
let sum_products = 0;
|
|
222
|
+
let sum_squared_x = 0;
|
|
223
|
+
|
|
224
|
+
for (let i = 0; i < flat_x.length; i++) {
|
|
225
|
+
|
|
226
|
+
const x = flat_x[i];
|
|
227
|
+
const y = flat_y[i];
|
|
228
|
+
|
|
229
|
+
sum_products += (x * y);
|
|
230
|
+
sum_x += x;
|
|
231
|
+
sum_y += y;
|
|
232
|
+
|
|
233
|
+
sum_squared_x += (x * x);
|
|
234
|
+
// sum_squared_y += (y * y);
|
|
235
|
+
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const value =
|
|
239
|
+
((flat_x.length * sum_products) - (sum_x * sum_y) ) /
|
|
240
|
+
((flat_x.length * sum_squared_x) - (sum_x * sum_x));
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
type: ValueType.number,
|
|
244
|
+
value,
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
|
|
250
|
+
Intercept: {
|
|
251
|
+
arguments: [
|
|
252
|
+
{ name: 'known_y' },
|
|
253
|
+
{ name: 'known_x' },
|
|
254
|
+
],
|
|
255
|
+
fn: (y: CellValue[][], x: CellValue[][]) => {
|
|
256
|
+
const flat_x = Utils.FlattenNumbers(x);
|
|
257
|
+
const flat_y = Utils.FlattenNumbers(y);
|
|
258
|
+
|
|
259
|
+
if (flat_x.length !== flat_y.length) {
|
|
260
|
+
return ArgumentError();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const N = flat_x.length;
|
|
264
|
+
|
|
265
|
+
let sum_x = 0;
|
|
266
|
+
let sum_y = 0;
|
|
267
|
+
let sum_products = 0;
|
|
268
|
+
let sum_squared_x = 0;
|
|
269
|
+
|
|
270
|
+
for (let i = 0; i < N; i++) {
|
|
271
|
+
|
|
272
|
+
const x = flat_x[i];
|
|
273
|
+
const y = flat_y[i];
|
|
274
|
+
|
|
275
|
+
sum_products += (x * y);
|
|
276
|
+
sum_x += x;
|
|
277
|
+
sum_y += y;
|
|
278
|
+
|
|
279
|
+
sum_squared_x += (x * x);
|
|
280
|
+
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
let m = ((N * sum_products) - (sum_x * sum_y) );
|
|
284
|
+
m /= ((N * sum_squared_x) - (sum_x * sum_x));
|
|
285
|
+
|
|
286
|
+
return {
|
|
287
|
+
type: ValueType.number, value: (sum_y - (m * sum_x)) / N,
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
|
|
293
|
+
Phi: {
|
|
294
|
+
arguments: [
|
|
295
|
+
{ name: 'x', boxed: true, unroll: true }
|
|
296
|
+
],
|
|
297
|
+
fn: (x: UnionValue) => {
|
|
298
|
+
|
|
299
|
+
if (x.type === ValueType.number) {
|
|
300
|
+
return {
|
|
301
|
+
type: ValueType.number,
|
|
302
|
+
value: (1 / Math.sqrt(Math.PI * 2)) * Math.exp(-x.value * x.value / 2),
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return ArgumentError();
|
|
307
|
+
}
|
|
308
|
+
},
|
|
309
|
+
|
|
310
|
+
'Z.Test': {
|
|
311
|
+
arguments: [
|
|
312
|
+
{ name: 'Array', boxed: true, },
|
|
313
|
+
{ name: 'x', boxed: true, unroll: true, },
|
|
314
|
+
{ name: 'Sigma', boxed: true, unroll: true, },
|
|
315
|
+
],
|
|
316
|
+
fn: (array: UnionValue, x: UnionValue, sigma?: UnionValue) => {
|
|
317
|
+
|
|
318
|
+
const data: number[] = [];
|
|
319
|
+
|
|
320
|
+
if (array.type === ValueType.array) {
|
|
321
|
+
for (const row of array.value) {
|
|
322
|
+
for (const cell of row) {
|
|
323
|
+
if (cell.type === ValueType.number) {
|
|
324
|
+
data.push(cell.value);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
else if (array.type === ValueType.number) {
|
|
330
|
+
data.push(array.value);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (data.length && x.type === ValueType.number) {
|
|
334
|
+
|
|
335
|
+
let average = 0;
|
|
336
|
+
const n = data.length;
|
|
337
|
+
for (const value of data) { average += value; }
|
|
338
|
+
average /= n;
|
|
339
|
+
|
|
340
|
+
const s = sigma?.type === ValueType.number ? sigma.value :
|
|
341
|
+
Math.sqrt(Variance(data, true));
|
|
342
|
+
|
|
343
|
+
return {
|
|
344
|
+
type: ValueType.number,
|
|
345
|
+
value: 1 - norm_dist((average - x.value) / (s / Math.sqrt(n)), 0, 1, true),
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return ArgumentError();
|
|
351
|
+
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
|
|
355
|
+
'Beta.Dist': {
|
|
356
|
+
description: 'beta distribution',
|
|
357
|
+
arguments: [
|
|
358
|
+
{ name: 'x', unroll: true},
|
|
359
|
+
{ name: 'a', },
|
|
360
|
+
{ name: 'b', },
|
|
361
|
+
{ name: 'cumulative', },
|
|
362
|
+
],
|
|
363
|
+
fn: (x: number, a: number, b: number, cumulative: boolean) => {
|
|
364
|
+
|
|
365
|
+
if (a < 0 || b < 0) {
|
|
366
|
+
return ArgumentError();
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (cumulative) {
|
|
370
|
+
return {
|
|
371
|
+
type: ValueType.number,
|
|
372
|
+
value: BetaCDF(x, a, b),
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
return {
|
|
377
|
+
type: ValueType.number,
|
|
378
|
+
value: BetaPDF(x, a, b),
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return ArgumentError();
|
|
383
|
+
}
|
|
384
|
+
},
|
|
385
|
+
|
|
386
|
+
'Beta.Inv': {
|
|
387
|
+
description: 'Inverse of the beta distribution',
|
|
388
|
+
arguments: [
|
|
389
|
+
{name: 'probability', unroll: true},
|
|
390
|
+
{name: 'a', },
|
|
391
|
+
{name: 'b', },
|
|
392
|
+
],
|
|
393
|
+
fn: (x: number, a: number, b: number) => {
|
|
394
|
+
|
|
395
|
+
if (a < 0 || b < 0) {
|
|
396
|
+
return ArgumentError();
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return {
|
|
400
|
+
type: ValueType.number,
|
|
401
|
+
value: InverseBeta(x, a, b),
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
},
|
|
405
|
+
|
|
406
|
+
Erf: {
|
|
407
|
+
fn: (a: number): UnionValue => {
|
|
408
|
+
return { type: ValueType.number, value: erf(a) };
|
|
409
|
+
},
|
|
410
|
+
},
|
|
411
|
+
|
|
412
|
+
'NormsInv': {
|
|
413
|
+
|
|
414
|
+
description: 'Inverse of the normal cumulative distribution',
|
|
415
|
+
arguments: [
|
|
416
|
+
{name: 'probability'},
|
|
417
|
+
],
|
|
418
|
+
|
|
419
|
+
fn: (q: number): UnionValue => {
|
|
420
|
+
return {
|
|
421
|
+
type: ValueType.number,
|
|
422
|
+
value: inverse_normal(q),
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
},
|
|
426
|
+
|
|
427
|
+
'Norm.Inv': {
|
|
428
|
+
description: 'Inverse of the normal cumulative distribution',
|
|
429
|
+
arguments: [
|
|
430
|
+
{name: 'probability'},
|
|
431
|
+
{name: 'mean', default: 0},
|
|
432
|
+
{name: 'standard deviation', default: 1},
|
|
433
|
+
],
|
|
434
|
+
xlfn: true,
|
|
435
|
+
fn: (q: number, mean = 0, stdev = 1): UnionValue => {
|
|
436
|
+
return {
|
|
437
|
+
type: ValueType.number,
|
|
438
|
+
value: inverse_normal(q) * stdev + mean,
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
},
|
|
442
|
+
|
|
443
|
+
'Norm.S.Inv': {
|
|
444
|
+
description: 'Inverse of the normal cumulative distribution',
|
|
445
|
+
arguments: [
|
|
446
|
+
{name: 'probability'},
|
|
447
|
+
{name: 'mean', default: 0},
|
|
448
|
+
{name: 'standard deviation', default: 1},
|
|
449
|
+
],
|
|
450
|
+
xlfn: true,
|
|
451
|
+
fn: (q: number, mean = 0, stdev = 1): UnionValue => {
|
|
452
|
+
return {
|
|
453
|
+
type: ValueType.number,
|
|
454
|
+
value: inverse_normal(q) * stdev + mean,
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
},
|
|
458
|
+
|
|
459
|
+
'Norm.Dist': {
|
|
460
|
+
|
|
461
|
+
description: 'Cumulative normal distribution',
|
|
462
|
+
arguments: [
|
|
463
|
+
{name: 'value'},
|
|
464
|
+
{name: 'mean', default: 0},
|
|
465
|
+
{name: 'standard deviation', default: 1},
|
|
466
|
+
{name: 'cumulative', default: true},
|
|
467
|
+
],
|
|
468
|
+
|
|
469
|
+
// this does need xlfn but it also requires four parameters
|
|
470
|
+
// (we have three and they are not required).
|
|
471
|
+
|
|
472
|
+
xlfn: true,
|
|
473
|
+
|
|
474
|
+
fn: (x: number, mean = 0, stdev = 1, cumulative = true): UnionValue => {
|
|
475
|
+
return { type: ValueType.number, value: norm_dist(x, mean, stdev, cumulative) };
|
|
476
|
+
},
|
|
477
|
+
},
|
|
478
|
+
|
|
479
|
+
'Norm.S.Dist': {
|
|
480
|
+
|
|
481
|
+
description: 'Cumulative normal distribution',
|
|
482
|
+
arguments: [
|
|
483
|
+
{name: 'value'},
|
|
484
|
+
{name: 'cumulative', default: true},
|
|
485
|
+
],
|
|
486
|
+
|
|
487
|
+
xlfn: true,
|
|
488
|
+
|
|
489
|
+
fn: (x: number, cumulative = true): UnionValue => {
|
|
490
|
+
return { type: ValueType.number, value: norm_dist(x, 0, 1, cumulative) };
|
|
491
|
+
},
|
|
492
|
+
},
|
|
493
|
+
|
|
147
494
|
'StDev.P': {
|
|
148
495
|
description: 'Returns the standard deviation of a set of values, corresponding to a population',
|
|
149
496
|
arguments: [{ name: 'data', }],
|
|
@@ -396,9 +743,46 @@ export const StatisticsFunctionLibrary: FunctionMap = {
|
|
|
396
743
|
},
|
|
397
744
|
},
|
|
398
745
|
|
|
746
|
+
GammaLn: {
|
|
747
|
+
description: 'Returns the natural log of the gamma function',
|
|
748
|
+
arguments: [{ name: 'value', boxed: true, unroll: true }],
|
|
749
|
+
fn: (value: UnionValue) => {
|
|
750
|
+
if (value.type === ValueType.number) {
|
|
751
|
+
return {
|
|
752
|
+
type: ValueType.number,
|
|
753
|
+
value: LnGamma(value.value),
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
return ArgumentError();
|
|
757
|
+
},
|
|
758
|
+
},
|
|
759
|
+
|
|
760
|
+
'GammaLn.Precise': {
|
|
761
|
+
description: 'Returns the natural log of the gamma function',
|
|
762
|
+
arguments: [{ name: 'value', boxed: true, unroll: true }],
|
|
763
|
+
fn: (value: UnionValue) => {
|
|
764
|
+
|
|
765
|
+
let cpx: Complex|undefined;
|
|
766
|
+
|
|
767
|
+
if (value.type === ValueType.number) {
|
|
768
|
+
cpx = { real: value.value, imaginary: 0 };
|
|
769
|
+
}
|
|
770
|
+
else if (value.type === ValueType.complex) {
|
|
771
|
+
cpx = value.value;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
if (cpx) {
|
|
775
|
+
const gamma = Gamma(cpx);
|
|
776
|
+
return ComplexOrReal(ComplexMath.Log(gamma));
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
return ArgumentError();
|
|
780
|
+
},
|
|
781
|
+
},
|
|
782
|
+
|
|
399
783
|
Gamma: {
|
|
400
784
|
description: 'Returns the gamma function for the given value',
|
|
401
|
-
arguments: [{ name: 'value', boxed: true }],
|
|
785
|
+
arguments: [{ name: 'value', boxed: true, unroll: true }],
|
|
402
786
|
fn: (value: UnionValue) => {
|
|
403
787
|
|
|
404
788
|
let complex: Complex = { real: 0, imaginary: 0 };
|
|
@@ -127,7 +127,7 @@ export const FlattenBoxed = (args: UnionValue[]): UnionValue[] => {
|
|
|
127
127
|
|
|
128
128
|
let result: UnionValue[] = [];
|
|
129
129
|
for (const arg of args) {
|
|
130
|
-
if (arg
|
|
130
|
+
if (arg?.type === ValueType.array) {
|
|
131
131
|
|
|
132
132
|
// possibly recursive
|
|
133
133
|
for (const row of arg.value) {
|
|
@@ -305,6 +305,15 @@ export const ApplyArrayX = <TFunc extends (...args: any[]) => UnionValue>(map: b
|
|
|
305
305
|
// it used || instead of ??. if we don't require
|
|
306
306
|
// boxed arguments, we need to handle naked booleans
|
|
307
307
|
|
|
308
|
+
// actually, this is still slightly wrong. we know (actually the
|
|
309
|
+
// caller of this function knows) if each argument is boxed or not,
|
|
310
|
+
// and if it's not, we should not return the boxed undefined type,
|
|
311
|
+
// we should just return undefined.
|
|
312
|
+
|
|
313
|
+
// that actually would basically solve false booleans as well, even
|
|
314
|
+
// if it were incorrectly returning undefined. the issue is we were
|
|
315
|
+
// returning a boxed undefined, which evaluates to true.
|
|
316
|
+
|
|
308
317
|
const apply = args.map((arg, index) => arrays[index] ? (arrays[index][i][j] ?? { type: ValueType.undefined }) : arg);
|
|
309
318
|
|
|
310
319
|
return base(...apply as Parameters<TFunc>);
|
|
@@ -2611,6 +2611,19 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
2611
2611
|
this.grid.ShowSheet(index, !hide);
|
|
2612
2612
|
}
|
|
2613
2613
|
|
|
2614
|
+
/** list sheets in the model */
|
|
2615
|
+
public ListSheets() {
|
|
2616
|
+
return this.model.sheets.list.map(sheet => {
|
|
2617
|
+
const entry: { name: string, hidden?: boolean } = {
|
|
2618
|
+
name: sheet.name,
|
|
2619
|
+
};
|
|
2620
|
+
if (!sheet.visible) {
|
|
2621
|
+
entry.hidden = true;
|
|
2622
|
+
}
|
|
2623
|
+
return entry;
|
|
2624
|
+
});
|
|
2625
|
+
}
|
|
2626
|
+
|
|
2614
2627
|
/**
|
|
2615
2628
|
* Show or hide sheet. This method is deprecated because it's ambiguous.
|
|
2616
2629
|
* To set a sheet's visibility, use `HideSheet`. To activate a sheet, use
|
|
@@ -7524,7 +7524,7 @@ export class Grid extends GridBase {
|
|
|
7524
7524
|
if (typeof column === 'number') column = [column];
|
|
7525
7525
|
|
|
7526
7526
|
const auto = typeof command.width !== 'number';
|
|
7527
|
-
const width = Math.round(command.width || 0 / this.scale)
|
|
7527
|
+
const width = Math.round((command.width || 0) / this.scale);
|
|
7528
7528
|
|
|
7529
7529
|
if (auto) {
|
|
7530
7530
|
for (const entry of column) {
|