@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
package/package.json CHANGED
@@ -1,25 +1,26 @@
1
1
  {
2
2
  "name": "@datagrok/eda",
3
3
  "friendlyName": "EDA",
4
- "version": "1.4.12",
4
+ "version": "1.5.0",
5
5
  "description": "Exploratory Data Analysis Tools",
6
6
  "dependencies": {
7
7
  "@datagrok-libraries/math": "^1.2.6",
8
- "@datagrok-libraries/ml": "^6.10.8",
9
- "@datagrok-libraries/statistics": "^1.10.0",
8
+ "@datagrok-libraries/ml": "^6.10.10",
9
+ "@datagrok-libraries/statistics": "^1.12.0",
10
10
  "@datagrok-libraries/tutorials": "^1.7.4",
11
- "@datagrok-libraries/utils": "^4.6.5",
11
+ "@datagrok-libraries/utils": "^4.7.0",
12
12
  "@keckelt/tsne": "^1.0.2",
13
13
  "@webgpu/types": "^0.1.40",
14
14
  "cash-dom": "^8.1.1",
15
- "datagrok-api": "^1.26.3",
15
+ "datagrok-api": "^1.27.0",
16
16
  "dayjs": "^1.11.9",
17
17
  "jstat": "^1.9.6",
18
18
  "mathjs": "^15.1.0",
19
19
  "source-map-loader": "^4.0.1",
20
20
  "umap-js": "^1.3.3",
21
21
  "worker-loader": "^3.0.8",
22
- "wu": "^2.1.0"
22
+ "wu": "^2.1.0",
23
+ "@datagrok-libraries/test": "^1.1.0"
23
24
  },
24
25
  "author": {
25
26
  "name": "Viktor Makarichev",
@@ -29,7 +30,7 @@
29
30
  "@typescript-eslint/eslint-plugin": "^5.32.0",
30
31
  "@typescript-eslint/parser": "^5.32.0",
31
32
  "css-loader": "^7.1.2",
32
- "datagrok-tools": "^4.14.55",
33
+ "datagrok-tools": "^5.1.5",
33
34
  "eslint": "^8.21.0",
34
35
  "eslint-config-google": "^0.14.0",
35
36
  "style-loader": "^4.0.0",
@@ -97,5 +98,8 @@
97
98
  "Reduce Dimensionality": null
98
99
  }
99
100
  }
101
+ },
102
+ "overrides": {
103
+ "datagrok-api": "$datagrok-api"
100
104
  }
101
105
  }
@@ -282,10 +282,14 @@ export namespace funcs {
282
282
  return await grok.functions.call('EDA:TrainPmpo', {});
283
283
  }
284
284
 
285
+ export async function getPmpoAppItems(view: DG.View ): Promise<any> {
286
+ return await grok.functions.call('EDA:GetPmpoAppItems', { view });
287
+ }
288
+
285
289
  /**
286
- Apply trained probabilistic multi-parameter optimization (pMPO) model to score samples
290
+ Generates syntethetic dataset oriented on the pMPO modeling
287
291
  */
288
- export async function applyPmpo(table: DG.DataFrame , file: DG.FileInfo ): Promise<void> {
289
- return await grok.functions.call('EDA:ApplyPmpo', { table, file });
292
+ export async function generatePmpoDataset(samples: number ): Promise<DG.DataFrame> {
293
+ return await grok.functions.call('EDA:GeneratePmpoDataset', { samples });
290
294
  }
291
295
  }
@@ -1,10 +1,13 @@
1
1
  import * as DG from 'datagrok-api/dg';
2
- import {runTests, tests, TestContext, initAutoTests as initTests} from '@datagrok-libraries/utils/src/test';
2
+ import {runTests, tests, TestContext, initAutoTests as initTests} from '@datagrok-libraries/test/src/test';
3
3
  import './tests/dim-reduction-tests';
4
4
  import './tests/linear-methods-tests';
5
5
  import './tests/classifiers-tests';
6
6
  import './tests/mis-vals-imputation-tests';
7
7
  import './tests/anova-tests';
8
+ import './tests/pmpo-tests';
9
+ import './tests/pareto-tests';
10
+
8
11
  export const _package = new DG.Package();
9
12
  export {tests};
10
13
 
