@datagrok/eda 1.4.12 → 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.
Files changed (103) hide show
  1. package/.eslintrc.json +0 -1
  2. package/CHANGELOG.md +10 -0
  3. package/CLAUDE.md +185 -0
  4. package/css/pmpo.css +9 -0
  5. package/dist/111.js +1 -1
  6. package/dist/111.js.map +1 -1
  7. package/dist/128.js +1 -1
  8. package/dist/128.js.map +1 -1
  9. package/dist/153.js +1 -1
  10. package/dist/153.js.map +1 -1
  11. package/dist/23.js +1 -1
  12. package/dist/23.js.map +1 -1
  13. package/dist/234.js +1 -1
  14. package/dist/234.js.map +1 -1
  15. package/dist/242.js +1 -1
  16. package/dist/242.js.map +1 -1
  17. package/dist/260.js +1 -1
  18. package/dist/260.js.map +1 -1
  19. package/dist/33.js +1 -1
  20. package/dist/33.js.map +1 -1
  21. package/dist/348.js +1 -1
  22. package/dist/348.js.map +1 -1
  23. package/dist/377.js +1 -1
  24. package/dist/377.js.map +1 -1
  25. package/dist/397.js +2 -0
  26. package/dist/397.js.map +1 -0
  27. package/dist/412.js +1 -1
  28. package/dist/412.js.map +1 -1
  29. package/dist/415.js +1 -1
  30. package/dist/415.js.map +1 -1
  31. package/dist/501.js +1 -1
  32. package/dist/501.js.map +1 -1
  33. package/dist/531.js +1 -1
  34. package/dist/531.js.map +1 -1
  35. package/dist/583.js +1 -1
  36. package/dist/583.js.map +1 -1
  37. package/dist/589.js +1 -1
  38. package/dist/589.js.map +1 -1
  39. package/dist/603.js +1 -1
  40. package/dist/603.js.map +1 -1
  41. package/dist/656.js +1 -1
  42. package/dist/656.js.map +1 -1
  43. package/dist/682.js +1 -1
  44. package/dist/682.js.map +1 -1
  45. package/dist/705.js +1 -1
  46. package/dist/705.js.map +1 -1
  47. package/dist/727.js +1 -1
  48. package/dist/727.js.map +1 -1
  49. package/dist/731.js +1 -1
  50. package/dist/731.js.map +1 -1
  51. package/dist/738.js +1 -1
  52. package/dist/738.js.map +1 -1
  53. package/dist/763.js +1 -1
  54. package/dist/763.js.map +1 -1
  55. package/dist/778.js +1 -1
  56. package/dist/778.js.map +1 -1
  57. package/dist/783.js +1 -1
  58. package/dist/783.js.map +1 -1
  59. package/dist/793.js +1 -1
  60. package/dist/793.js.map +1 -1
  61. package/dist/810.js +1 -1
  62. package/dist/810.js.map +1 -1
  63. package/dist/860.js +1 -1
  64. package/dist/860.js.map +1 -1
  65. package/dist/907.js +1 -1
  66. package/dist/907.js.map +1 -1
  67. package/dist/950.js +1 -1
  68. package/dist/950.js.map +1 -1
  69. package/dist/980.js +1 -1
  70. package/dist/980.js.map +1 -1
  71. package/dist/990.js +1 -1
  72. package/dist/990.js.map +1 -1
  73. package/dist/package-test.js +1 -1
  74. package/dist/package-test.js.map +1 -1
  75. package/dist/package.js +1 -1
  76. package/dist/package.js.map +1 -1
  77. package/eslintrc.json +0 -1
  78. package/files/drugs-props-train-scores.csv +664 -0
  79. package/package.json +11 -7
  80. package/src/package-api.ts +7 -3
  81. package/src/package-test.ts +4 -1
  82. package/src/package.g.ts +21 -9
  83. package/src/package.ts +33 -23
  84. package/src/pareto-optimization/pareto-computations.ts +6 -0
  85. package/src/pareto-optimization/pareto-optimizer.ts +1 -1
  86. package/src/pls/pls-constants.ts +3 -1
  87. package/src/pls/pls-tools.ts +73 -69
  88. package/src/probabilistic-scoring/data-generator.ts +202 -0
  89. package/src/probabilistic-scoring/nelder-mead.ts +204 -0
  90. package/src/probabilistic-scoring/pmpo-defs.ts +141 -3
  91. package/src/probabilistic-scoring/pmpo-utils.ts +240 -126
  92. package/src/probabilistic-scoring/prob-scoring.ts +862 -135
  93. package/src/probabilistic-scoring/stat-tools.ts +141 -6
  94. package/src/tests/anova-tests.ts +1 -1
  95. package/src/tests/classifiers-tests.ts +1 -1
  96. package/src/tests/dim-reduction-tests.ts +1 -1
  97. package/src/tests/linear-methods-tests.ts +1 -1
  98. package/src/tests/mis-vals-imputation-tests.ts +1 -1
  99. package/src/tests/pareto-tests.ts +251 -0
  100. package/src/tests/pmpo-tests.ts +797 -0
  101. package/test-console-output-1.log +303 -239
  102. package/test-record-1.mp4 +0 -0
  103. package/files/mpo-done.ipynb +0 -2123
