@datagrok/eda 1.5.0 → 1.5.1

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/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@datagrok/eda",
3
3
  "friendlyName": "EDA",
4
- "version": "1.5.0",
4
+ "version": "1.5.1",
5
5
  "description": "Exploratory Data Analysis Tools",
6
6
  "dependencies": {
7
7
  "@datagrok-libraries/math": "^1.2.6",
8
8
  "@datagrok-libraries/ml": "^6.10.10",
9
- "@datagrok-libraries/statistics": "^1.12.0",
9
+ "@datagrok-libraries/statistics": "^1.12.1",
10
10
  "@datagrok-libraries/tutorials": "^1.7.4",
11
11
  "@datagrok-libraries/utils": "^4.7.0",
12
12
  "@keckelt/tsne": "^1.0.2",
@@ -46,6 +46,7 @@ export enum TITLE {
46
46
  BROWSE = 'Browse',
47
47
  ANALYSIS = 'Features Analysis',
48
48
  QUADRATIC = 'Quadratic',
49
+ BIAS = 'bias',
49
50
  }
50
51
 
51
52
  /** Tooltips */
@@ -102,6 +103,10 @@ export const X_COORD = 200;
102
103
  export const Y_COORD = 200;
103
104
  export const DELAY = 2000;
104
105
 
106
+ export const MAX_ROWS_IN_PREDICTION_TOOLTIP = 20;
107
+
108
+ export const NUMS_AFTER_COMMA = 3;
109
+
105
110
  /** Curves colors */
106
111
  export enum COLOR {
107
112
  AXIS = '#838383',
@@ -4,9 +4,10 @@ import * as grok from 'datagrok-api/grok';
4
4
  import * as ui from 'datagrok-api/ui';
5
5
  import * as DG from 'datagrok-api/dg';
6
6
 
7
- import {PLS_ANALYSIS, ERROR_MSG, TITLE, HINT, LINK, COMPONENTS, INT, TIMEOUT,
7
+ import {PLS_ANALYSIS, ERROR_MSG, TITLE, HINT, LINK, COMPONENTS,
8
8
  RESULT_NAMES, WASM_OUTPUT_IDX, RADIUS, LINE_WIDTH, COLOR, X_COORD, Y_COORD,
9
- DEMO_INTRO_MD, DEMO_RESULTS_MD, DEMO_RESULTS} from './pls-constants';
9
+ DEMO_INTRO_MD, DEMO_RESULTS_MD, DEMO_RESULTS, NUMS_AFTER_COMMA,
10
+ MAX_ROWS_IN_PREDICTION_TOOLTIP} from './pls-constants';
10
11
  import {checkWasmDimensionReducerInputs, checkColumnType, checkMissingVals, describeElements} from '../utils';
11
12
  import {_partialLeastSquareRegressionInWebWorker} from '../../wasm/EDAAPI';
12
13
  import {carsDataframe} from '../data-generators';
@@ -54,6 +55,19 @@ function setStyle(valid: boolean, element: HTMLElement, tooltip: string, errorMs
54
55
  }
55
56
  };
56
57
 
58
+ function getModelFormulaTerms(loadingsRegrCoefsTable: DG.DataFrame, bias: number): Map<string, number> {
59
+ const featureNames = loadingsRegrCoefsTable.col(TITLE.FEATURE)!.toList() as string[];
60
+ const regrCoefs = loadingsRegrCoefsTable.col(TITLE.REGR_COEFS)!.getRawData();
61
+
62
+ const terms = new Map([[TITLE.BIAS as string, bias]]);
63
+
64
+ featureNames.forEach((name, idx) => {
65
+ terms.set(name, regrCoefs[idx]);
66
+ });
67
+
68
+ return terms;
69
+ }
70
+
57
71
  /** Return lines */
58
72
  export function getLines(names: string[]): DG.FormulaLine[] {
59
73
  const lines: DG.FormulaLine[] = [];
@@ -115,7 +129,7 @@ export async function getPlsAnalysis(input: PlsInput): Promise<PlsOutput> {
115
129
 
116
130
  /** Return debiased predction by PLS regression */
117
131
  function debiasedPrediction(features: DG.ColumnList, params: DG.Column,
118
- target: DG.Column, biasedPrediction: DG.Column): DG.Column {
132
+ target: DG.Column, biasedPrediction: DG.Column): {debiased: DG.Column, bias: number} {
119
133
  const samples = target.length;
120
134
  const dim = features.length;
121
135
  const rawParams = params.getRawData();
@@ -131,7 +145,7 @@ function debiasedPrediction(features: DG.ColumnList, params: DG.Column,
131
145
  for (let i = 0; i < samples; ++i)
132
146
  debiased[i] = bias + biased[i];
133
147
 
134
- return DG.Column.fromFloat32Array('Debiased', debiased, samples);
148
+ return {debiased: DG.Column.fromFloat32Array('Debiased', debiased, samples), bias: bias};
135
149
  }
136
150
 
137
151
  /** Return an input for the quadratic PLS regression */
@@ -223,7 +237,8 @@ async function performMVA(input: PlsInput, analysisType: PLS_ANALYSIS): Promise<
223
237
 
224
238
  // 1. Predicted vs Reference scatter plot
225
239
  // Debias prediction (since PLS center data)
226
- const pred = debiasedPrediction(features, result.regressionCoefficients, input.predict, result.prediction);
240
+ const debiased = debiasedPrediction(features, result.regressionCoefficients, input.predict, result.prediction);
241
+ const pred = debiased.debiased;
227
242
  pred.name = cols.getUnusedName(`${input.predict.name} ${RESULT_NAMES.SUFFIX}`);
228
243
  cols.add(pred);
229
244
  const predictVsReferScatter = view.addViewer(DG.Viewer.scatterPlot(sourceTable, {
@@ -250,6 +265,9 @@ async function performMVA(input: PlsInput, analysisType: PLS_ANALYSIS): Promise<
250
265
  help: LINK.COEFFS,
251
266
  showValueSelector: false,
252
267
  showStackSelector: false,
268
+ description: `bias = ${debiased.bias.toFixed(NUMS_AFTER_COMMA)}`,
269
+ descriptionVisibilityMode: 'Always',
270
+ descriptionPosition: 'Bottom',
253
271
  }));
254
272
 
255
273
  // 3. Loadings Scatter Plot
@@ -348,6 +366,10 @@ async function performMVA(input: PlsInput, analysisType: PLS_ANALYSIS): Promise<
348
366
  ['left', 'left', 'right', 'right', 'left'],
349
367
  );
350
368
  }
369
+
370
+ // Add formula tooltip to the prediction column
371
+ const modelFormulaTerms = getModelFormulaTerms(loadingsRegrCoefsTable, debiased.bias);
372
+ setPredictionTooltip(view, pred, modelFormulaTerms);
351
373
  } // performMVA
352
374
 
353
375
  /** Run multivariate analysis (PLS) */
@@ -559,3 +581,79 @@ export async function runDemoMVA(): Promise<void> {
559
581
 
560
582
  await runMVA(PLS_ANALYSIS.DEMO);
561
583
  }
584
+
585
+ function setPredictionTooltip(view: DG.TableView, predCol: DG.Column, modelTerms: Map<string, number>): void {
586
+ view.grid.onCellTooltip((cell, x, y) => {
587
+ if (cell.isColHeader) {
588
+ const cellCol = cell.tableColumn;
589
+
590
+ if (cellCol == null)
591
+ return false;
592
+
593
+ if (cellCol.name === predCol.name) {
594
+ ui.tooltip.show(getPredictionTooltip(modelTerms, predCol), x, y);
595
+ return true;
596
+ }
597
+ }
598
+ return false;
599
+ });
600
+ }
601
+
602
+ function getPredictionTooltip(modelTerms: Map<string, number>, predCol: DG.Column): HTMLElement {
603
+ let idx = 0;
604
+ const bias = modelTerms.get(TITLE.BIAS) ?? 0;
605
+ const elements: HTMLElement[] = [];
606
+ if (Math.abs(bias) > 0) {
607
+ const biasEl = ui.divText(`${bias}`);
608
+ biasEl.style.marginTop = '2px';
609
+ biasEl.style.marginLeft = '4px';
610
+ elements.push(biasEl);
611
+ ++idx;
612
+ }
613
+
614
+ const sortedTerms = [...modelTerms.entries()]
615
+ .filter(([key]) => key !== TITLE.BIAS)
616
+ .sort((a, b) => Math.abs(b[1]) - Math.abs(a[1]));
617
+
618
+ const maxFeatureRows = MAX_ROWS_IN_PREDICTION_TOOLTIP - elements.length;
619
+ const hasOverflow = sortedTerms.length > maxFeatureRows;
620
+ const visibleTerms = hasOverflow ? sortedTerms.slice(0, maxFeatureRows - 1) : sortedTerms;
621
+
622
+ for (const [key, value] of visibleTerms) {
623
+ const signEl = ui.divText(idx > 0 ? '+ ' : '');
624
+ signEl.style.marginRight = '4px';
625
+ signEl.style.marginLeft = '4px';
626
+
627
+ const featureEl = ui.divText(`${key}`);
628
+ featureEl.style.fontWeight = 'bold';
629
+
630
+ const valueEl = ui.divText(` * ${value > 0 ? value : `(${value})`}`);
631
+ valueEl.style.marginLeft = '4px';
632
+
633
+ const rowEl = ui.divH([signEl, featureEl, valueEl]);
634
+ rowEl.style.marginTop = '4px';
635
+ elements.push(rowEl);
636
+
637
+ ++idx;
638
+ }
639
+
640
+ if (hasOverflow) {
641
+ const hidden = sortedTerms.length - visibleTerms.length;
642
+ const ellipsisEl = ui.divText(`(${hidden} more term${hidden > 1 ? 's' : ''})`);
643
+ ellipsisEl.style.marginTop = '4px';
644
+ ellipsisEl.style.marginLeft = '4px';
645
+ ellipsisEl.style.fontStyle = 'italic';
646
+ elements.push(ellipsisEl);
647
+ }
648
+
649
+ const headerEl = ui.divText('Formula:');
650
+
651
+ const leftEl = ui.divText(`${predCol.name} = `);
652
+ leftEl.style.fontWeight = 'bold';
653
+ leftEl.style.marginTop = '4px';
654
+
655
+ const elementsContainer = ui.divV(elements);
656
+ elementsContainer.style.marginTop = '4px';
657
+
658
+ return ui.divV([headerEl, leftEl, elementsContainer]);
659
+ }
@@ -540,13 +540,13 @@ export class Pmpo {
540
540
  const rows = rootsCol.querySelectorAll('div.d4-flex-row.ui-div.statistics-mpo-row');
541
541
 
542
542
  rows.forEach((row, idx) => {
543
- const children = row.children;
544
- if (children.length < 2) // expecting descriptor name, weight & profile
543
+ const editor = row.querySelector('.statistics-mpo-line-editor') as HTMLElement;
544
+ if (!editor)
545
545
  return;
546
546
 
547
- const profileRoot = children[2] as HTMLElement;
548
- profileRoot.style.width = '100%';
549
- this.desirabilityProfileRoots.set(names[idx], profileRoot);
547
+ editor.style.width = '100%';
548
+ editor.style.height = '100%';
549
+ this.desirabilityProfileRoots.set(names[idx], editor);
550
550
  });
551
551
  } // updateDesirabilityProfileData
552
552