@trebco/treb 30.12.0 → 30.16.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.
@@ -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.type === ValueType.array) {
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>);
@@ -48,7 +48,7 @@ export type ChartFunction
48
48
  | 'Box.Plot'
49
49
  ;
50
50
 
51
- type SupportFunction = 'Group'|'Series';
51
+ type SupportFunction = 'Group'|'Series' ; // |'Scatter.Series';
52
52
 
53
53
  /**
54
54
  * chart functions for registration
@@ -87,20 +87,57 @@ export const ChartFunctions: Record<ChartFunction|SupportFunction, CompositeFunc
87
87
  { name: 'X', metadata: true, },
88
88
  { name: 'Y', metadata: true, },
89
89
  { name: 'Z', metadata: true, },
90
- { name: 'index', },
91
- { name: 'subtype', },
90
+ { name: 'Index', },
91
+ { name: 'Subtype', },
92
92
  { name: 'Labels', description: 'Labels for bubble charts only (atm)' },
93
+ { name: 'Axis', description: `Series axis (scatter plot only)` },
93
94
  ],
94
95
  fn: (...args: unknown[]) => {
95
96
  return {
96
97
  type: ValueType.object,
97
- value: args,
98
+ value: {
99
+ label: args[0],
100
+ x: args[1],
101
+ y: args[2],
102
+ z: args[3],
103
+ index: args[4],
104
+ subtype: args[5],
105
+ data_labels: args[6],
106
+ axis: args[7],
107
+ },
98
108
  key: 'series',
99
109
  };
100
110
  },
101
111
  category: ['chart functions'],
102
112
  },
103
113
 
114
+ /*
115
+ 'Scatter.Series': {
116
+ arguments: [
117
+ { name: 'Label' }, // , metadata: true, },
118
+ { name: 'X', metadata: true, },
119
+ { name: 'Y', metadata: true, },
120
+ { name: 'index', },
121
+ { name: 'subtype', },
122
+ { name: 'axis', },
123
+ ],
124
+ fn: (...args: unknown[]) => {
125
+ return {
126
+ type: ValueType.object,
127
+ value: {
128
+ label: args[0],
129
+ x: args[1],
130
+ y: args[2],
131
+ index: args[3],
132
+ subtype: args[4],
133
+ axis: args[5],
134
+ },
135
+ key: 'series',
136
+ };
137
+ },
138
+ },
139
+ */
140
+
104
141
  'Bar.Chart': {
105
142
  arguments: [
106
143
  { name: 'Data', metadata: true, },
@@ -33,6 +33,18 @@ export interface ReferenceMetadata {
33
33
 
34
34
  export interface ReferenceSeries extends ExtendedUnion {
35
35
  key: 'series',
36
+ value: {
37
+ label?: string;
38
+ x?: UnionValue;
39
+ y?: UnionValue;
40
+ z?: UnionValue;
41
+ index?: number;
42
+ subtype?: string;
43
+ data_labels?: string[];
44
+ axis?: number;
45
+ }
46
+
47
+ /*
36
48
  value: [
37
49
  CellValue?, // { name: 'Label' }, // , metadata: true, },
38
50
  UnionValue?, // { name: 'X', metadata: true, },
@@ -42,6 +54,8 @@ export interface ReferenceSeries extends ExtendedUnion {
42
54
  CellValue?, // { name: 'subtype', },
43
55
  CellValue?, // { name: 'Labels', description: 'Labels for bubble charts only (atm)' },
44
56
  ];
57
+ */
58
+
45
59
  }
46
60
 
47
61
  export const IsMetadata = (value?: unknown): value is ExtendedUnion & { value: ReferenceMetadata } => {
@@ -54,7 +68,8 @@ export const IsMetadata = (value?: unknown): value is ExtendedUnion & { value: R
54
68
  export const IsSeries = (value?: unknown): value is ReferenceSeries => {
55
69
  return (!!value && (typeof value === 'object')
56
70
  && (value as ReferenceSeries).key === 'series'
57
- && Array.isArray((value as ReferenceSeries).value));
71
+ // && Array.isArray((value as ReferenceSeries).value));
72
+ && (typeof (value as ReferenceSeries).value === 'object'));
58
73
  };
59
74
 
60
75
  export const IsArrayUnion = (value?: unknown): value is ArrayUnion => {
@@ -140,9 +155,11 @@ export interface ScatterData2 extends ChartDataBaseType {
140
155
 
141
156
  x_scale: RangeScale;
142
157
  y_scale: RangeScale;
158
+ y2_scale?: RangeScale;
143
159
 
144
160
  x_labels?: string[];
145
161
  y_labels?: string[];
162
+ y2_labels?: string[];
146
163
 
147
164
  style?: 'plot'|'line'|'area';
148
165
 
@@ -311,8 +328,10 @@ export interface SeriesType {
311
328
  subtype?: string;
312
329
  x: SubSeries;
313
330
  y: SubSeries;
331
+ y2?: SubSeries;
314
332
  z?: SubSeries;
315
333
  index?: number;
316
334
  labels?: string[];
335
+ axis?: number;
317
336
  }
318
337