@@ -1,17 +1,20 @@
1
1
  // Utility functions for probabilistic scoring (pMPO)
2
- // Link: https://pmc.ncbi.nlm.nih.gov/articles/PMC4716604/
2
+ // Source paper https://pmc.ncbi.nlm.nih.gov/articles/PMC4716604/
3
3
 
4
4
  import * as grok from 'datagrok-api/grok';
5
5
  import * as ui from 'datagrok-api/ui';
6
6
  import * as DG from 'datagrok-api/dg';
7
7
 
8
+ import {generateMpoFileName, MPO_PROFILE_CHANGED_EVENT} from '@datagrok-libraries/statistics/src/mpo/utils';
8
9
  import '../../css/pmpo.css';
9
10
 
10
11
  import {COLORS, DESCR_TABLE_TITLE, DESCR_TITLE, DescriptorStatistics, DesirabilityProfileProperties,
11
12
  DESIRABILITY_COL_NAME, FOLDER, P_VAL, PMPO_COMPUTE_FAILED, PmpoParams, SCORES_TITLE,
12
- SELECTED_TITLE, STAT_TO_TITLE_MAP, TINY, WEIGHT_TITLE,
13
- CorrelationTriple} from './pmpo-defs';
14
- import {computeSigmoidParamsFromX0, getCutoffs, normalPdf, sigmoidS, solveNormalIntersection} from './stat-tools';
13
+ SELECTED_TITLE, STAT_TO_TITLE_MAP, TINY, WEIGHT_TITLE, CorrelationTriple,
14
+ BASIC_RANGE_SIGMA_COEFFS, EXTENDED_RANGE_SIGMA_COEFFS, EQUALITY_SIGN,
15
+ PREFERABLE_CATEGORIES} from './pmpo-defs';
16
+ import {computeSigmoidParamsFromX0, getCutoffs, gaussDesirabilityFunc, sigmoidS,
17
+ solveNormalIntersection} from './stat-tools';
15
18
  import {getColorScaleDiv} from '../pareto-optimization/utils';
16
19
  import {OPT_TYPE} from '../pareto-optimization/defs';
17
20
 