package/src/package.g.ts CHANGED
@@ -7,6 +7,7 @@ export function info() : void {
7
7
  }
8
8
 
9
9
  //tags: init
10
+ //meta.role: init
10
11
  export async function init() : Promise<void> {
11
12
  await PackageFunctions.init();
12
13
  }
@@ -37,34 +38,37 @@ export async function PCA(table: DG.DataFrame, features: DG.ColumnList, componen
37
38
  }
38
39
 
39
40
  //name: DBSCAN clustering
41
+ //tags: dim-red-postprocessing-function
40
42
  //input: column col1
41
43
  //input: column col2
42
44
  //input: double epsilon = 0.01 { description: Minimum distance between two points to be considered as in the same neighborhood. }
43
45
  //input: int minimumPoints = 5 { description: Minimum number of points to form a dense region. }
44
46
  //meta.defaultPostProcessingFunction: true
45
- //meta.role: dimRedPostprocessingFunction
47
+ //meta.role: dim-red-postprocessing-function
46
48
  export async function dbscanPostProcessingFunction(col1: DG.Column, col2: DG.Column, epsilon: number, minimumPoints: number) : Promise<void> {
47
49
  await PackageFunctions.dbscanPostProcessingFunction(col1, col2, epsilon, minimumPoints);
48
50
  }
49
51
 
50
52
  //name: None (number)
53
+ //tags: dim-red-preprocessing-function
51
54
  //input: column col
52
55
  //input: string _metric { optional: true }
53
56
  //output: object result
54
57
  //meta.supportedTypes: int,float,double,qnum
55
58
  //meta.supportedDistanceFunctions: Difference
56
- //meta.role: dimRedPreprocessingFunction
59
+ //meta.role: dim-red-preprocessing-function
57
60
  export function numberPreprocessingFunction(col: DG.Column, _metric: string) {
58
61
  return PackageFunctions.numberPreprocessingFunction(col, _metric);
59
62
  }
60
63
 
61
64
  //name: None (string)
65
+ //tags: dim-red-preprocessing-function
62
66
  //input: column col
63
67
  //input: string _metric { optional: true }
64
68
  //output: object result
65
69
  //meta.supportedTypes: string
66
70
  //meta.supportedDistanceFunctions: One-Hot,Levenshtein,Hamming
67
- //meta.role: dimRedPreprocessingFunction
71
+ //meta.role: dim-red-preprocessing-function
68
72
  export function stringPreprocessingFunction(col: DG.Column, _metric: string) {
69
73
  return PackageFunctions.stringPreprocessingFunction(col, _metric);
70
74
  }