@@ -243,11 +246,22 @@ export function getModelParams(desired: DG.DataFrame, nonDesired: DG.DataFrame,
243
246
  // Unbiased standard deviation
244
247
  const sigmaNonDes = nonDesCol.stats.stdev * Math.sqrt((nonDesLen - 1) / nonDesLen);
245
248
 
246
- // Compute cutoffs and intersections
249
+ // Compute cutoffs
247
250
  const cutoffs = getCutoffs(muDes, sigmaDes, muNonDes, sigmaNonDes);
251
+
252
+ // column_stats['inflection'] = np.exp(-np.square((column_stats['cutoff'] - column_stats['good_mean'])) /
253
+ // (2 * np.square(column_stats['good_std'])))
254
+ // Compute inflection point
255
+ const inflection = Math.exp(-((cutoffs.cutoff - muDes) ** 2) / (2 * (sigmaDes ** 2)));
256
+
257
+ // Compute intersections of the two normal distributions
248
258
  const intersections = solveNormalIntersection(muDes, sigmaDes, muNonDes, sigmaNonDes);
249
259
 
250
- // Compute parameters for the generalized sigmoid function
260
+ const b = (Math.pow(inflection, -1.0) - 1.0);
261
+ const n = (Math.pow(qCutoff, -1.0) - 1.0);
262
+ const c = Math.pow(10.0, ((Math.log10(n / b)) / (-1.0 * (muNonDes - cutoffs.cutoff))));
263
+
264
+ // Compute parameters for the generalized sigmoid function TODO: delete
251
265
 
252
266
  let x0: number | null = null;
253
267
 
@@ -279,17 +293,20 @@ export function getModelParams(desired: DG.DataFrame, nonDesired: DG.DataFrame,
279
293
  desStd: sigmaDes,
280
294
  nonDesAvg: muNonDes,
281
295
  nonDesStd: sigmaNonDes,
296
+ min: Math.min(desCol.stats.min, nonDesCol.stats.min),
297
+ max: Math.max(desCol.stats.max, nonDesCol.stats.max),
282
298
  cutoff: cutoffs.cutoff,
283
299
  cutoffDesired: cutoffs.cutoffDesired,
284
300
  cutoffNotDesired: cutoffs.cutoffNotDesired,
285
301
  pX0: sigmoidParams.pX0,
286
- b: sigmoidParams.b,
287
- c: sigmoidParams.c,
302
+ b: b,
303
+ c: c,
288
304
  zScore: z,
289
305
  weight: z,
290
306
  intersections: intersections,
291
307
  x0: x0,
292
308
  xBound: xBound,
309
+ inflection: inflection,
293
310
  });
294
311
  });
295
312
 
@@ -338,12 +355,13 @@ export async function loadPmpoParams(file: DG.FileInfo): Promise<Map<string, Pmp
338
355
  * @param name Name of the desirability profile.
339
356
  * @param description Description of the desirability profile.
340
357
  */
341
- export function getDesirabilityProfileJson(params: Map<string, PmpoParams>, name: string, description: string) {
358
+ export function getDesirabilityProfileJson(params: Map<string, PmpoParams>, useSigmoidalCorrection: boolean,
359
+ name: string, description: string, truncatedRange: boolean): any {
342
360
  return {
343
361
  'type': 'MPO Desirability Profile',
344
362
  'name': name,
345
363
  'description': description,
346
- 'properties': getDesirabilityProfileProperties(params),
364
+ 'properties': getDesirabilityProfileProperties(params, useSigmoidalCorrection, truncatedRange),
347
365
  };
348
366
  }
349
367
 
@@ -351,45 +369,23 @@ export function getDesirabilityProfileJson(params: Map<string, PmpoParams>, name
351
369
  * @param params Map of descriptor names to their pMPO parameters.
352
370
  * @param modelName Suggested model name (used as default file name).
353
371
  */
354
- export async function saveModel(params: Map<string, PmpoParams>, modelName: string): Promise<void> {
355
- let fileName = modelName;
356
- const nameInput = ui.input.string('File', {
357
- value: fileName,
358
- nullable: false,
359
- onValueChanged: (val) => {
360
- fileName = val;
361
- dlg.getButton('Save').disabled = (fileName.length < 1) || (folderName.length < 1);
362
- },
363
- });
364
-
365
- let folderName = FOLDER;
366
- const folderInput = ui.input.string('Folder', {
367
- value: folderName,
368
- nullable: false,
369
- onValueChanged: (val) => {
370
- folderName = val;
371
- dlg.getButton('Save').disabled = (fileName.length < 1) || (folderName.length < 1);
372
- },
372
+ export async function saveModel(params: Map<string, PmpoParams>, modelName: string,
373
+ useSigmoidalCorrection: boolean): Promise<void> {
374
+ const nameInput = ui.input.string('Name', {value: modelName, nullable: false});
375
+ const descriptionInput = ui.input.textArea('Description', {value: ' ', nullable: true});
376
+ const typeInput = ui.input.bool('Desirability Profile', {
377
+ value: true,
378
+ tooltipText: 'Save the model as an MPO Desirability Profile. If disabled, the model is saved in the pMPO format.',
373
379
  });
374
380
 
375
- const save = async () => {
376
- const path = `${folderName}/${fileName}.json`;
377
- try {
378
- const jsonString = JSON.stringify(objectToSave(), null, 2);
379
- await grok.dapi.files.writeAsText(path, jsonString);
380
- grok.shell.info(`Saved to ${path}`);
381
- } catch (err) {
382
- grok.shell.error(`Failed to save: ${err instanceof Error ? err.message : 'the platform issue'}.`);
383
- }
384
- dlg.close();
385
- };
386
-
387
381
  const objectToSave = () => {
388
382
  if (typeInput.value) {
389
383
  return getDesirabilityProfileJson(
390
384
  params,
385
+ useSigmoidalCorrection,
391
386
  nameInput.value,
392
387
  descriptionInput.value,
388
+ false,
393
389
  );
394
390
  }
395
391
 
@@ -401,34 +397,29 @@ export async function saveModel(params: Map<string, PmpoParams>, modelName: stri
401
397
  };
402
398
  };
403
399
 
404
- const modelNameInput = ui.input.string('Name', {value: modelName, nullable: true});
405
- const descriptionInput = ui.input.textArea('Description', {value: ' ', nullable: true});
406
- const typeInput = ui.input.bool('Desirability Profile', {
407
- value: true,
408
- tooltipText: 'Save the model as an MPO Desirability Profile. If disabled, the model is saved in the pMPO format.',
409
- });
410
-
411
400
  const dlg = ui.dialog({title: 'Save model'})
412
- .add(ui.h2('Path'))
413
- .add(folderInput)
414
401
  .add(nameInput)
415
- .add(ui.h2('Model'))
416
- .add(modelNameInput)
417
402
  .add(descriptionInput)
418
403
  .add(typeInput)
419
404
  .addButton('Save', async () => {
420
- const exist = await grok.dapi.files.exists(`${folderName}/${fileName}.json`);
421
- if (!exist)
422
- await save();
423
- else {
424
- // Handle overwrite confirmation
425
- ui.dialog({title: 'Warning'})
426
- .add(ui.label('Overwrite existing file?'))
427
- .onOK(async () => await save())
428
- .show();
405
+ try {
406
+ const files = await grok.dapi.files.list(FOLDER);
407
+ const existingFileNames = new Set(files.map((f) => f.name));
408
+ const fileName = generateMpoFileName(nameInput.value, existingFileNames);
409
+ const path = `${FOLDER}/${fileName}`;
410
+ const jsonString = JSON.stringify(objectToSave(), null, 2);
411
+ await grok.dapi.files.writeAsText(path, jsonString);
412
+ grok.events.fireCustomEvent(MPO_PROFILE_CHANGED_EVENT, {});
413
+ grok.shell.info(`Saved to ${path}`);
414
+ } catch (err) {
415
+ grok.shell.error(`Failed to save: ${err instanceof Error ? err.message : 'the platform issue'}.`);
429
416
  }
417
+ dlg.close();
430
418
  })
431
419
  .show();
420
+
421
+ dlg.getButton('Save').disabled = !nameInput.validate();
422
+ nameInput.onInput.subscribe(() => dlg.getButton('Save').disabled = !nameInput.validate());
432
423
  } // saveModel
433
424
 
434
425
  /** Adds columns with correlation coefficients between descriptors.
@@ -462,7 +453,10 @@ export function addCorrelationColumns(df: DG.DataFrame, descriptorNames: string[
462
453
  return df;
463
454
  } // addCorrelationColumns
464
455
 
465
- /* Sets color coding for the p-value column in the statistics table */
456
+ /** Sets color coding for the p-value column in the statistics table
457
+ * @param table DataFrame with descriptor statistics.
458
+ * @param pValTresh P-value threshold.
459
+ */
466
460
  export function setPvalColumnColorCoding(table: DG.DataFrame, pValTresh: number): void {
467
461
  const pValCol = table.col(P_VAL);
468
462
  if (pValCol == null)
@@ -475,7 +469,11 @@ export function setPvalColumnColorCoding(table: DG.DataFrame, pValTresh: number)
475
469
  pValCol.meta.colors.setConditional(rules);
476
470
  } // setPvalColumnColorCoding
477
471
 
478
- /* Sets color coding for the p-value column in the statistics table */
472
+ /** Sets color coding for the correlation columns in the statistics table.
473
+ * @param table DataFrame with descriptor statistics.
474
+ * @param descriptorNames List of descriptor names.
475
+ * @param r2Tresh R-squared threshold.
476
+ */
479
477
  export function setCorrColumnColorCoding(table: DG.DataFrame, descriptorNames: string[], r2Tresh: number): void {
480
478
  descriptorNames.forEach((name) => {
481
479
  const col = table.col(name);
@@ -483,8 +481,8 @@ export function setCorrColumnColorCoding(table: DG.DataFrame, descriptorNames: s
483
481
  return;
484
482
 
485
483
  const rules: Record<string, string> = {};
486
- rules[`>${r2Tresh}`] = COLORS.SKIPPED;
487
- rules[`=<${r2Tresh}`] = COLORS.SELECTED;
484
+ rules[`>=${r2Tresh}`] = COLORS.SKIPPED;
485
+ rules[`<${r2Tresh}`] = COLORS.SELECTED;
488
486
 
489
487
  col.meta.colors.setConditional(rules);
490
488
  });
@@ -492,20 +490,18 @@ export function setCorrColumnColorCoding(table: DG.DataFrame, descriptorNames: s
492
490
 
493
491
  /** Returns desirability profile properties for the given pMPO parameters.
494
492
  * @param params Map of descriptor names to their pMPO parameters.
493
+ * @param useSigmoidalCorrection Whether to use sigmoidal correction in desirability functions.
494
+ * @param displayProfile Whether to create a profile to be displayed in the stat grid (true - truncated range).
495
495
  */
496
- function getDesirabilityProfileProperties(params: Map<string, PmpoParams>) {
496
+ function getDesirabilityProfileProperties(params: Map<string, PmpoParams>,
497
+ useSigmoidalCorrection: boolean, truncatedRange: boolean): DesirabilityProfileProperties {
497
498
  const props: DesirabilityProfileProperties = {};
498
499
 
499
- let maxWeight = 0;
500
- params.forEach((param) => maxWeight = Math.max(maxWeight, param.weight));
501
-
502
- const scale = (maxWeight > 0) ? (1 / maxWeight) : 1;
503
-
504
500
  params.forEach((param, name) => {
505
- const range = significantPoints(param);
501
+ const range = significantPoints(param, truncatedRange);
506
502
  props[name] = {
507
- weight: param.weight * scale,
508
- line: getLine(param),
503
+ weight: param.weight,
504
+ line: getLine(param, useSigmoidalCorrection, truncatedRange),
509
505
  min: Math.min(...range),
510
506
  max: Math.max(...range),
511
507
  };
@@ -514,67 +510,185 @@ function getDesirabilityProfileProperties(params: Map<string, PmpoParams>) {
514
510
  return props;
515
511
  } // getDesirabilityProfileProperties
516
512
 
517
- /** Returns array of arguments for Gaussian function centered at mu with stddev sigma. */
518
- function getArgsOfGaussFunc(mu: number, sigma: number): number[] {
519
- return [
520
- mu - 3 * sigma,
521
- mu - 2.5 * sigma,
522
- mu - 2 * sigma,
523
- mu - 1.5 * sigma,
524
- mu - sigma,
525
- mu - 0.5 * sigma,
526
- mu - 0.25 * sigma,
527
- mu,
528
- mu + 0.25 * sigma,
529
- mu + 0.5 * sigma,
530
- mu + sigma,
531
- mu + 1.5 * sigma,
532
- mu + 2 * sigma,
533
- mu + 2.5 * sigma,
534
- mu + 3 * sigma,
535
- ];
513
+ /** Returns array of arguments for Gaussian function centered at mu with stddev sigma.
514
+ * @param mu Mean of the Gaussian function.
515
+ * @param sigma Standard deviation of the Gaussian function.
516
+ * @param truncatedRange Whether to use truncated range (for interactive app) or extended range (for full profile).
517
+ * @return Array of arguments for the Gaussian function.
518
+ */
519
+ function getArgsOfGaussFunc(mu: number, sigma: number, truncatedRange: boolean): number[] {
520
+ return truncatedRange ?
521
+ BASIC_RANGE_SIGMA_COEFFS.map((coeff) => mu + coeff * sigma) : // range for interactive app
522
+ EXTENDED_RANGE_SIGMA_COEFFS.map((coeff) => mu + coeff * sigma); // actual full range for desirability profile
536
523
  } // getArgsOfGaussFunc
537
524
 
538
- /** Returns scale factor for the given pMPO parameters and range of x values. */
539
- function getScale(param: PmpoParams, range: number[]): number {
540
- const values = range.map((x) => basicFunction(x, param));
525
+ /** Basic pMPO function combining Gaussian and sigmoid functions.
526
+ * @param x Argument.
527
+ * @param param pMPO parameters.
528
+ * @param useSigmoidalCorrection Whether to use sigmoidal correction.
529
+ * @return Value of the basic pMPO function at x.
530
+ */
531
+ function basicFunction(x: number, param: PmpoParams, useSigmoidalCorrection: boolean): number {
532
+ return gaussDesirabilityFunc(x, param.desAvg, param.desStd) *
533
+ (useSigmoidalCorrection ? sigmoidS(x, param.cutoff, param.b, param.c) : 1);
534
+ }
535
+
536
+ /** Returns line points for the given pMPO parameters.
537
+ * @param param pMPO parameters.
538
+ * @param useSigmoidalCorrection Whether to use sigmoidal correction.
539
+ * @param truncatedRange Whether to use truncated range (for interactive app) or extended range (for full profile).
540
+ * @return Array of [x, y] points representing the desirability function line.
541
+ */
542
+ function getLine(param: PmpoParams, useSigmoidalCorrection: boolean, truncatedRange: boolean): [number, number][] {
543
+ const range = significantPoints(param, truncatedRange);
544
+
545
+ return range.map((x) => [x, basicFunction(x, param, useSigmoidalCorrection)]);
546
+ }
547
+
548
+ /** Returns significant points for the given pMPO parameters.
549
+ * @param param pMPO parameters.
550
+ * @param truncatedRange Whether to use truncated range (for interactive app) or extended range (for full profile).
551
+ * @return Array of significant points for the desirability function.
552
+ */
553
+ function significantPoints(param: PmpoParams, truncatedRange: boolean): number[] {
554
+ const points = getArgsOfGaussFunc(param.desAvg, param.desStd, truncatedRange);
555
+
556
+ /* Truncate range to show less points */
557
+ if (truncatedRange) {
558
+ const min = Math.min(param.min, param.desAvg - 3 * param.desStd);
559
+ const max = Math.max(param.max, param.desAvg + 3 * param.desStd);
560
+
561
+ return points
562
+ .filter((x) => (min <= x) && (x <= max))
563
+ .sort();
564
+ }
565
+
566
+ return points;
567
+ } // significantPoints
541
568
 
542
- return Math.max(...values);
569
+ /** Custom error class for pMPO-related errors. */
570
+ export class PmpoError extends Error {
571
+ constructor(message: string) {
572
+ super(message);
573
+ this.name = 'PmpoError';
574
+ }
543
575
  }
544
576
 
545
- /** Basic pMPO function combining Gaussian and sigmoid functions. */
546
- function basicFunction(x: number, param: PmpoParams): number {
547
- return normalPdf(x, param.desAvg, param.desStd) * sigmoidS(x, param.x0, param.b, param.c);
577
+ /** Returns the initial column for the desirability input, preferring boolean columns.
578
+ * @param cols List of columns to choose from.
579
+ * @return The initial column for the desirability input.
580
+ */
581
+ export function getInitCol(cols: DG.Column[]): DG.Column {
582
+ for (const col of cols) {
583
+ if ((col.type === DG.COLUMN_TYPE.BOOL) && (col.stats.stdev > 0))
584
+ return col;
585
+ }
586
+
587
+ for (const col of cols) {
588
+ if ((col.isNumerical) && (col.stats.stdev > 0))
589
+ return col;
590
+ }
591
+
592
+ return cols[0];
548
593
  }
549
594
 
550
- /** Returns line points for the given pMPO parameters. */
551
- function getLine(param: PmpoParams): [number, number][] {
552
- //const range = getArgsOfGaussFunc(param.desAvg, param.desStd);
553
- const range = significantPoints(param);
554
- const scale = getScale(param, range);
595
+ /** Returns a comparator function based on the given equality sign.
596
+ * @param sign Equality sign ('<', '<=', '>', '>=').
597
+ * @return Comparator function that takes two numbers and returns a boolean.
598
+ */
599
+ function getComparator(sign: EQUALITY_SIGN): (a: number, b: number) => boolean {
600
+ switch (sign) {
601
+ case EQUALITY_SIGN.LESS: return (a, b) => a < b;
602
+ case EQUALITY_SIGN.LESS_OR_EQUAL: return (a, b) => a <= b;
603
+ case EQUALITY_SIGN.GREATER: return (a, b) => a > b;
604
+ case EQUALITY_SIGN.GREATER_OR_EQUAL: return (a, b) => a >= b;
605
+
606
+ default:
607
+ throw new Error(`Unsupported sign: ${sign}`);
608
+ }
609
+ }
555
610
 
556
- return range.map((x) => [x, basicFunction(x, param) / scale]);
611
+ /** Converts a numeric column to a boolean column based on the given threshold and equality sign.
612
+ * @param numericCol Numeric column to convert.
613
+ * @param threshold Threshold value for comparison.
614
+ * @param sign Equality sign for comparison ('<', '<=', '>', '>=').
615
+ * @return Boolean column resulting from the comparison.
616
+ */
617
+ export function getBoolDesirabilityColData(numericCol: DG.Column,
618
+ threshold: number, sign: EQUALITY_SIGN): {column: DG.Column, tooltip: string} {
619
+ const boolArr = new Array<boolean>(numericCol.length);
620
+ const numericArr = numericCol.getRawData();
621
+
622
+ const comparator = getComparator(sign);
623
+
624
+ for (let i = 0; i < numericCol.length; ++i)
625
+ boolArr[i] = comparator(numericArr[i], threshold);
626
+
627
+ return {
628
+ column: DG.Column.fromList(DG.COLUMN_TYPE.BOOL, '', boolArr),
629
+ tooltip: `Desirability based on the condition:\n\n **${numericCol.name} ${sign} ${threshold}**`,
630
+ };
557
631
  }
558
632
 
559
- /** Returns significant points for the given pMPO parameters. */
560
- function significantPoints(param: PmpoParams): number[] {
561
- const start = param.desAvg - 10 * param.desStd;
562
- const end = param.desAvg + 10 * param.desStd;
563
- const steps = 1000;
564
-
565
- let arg = start;
566
- let func = basicFunction(arg, param);
567
- let x = 0;
568
- let y = 0;
569
-
570
- for (let i = 0; i <= steps; i++) {
571
- x = start + ((end - start) * i) / steps;
572
- y = basicFunction(x, param);
573
- if (y > func) {
574
- arg = x;
575
- func = y;
576
- }
633
+ /** Checks whether the desirability column is valid based on the given threshold and equality sign.
634
+ * @param desCol Desirability column to check.
635
+ * @param threshold Threshold value for comparison.
636
+ * @param sign Equality sign for comparison ('<', '<=', '>', '>=').
637
+ * @return True if the desirability column is valid, false otherwise.
638
+ */
639
+ export function isDesirabilityValid(desCol: DG.Column, threshold: number, sign: EQUALITY_SIGN): boolean {
640
+ const min = desCol.stats.min;
641
+ const max = desCol.stats.max;
642
+
643
+ switch (sign) {
644
+ case EQUALITY_SIGN.LESS:
645
+ return (max >= threshold) && (min < threshold);
646
+
647
+ case EQUALITY_SIGN.LESS_OR_EQUAL:
648
+ return (max > threshold) && (min <= threshold);
649
+
650
+ case EQUALITY_SIGN.GREATER:
651
+ return (min <= threshold) && (max > threshold);
652
+
653
+ default:
654
+ return (min < threshold) && (max >= threshold);
577
655
  }
656
+ }
578
657
 
579
- return getArgsOfGaussFunc(arg, param.desStd);
580
- } // significantPoints
658
+ /** Converts a string column to a boolean column based on the given desirable categories.
659
+ * @param stringCol String column to convert.
660
+ * @param desirableCategories List of categories that should be considered as desirable.
661
+ * @return Boolean column resulting from the conversion and a tooltip describing the desirability.
662
+ */
663
+ export function getDesirabilityColumnFromCategories(stringCol: DG.Column, desirableCategories: string[]):
664
+ {column: DG.Column, tooltip: string} {
665
+ const boolArr = new Array<boolean>(stringCol.length);
666
+ const raw = stringCol.getRawData();
667
+ const categories = stringCol.categories;
668
+
669
+ for (let i = 0; i < stringCol.length; ++i)
670
+ boolArr[i] = desirableCategories.includes(categories[raw[i]]);
671
+
672
+ const nonDesirableCategories = categories.filter((cat) => !desirableCategories.includes(cat));
673
+
674
+ const c = `\u2705 ${desirableCategories.join(', ')}`;
675
+ const unchecked = `\u274c ${nonDesirableCategories.join(', ')}`;
676
+
677
+ return {
678
+ column: DG.Column.fromList(DG.COLUMN_TYPE.BOOL, '', boolArr),
679
+ tooltip: `Desirability based on the selected categories:\n\n **${c}**\n\n **${unchecked}**`,
680
+ };
681
+ } // getDesirabilityColumnFromCategories
682
+
683
+ /** Returns a list of selected categories based on the given list of categories and preferable categories.
684
+ * @param categories List of categories to select from.
685
+ * @return List of selected categories.
686
+ */
687
+ export function getSelectedCategories(categories: string[]): string[] {
688
+ const selected = categories.filter((cat) => PREFERABLE_CATEGORIES.includes(cat));
689
+
690
+ if (selected.length > 0)
691
+ return selected;
692
+
693
+ return [categories[0]];
694
+ }