@@ -77,6 +81,7 @@ export async function reduceDimensionality() : Promise<void> {
77
81
 
78
82
  //tags: editor
79
83
  //input: funccall call
84
+ //meta.role: editor
80
85
  export function GetMCLEditor(call: DG.FuncCall) : void {
81
86
  PackageFunctions.GetMCLEditor(call);
82
87
  }
@@ -102,6 +107,7 @@ export async function MCLClustering(df: DG.DataFrame, cols: DG.Column[], metrics
102
107
 
103
108
  //name: MCL
104
109
  //description: Markov clustering viewer
110
+ //tags: viewer
105
111
  //output: viewer result
106
112
  //meta.showInGallery: false
107
113
  //meta.role: viewer
@@ -532,6 +538,7 @@ export function paretoFront() : void {
532
538
 
533
539
  //name: Pareto front
534
540
  //description: Pareto front viewer
541
+ //tags: viewer
535
542
  //output: viewer result
536
543
  //meta.icon: icons/pareto-front-viewer.svg
537
544
  //meta.role: viewer
@@ -540,14 +547,19 @@ export function paretoFrontViewer() : any {
540
547
  }
541
548
 
542
549
  //description: Train probabilistic multi-parameter optimization (pMPO) model
543
- //top-menu: Chem | Calculate | Train pMPO...
544
550
  export function trainPmpo() : void {
545
551
  PackageFunctions.trainPmpo();
546
552
  }
547
553
 
548
- //description: Apply trained probabilistic multi-parameter optimization (pMPO) model to score samples
549
- //input: dataframe table
550
- //input: file file
551
- export async function applyPmpo(table: DG.DataFrame, file: DG.FileInfo) : Promise<void> {
552
- await PackageFunctions.applyPmpo(table, file);
554
+ //input: view view
555
+ //output: object result
556
+ export function getPmpoAppItems(view: any) : any {
557
+ return PackageFunctions.getPmpoAppItems(view);
558
+ }
559
+
560
+ //description: Generates syntethetic dataset oriented on the pMPO modeling
561
+ //input: int samples
562
+ //output: dataframe Synthetic
563
+ export async function generatePmpoDataset(samples: number) : Promise<any> {
564
+ return await PackageFunctions.generatePmpoDataset(samples);
553
565
  }
package/src/package.ts CHANGED
@@ -36,10 +36,12 @@ import {SoftmaxClassifier} from './softmax-classifier';
36
36
 
37
37
  import {initXgboost} from '../wasm/xgbooster';
38
38
  import {XGBooster} from './xgbooster';
39
+
39
40
  import {ParetoOptimizer} from './pareto-optimization/pareto-optimizer';
40
41
  import {ParetoFrontViewer} from './pareto-optimization/pareto-front-viewer';
42
+
41
43
  import {Pmpo} from './probabilistic-scoring/prob-scoring';
42
- import {loadPmpoParams} from './probabilistic-scoring/pmpo-utils';
44
+ import {getSynteticPmpoData} from './probabilistic-scoring/data-generator';
43
45
 
44
46
  export const _package = new DG.Package();
45
47
  export * from './package.g';
@@ -53,7 +55,7 @@ export class PackageFunctions {
53
55
  }
54
56
 
55
57
 
56
- @grok.decorators.init({})
58
+ @grok.decorators.init({tags: ['init']})
57
59
  static async init(): Promise<void> {
58
60
  await _initEDAAPI();
59
61
  await initXgboost();
@@ -115,8 +117,9 @@ export class PackageFunctions {
115
117
 
116
118
 
117
119
  @grok.decorators.func({
118
- 'meta': {'defaultPostProcessingFunction': 'true', role: 'dimRedPostprocessingFunction'},
120
+ 'meta': {'defaultPostProcessingFunction': 'true', 'role': 'dim-red-postprocessing-function'},
119
121
  'name': 'DBSCAN clustering',
122
+ 'tags': ['dim-red-postprocessing-function'],
120
123
  })
121
124
  static async dbscanPostProcessingFunction(
122
125
  col1: DG.Column,
@@ -145,9 +148,10 @@ export class PackageFunctions {
145
148
  'meta': {
146
149
  'supportedTypes': 'int,float,double,qnum',
147
150
  'supportedDistanceFunctions': 'Difference',
148
- 'role': 'dimRedPreprocessingFunction'
151
+ 'role': 'dim-red-preprocessing-function',
149
152
  },
150
153
  'name': 'None (number)',
154
+ 'tags': ['dim-red-preprocessing-function'],
151
155
  'outputs': [{name: 'result', type: 'object'}],
152
156
  })
153
157
  static numberPreprocessingFunction(
@@ -163,8 +167,9 @@ export class PackageFunctions {
163
167
  'meta': {
164
168
  'supportedTypes': 'string',
165
169
  'supportedDistanceFunctions': 'One-Hot,Levenshtein,Hamming',
166
- 'role': 'dimRedPreprocessingFunction'
170
+ 'role': 'dim-red-preprocessing-function',
167
171
  },
172
+ 'tags': ['dim-red-preprocessing-function'],
168
173
  'name': 'None (string)',
169
174
  'outputs': [{name: 'result', type: 'object'}],
170
175
  })
@@ -219,7 +224,7 @@ export class PackageFunctions {
219
224
  }
220
225
 
221
226
 
222
- @grok.decorators.editor()
227
+ @grok.decorators.editor({tags: ['editor']})
223
228
  static GetMCLEditor(
224
229
  call: DG.FuncCall): void {
225
230
  try {
@@ -287,6 +292,7 @@ export class PackageFunctions {
287
292
  @grok.decorators.func({
288
293
  'outputs': [{'name': 'result', 'type': 'viewer'}],
289
294
  'meta': {showInGallery: 'false', role: 'viewer'},
295
+ 'tags': ['viewer'],
290
296
  'name': 'MCL',
291
297
  'description': 'Markov clustering viewer',
292
298
  })
@@ -979,14 +985,14 @@ export class PackageFunctions {
979
985
  'name': 'Pareto front',
980
986
  'description': 'Pareto front viewer',
981
987
  'outputs': [{'name': 'result', 'type': 'viewer'}],
982
- 'meta': {'icon': 'icons/pareto-front-viewer.svg', role: 'viewer'},
988
+ 'meta': {'icon': 'icons/pareto-front-viewer.svg', 'role': 'viewer'},
989
+ 'tags': ['viewer'],
983
990
  })
984
991
  static paretoFrontViewer(): DG.Viewer {
985
992
  return new ParetoFrontViewer();
986
993
  }
987
994
 
988
995
  @grok.decorators.func({
989
- 'top-menu': 'Chem | Calculate | Train pMPO...',
990
996
  'name': 'trainPmpo',
991
997
  'description': 'Train probabilistic multi-parameter optimization (pMPO) model',
992
998
  })
@@ -1004,22 +1010,26 @@ export class PackageFunctions {
1004
1010
  pMPO.runTrainingApp();
1005
1011
  }
1006
1012
 
1013
+ @grok.decorators.func({'name': 'getPmpoAppItems', 'outputs': [{name: 'result', type: 'object'}]})
1014
+ static getPmpoAppItems(@grok.decorators.param({type: 'view'}) view: DG.TableView): any | null {
1015
+ const df = view.dataFrame;
1016
+ if (!Pmpo.isTableValid(df))
1017
+ return null;
1018
+
1019
+ const pMPO = new Pmpo(df, view);
1020
+
1021
+ return pMPO.getPmpoAppItems();
1022
+ }
1023
+
1007
1024
  @grok.decorators.func({
1008
- //'top-menu': 'ML | Apply pMPO...',
1009
- 'name': 'applyPmpo',
1010
- 'description': 'Apply trained probabilistic multi-parameter optimization (pMPO) model to score samples',
1025
+ 'name': 'generatePmpoDataset',
1026
+ 'description': 'Generates syntethetic dataset oriented on the pMPO modeling',
1027
+ 'outputs': [{name: 'Synthetic', type: 'dataframe'}],
1011
1028
  })
1012
- static async applyPmpo(
1013
- @grok.decorators.param({'type': 'dataframe'}) table: DG.DataFrame,
1014
- @grok.decorators.param({'type': 'file'}) file: DG.FileInfo,
1015
- ): Promise<void> {
1016
- try {
1017
- const params = await loadPmpoParams(file);
1018
- const predName = table.columns.getUnusedName('pMPO score');
1019
- const prediction = Pmpo.predict(table, params, predName);
1020
- table.columns.add(prediction, true);
1021
- } catch (err) {
1022
- grok.shell.warning(`Failed to apply pMPO: ${err instanceof Error ? err.message : 'the platform issue.'}`);
1023
- }
1029
+ static async generatePmpoDataset(@grok.decorators.param({'type': 'int'}) samples: number): Promise<DG.DataFrame> {
1030
+ const df = await getSynteticPmpoData(samples, false);
1031
+ df.name = 'Synthetic';
1032
+ return df;
1024
1033
  }
1034
+
1025
1035
  }
@@ -2,6 +2,12 @@
2
2
 
3
3
  import {NumericArray, OPT_TYPE} from './defs';
4
4
 
5
+ /** Computes the Pareto front mask for a given dataset and optimization sense
6
+ * @param rawData Array of numeric arrays representing the dataset (each array corresponds to a feature/dimension)
7
+ * @param sense Array of optimization types (OPT_TYPE.MIN or OPT_TYPE.MAX) for each dimension
8
+ * @param nPoints Number of data points in the dataset
9
+ * @param nullIndices Optional set of indices corresponding to missing values (these points will be marked as non-optimal)
10
+ * @returns Boolean array where true indicates that the point is on the Pareto front */
5
11
  export function getParetoMask(rawData: NumericArray[], sense: OPT_TYPE[], nPoints: number,
6
12
  nullIndices?: Set<number>): boolean[] {
7
13
  if (nPoints === 0)
@@ -19,7 +19,7 @@ export class ParetoOptimizer {
19
19
  private toUpdatePcCols = false;
20
20
  private paretoFrontViewer: DG.Viewer;
21
21
  private resultColName: string;
22
- private intervalId: NodeJS.Timeout | null = null;
22
+ private intervalId: ReturnType<typeof setInterval> | null = null;
23
23
  private inputsMap = new Map<string, DG.InputBase>();
24
24
  private pcPlotNode: DG.DockNode | null = null;
25
25
  private inputFormNode: DG.DockNode | null = null;
@@ -17,8 +17,10 @@ export enum ERROR_MSG {
17
17
  ENOUGH = 'Not enough of features',
18
18
  COMP_LIN_PLS = 'Components count must be less than the number of features',
19
19
  COMP_QUA_PLS = 'Too large components count for the quadratic PLS regression',
20
- COMPONENTS = 'Components count must be greater than 1',
20
+ COMP_ROWS = 'Components count must not exceed the number of rows',
21
+ COMPONENTS = 'Components count must be at least 1',
21
22
  INV_INP = 'Invalid inputs',
23
+ NULL_COMPS = 'Components count is not specified',
22
24
  }
23
25
 
24
26
  /** Widget titles */
@@ -36,6 +36,24 @@ export type PlsInput = {
36
36
 
37
37
  type TypedArray = Int32Array | Float32Array | Uint32Array | Float64Array;
38
38
 
39
+ /** Set style for input element depending on the validity of the value */
40
+ function setStyle(valid: boolean, element: HTMLElement, tooltip: string, errorMsg: string) {
41
+ if (valid) {
42
+ element.style.color = COLOR.VALID_TEXT;
43
+ element.style.borderBottomColor = COLOR.VALID_LINE;
44
+ ui.tooltip.bind(element, tooltip);
45
+ } else {
46
+ element.style.color = COLOR.INVALID;
47
+ element.style.borderBottomColor = COLOR.INVALID;
48
+ ui.tooltip.bind(element, () => {
49
+ const hint = ui.label(tooltip);
50
+ const err = ui.label(errorMsg);
51
+ err.style.color = COLOR.INVALID;
52
+ return ui.divV([hint, err]);
53
+ });
54
+ }
55
+ };
56
+
39
57
  /** Return lines */
40
58
  export function getLines(names: string[]): DG.FormulaLine[] {
41
59
  const lines: DG.FormulaLine[] = [];
@@ -137,7 +155,7 @@ function getQuadraticPlsInput(input: PlsInput): PlsInput {
137
155
 
138
156
  for (let j = i; j < colsCount; ++j) {
139
157
  col2 = cols[j];
140
- raw2 = col2.getRawData();
158
+ raw2 = col2.getRawData();
141
159
  qaudrRaw = new Float32Array(rowsCount);
142
160
 
143
161
  for (let k = 0; k < rowsCount; ++k)
@@ -268,7 +286,7 @@ async function performMVA(input: PlsInput, analysisType: PLS_ANALYSIS): Promise<
268
286
  });
269
287
 
270
288
 
271
- // 4.3) create lines & circles
289
+ // 4.3) create lines & circles
272
290
  view.addViewer(scoresScatter);
273
291
  scoresScatter.meta.formulaLines.addAll(getLines(scoreNames));
274
292
 
@@ -321,7 +339,7 @@ async function performMVA(input: PlsInput, analysisType: PLS_ANALYSIS): Promise<
321
339
  }));
322
340
 
323
341
  // emphasize viewers in the demo case
324
- if (analysisType === PLS_ANALYSIS.DEMO) {
342
+ if (analysisType === PLS_ANALYSIS.DEMO) {
325
343
  grok.shell.windows.help.showHelp(ui.markdown(DEMO_RESULTS_MD));
326
344
 
327
345
  describeElements(
@@ -372,38 +390,32 @@ export async function runMVA(analysisType: PLS_ANALYSIS): Promise<void> {
372
390
  return;
373
391
  }
374
392
 
375
- let features: DG.Column[] = numCols.slice(0, numCols.length - 1);
376
- let predict = numCols[numCols.length - 1];
377
- let components = min(numColNames.length - 1, COMPONENTS.DEFAULT as number);
378
- let isQuadratic = false;
379
-
380
- const isPredictValid = () => {
381
- for (const col of features)
382
- if (col.name === predict.name)
383
- return false;
384
- return true;
393
+ const doFeaturesIncludePredict = () => {
394
+ return featuresInput.value.some((col) => col.name === predictInput.value!.name);
385
395
  };
386
396
 
387
397
  const isCompConsistent = () => {
388
- if (components < 1)
398
+ if (componentsInput.value! < 1)
389
399
  return false;
390
400
 
391
- const n = features.length;
401
+ if (componentsInput.value! > table.rowCount)
402
+ return false;
392
403
 
393
- if (isQuadratic)
394
- return components <= (n + 1) * n / 2 + n;
404
+ const n = featuresInput.value.length;
395
405
 
396
- return components <= n;
397
- }
406
+ if (isQuadraticInput.value)
407
+ return componentsInput.value! <= (n + 1) * n / 2 + n;
408
+
409
+ return componentsInput.value! <= n;
410
+ };
398
411
 
399
412
  // response (to predict)
400
413
  const predictInput = ui.input.column(TITLE.PREDICT, {
401
414
  table: table,
402
- value: predict,
415
+ value: numCols[numCols.length - 1],
403
416
  nullable: false,
404
- onValueChanged: (value) => {
405
- predict = value;
406
- updateIputs();
417
+ onValueChanged: (_) => {
418
+ updateInputStyles();
407
419
  },
408
420
  filter: (col: DG.Column) => isValidNumeric(col),
409
421
  tooltipText: HINT.PREDICT,
@@ -413,21 +425,21 @@ export async function runMVA(analysisType: PLS_ANALYSIS): Promise<void> {
413
425
  const featuresInput = ui.input.columns(TITLE.USING, {
414
426
  table: table,
415
427
  available: numColNames,
416
- value: features,
417
- onValueChanged: (val) => {
418
- features = val;
419
- updateIputs();
428
+ value: numCols.slice(0, numCols.length - 1),
429
+ onValueChanged: (_) => {
430
+ updateInputStyles();
420
431
  },
421
432
  tooltipText: HINT.FEATURES,
433
+ nullable: false,
422
434
  });
423
435
 
424
436
  // components count
425
437
  const componentsInput = ui.input.int(TITLE.COMPONENTS, {
426
- value: components,
438
+ value: min(numColNames.length - 1, COMPONENTS.DEFAULT as number),
427
439
  showPlusMinus: true,
428
- onValueChanged: (val) => {
429
- components = val;
430
- updateIputs();
440
+ nullable: false,
441
+ onValueChanged: (_) => {
442
+ updateInputStyles();
431
443
  },
432
444
  tooltipText: HINT.COMPONENTS,
433
445
  });
@@ -446,28 +458,14 @@ export async function runMVA(analysisType: PLS_ANALYSIS): Promise<void> {
446
458
  dlgRunBtnTooltip = HINT.MVA;
447
459
  }
448
460
 
449
- const setStyle = (valid: boolean, element: HTMLElement, tooltip: string, errorMsg: string) => {
450
- if (valid) {
451
- element.style.color = COLOR.VALID_TEXT;
452
- element.style.borderBottomColor = COLOR.VALID_LINE;
453
- ui.tooltip.bind(element, tooltip);
454
- } else {
455
- element.style.color = COLOR.INVALID;
456
- element.style.borderBottomColor = COLOR.INVALID;
457
- ui.tooltip.bind(element, () => {
458
- const hint = ui.label(tooltip);
459
- const err = ui.label(errorMsg);
460
- err.style.color = COLOR.INVALID;
461
- return ui.divV([hint, err]);
462
- });
463
- }
464
- };
465
-
466
- const updateIputs = () => {
467
- const predValid = isPredictValid();
461
+ const updateInputStyles = () => {
462
+ const featuresValid = featuresInput.value.length >= 1;
463
+ const predValid = featuresValid && !doFeaturesIncludePredict();
468
464
  let compValid: boolean;
469
465
 
470
- if (predValid) {
466
+ if (!featuresValid)
467
+ setStyle(false, featuresInput.input, HINT.FEATURES, ERROR_MSG.ENOUGH);
468
+ else if (predValid) {
471
469
  setStyle(true, predictInput.input, HINT.PREDICT, '');
472
470
  setStyle(true, featuresInput.input, HINT.FEATURES, '');
473
471
  } else {
@@ -475,9 +473,12 @@ export async function runMVA(analysisType: PLS_ANALYSIS): Promise<void> {
475
473
  setStyle(false, featuresInput.input, HINT.FEATURES, ERROR_MSG.PREDICT);
476
474
  }
477
475
 
478
- if (components < 1) {
476
+ if (componentsInput.value == null) {
477
+ setStyle(false, componentsInput.input, HINT.COMPONENTS, ERROR_MSG.NULL_COMPS);
478
+ compValid = false;
479
+ } else if (componentsInput.value < 1) {
479
480
  setStyle(false, componentsInput.input, HINT.COMPONENTS, ERROR_MSG.COMPONENTS);
480
- compValid = false;
481
+ compValid = false;
481
482
  } else {
482
483
  compValid = isCompConsistent();
483
484
 
@@ -486,7 +487,9 @@ export async function runMVA(analysisType: PLS_ANALYSIS): Promise<void> {
486
487
  if (predValid)
487
488
  setStyle(true, featuresInput.input, HINT.FEATURES, '');
488
489
  } else {
489
- const errMsg = isQuadratic ? ERROR_MSG.COMP_QUA_PLS : ERROR_MSG.COMP_LIN_PLS;
490
+ const errMsg = componentsInput.value! > table.rowCount ?
491
+ ERROR_MSG.COMP_ROWS :
492
+ isQuadraticInput.value ? ERROR_MSG.COMP_QUA_PLS : ERROR_MSG.COMP_LIN_PLS;
490
493
  setStyle(false, componentsInput.input, HINT.COMPONENTS, errMsg);
491
494
  setStyle(false, featuresInput.input, HINT.FEATURES, ERROR_MSG.ENOUGH);
492
495
  }
@@ -497,10 +500,18 @@ export async function runMVA(analysisType: PLS_ANALYSIS): Promise<void> {
497
500
  dlg.getButton(TITLE.RUN).disabled = !isValid;
498
501
 
499
502
  return isValid;
503
+ }; // updateInputStyles
504
+
505
+ const getStrColWithUniqueVals = () => {
506
+ for (const col of strCols) {
507
+ if (col.stats.uniqueCount === table.rowCount)
508
+ return col;
509
+ }
510
+ return undefined;
500
511
  };
501
512
 
502
513
  // names of samples
503
- let names = (strCols.length > 0) ? strCols[0] : undefined;
514
+ let names = getStrColWithUniqueVals();
504
515
  const namesInputs = ui.input.column(TITLE.NAMES, {
505
516
  table: table,
506
517
  value: names,
@@ -512,11 +523,10 @@ export async function runMVA(analysisType: PLS_ANALYSIS): Promise<void> {
512
523
 
513
524
  // quadratic/linear model
514
525
  const isQuadraticInput = ui.input.bool(TITLE.QUADRATIC, {
515
- value: isQuadratic,
526
+ value: false,
516
527
  tooltipText: HINT.QUADRATIC,
517
- onValueChanged: (val) => {
518
- isQuadratic = val;
519
- updateIputs();
528
+ onValueChanged: (_) => {
529
+ updateInputStyles();
520
530
  },
521
531
  });
522
532
 
@@ -527,21 +537,15 @@ export async function runMVA(analysisType: PLS_ANALYSIS): Promise<void> {
527
537
 
528
538
  await performMVA({
529
539
  table: table,
530
- features: DG.DataFrame.fromColumns(features).columns,
531
- predict: predict,
532
- components: components,
533
- isQuadratic: isQuadratic,
540
+ features: DG.DataFrame.fromColumns(featuresInput.value).columns,
541
+ predict: predictInput.value!,
542
+ components: componentsInput.value!,
543
+ isQuadratic: isQuadraticInput.value,
534
544
  names: names,
535
545
  }, analysisType);
536
546
  }, undefined, dlgRunBtnTooltip)
537
547
  .show({x: X_COORD, y: Y_COORD});
538
548
 
539
- // the following delay provides correct styles (see https://reddata.atlassian.net/browse/GROK-15196)
540
- setTimeout(() => {
541
- featuresInput.value = numCols.filter((col) => col !== predict);
542
- features = featuresInput.value;
543
- }, TIMEOUT);
544
-
545
549
  grok.shell.v.append(dlg.root);
546
550
  } // runMVA
547
551