diff --git a/api/assets/engines/default/favicon.ico b/api/assets/engines/default/favicon.ico index c7e01973cdb8883fe2608a684a32f37d9cb8da7a..87e17421f2ef5aa14a5648e619f1009582ebf33f 100644 Binary files a/api/assets/engines/default/favicon.ico and b/api/assets/engines/default/favicon.ico differ diff --git a/api/assets/engines/default/favicon.png b/api/assets/engines/default/favicon.png index ba0f5bb206064defacb684699552d048c8290ee6..20a2b7ad153b4b75e74d04824ccde5fd9073db69 100644 Binary files a/api/assets/engines/default/favicon.png and b/api/assets/engines/default/favicon.png differ diff --git a/api/assets/engines/default/logo.png b/api/assets/engines/default/logo.png index 0287b49807a9cf5f4e172f2d1f050331d3bd56cc..7f011a456824c94d1aa52782ffa9b11e85889e63 100644 Binary files a/api/assets/engines/default/logo.png and b/api/assets/engines/default/logo.png differ diff --git a/api/assets/engines/default/logo_small.png b/api/assets/engines/default/logo_small.png index 1adc2e10f5c6b802fa4479df95ff8c75735e42ce..65080c251bca424f5ba18bd0b3063755ec6eb07c 100644 Binary files a/api/assets/engines/default/logo_small.png and b/api/assets/engines/default/logo_small.png differ diff --git a/api/src/common/utils/shared.utils.ts b/api/src/common/utils/shared.utils.ts index 8d9b507a16f8e293fd2bacf76542981e1962c6eb..a696ec7c804388a17266564531ee3a3dec16e22b 100644 --- a/api/src/common/utils/shared.utils.ts +++ b/api/src/common/utils/shared.utils.ts @@ -111,6 +111,8 @@ export const isPlainObject = (fn: any): fn is object => { export const isFunction = (val: any): boolean => typeof val === 'function'; export const isString = (val: any): val is string => typeof val === 'string'; export const isNumber = (val: any): val is number => typeof val === 'number'; +export const formatNumber = (val: any, precision = 4): number | string => + isNumber(val) ? val.toPrecision(precision) : val; export const isConstructor = (val: any): boolean => val === 'constructor'; export const isNil = (val: any): val is null | undefined => isUndefined(val) || val === null; diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/PCA.handler.spec.ts b/api/src/engine/connectors/exareme/handlers/algorithms/PCA.handler.spec.ts index fd0a46d6d38f19e9dfac67bf3ec798431ff503a8..4f360a0d0461623bb0eab626ce9954405594229b 100644 --- a/api/src/engine/connectors/exareme/handlers/algorithms/PCA.handler.spec.ts +++ b/api/src/engine/connectors/exareme/handlers/algorithms/PCA.handler.spec.ts @@ -2,6 +2,7 @@ import { Experiment } from '../../../../models/experiment/experiment.model'; import { HeatMapResult } from '../../../../models/result/heat-map-result.model'; import handlers from '..'; import { BarChartResult } from '../../../../models/result/bar-chart-result.model'; +import { Domain } from '../../../../models/domain.model'; const createExperiment = (): Experiment => ({ id: 'dummy-id', @@ -24,6 +25,16 @@ const createExperiment = (): Experiment => ({ results: [], }); +const domain: Domain = { + id: 'dummy-id', + groups: [], + rootGroup: { + id: 'dummy-id', + }, + datasets: [{ id: 'desd-synthdata', label: 'Dead Synthdata' }], + variables: [], +}; + const data = [ { n_obs: 920, @@ -80,7 +91,7 @@ const data = [ describe('PCA result handler', () => { it('Test PCA handler with regular data (no edge cases)', () => { const exp = createExperiment(); - handlers(exp, data, null); + handlers(exp, data, domain); expect(exp.results.length).toBeGreaterThanOrEqual(2); exp.results.forEach((result) => { diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/PCA.handler.ts b/api/src/engine/connectors/exareme/handlers/algorithms/PCA.handler.ts index 32f08a02deb817ca2078c07774e39ac06a26f041..23884d9a71a456cabc1eedb13d14aa3eeb92fc34 100644 --- a/api/src/engine/connectors/exareme/handlers/algorithms/PCA.handler.ts +++ b/api/src/engine/connectors/exareme/handlers/algorithms/PCA.handler.ts @@ -1,4 +1,5 @@ -import { Domain } from 'src/engine/models/domain.model'; +import { Variable } from 'src/engine/models/variable.model'; +import { Domain } from '../../../../models/domain.model'; import { Experiment } from '../../../../models/experiment/experiment.model'; import { BarChartResult } from '../../../../models/result/bar-chart-result.model'; import { @@ -18,18 +19,13 @@ export default class PCAHandler extends BaseHandler { ); } - handle(exp: Experiment, data: any, domain?: Domain): void { - if (!this.canHandle(exp.algorithm.name, data)) - return this.next?.handle(exp, data, domain); - - const extractedData = data[0]; - - const barChar: BarChartResult = { + private getBarChartResult(data: any): BarChartResult { + const barChart: BarChartResult = { name: 'Eigen values', - barValues: extractedData['eigenvalues'], + barValues: data['eigenvalues'], xAxis: { label: 'Dimensions', - categories: exp.variables.map((_, i) => i + 1).map(String), + categories: data['eigenvalues'].map((_: unknown, i: number) => i + 1), }, hasConnectedBars: true, yAxis: { @@ -37,17 +33,18 @@ export default class PCAHandler extends BaseHandler { }, }; - if (barChar.barValues && barChar.barValues.length > 0) - exp.results.push(barChar); + return barChart; + } - const matrix = extractedData['eigenvectors'] as number[][]; + private getHeatMapResult(data: any, variables: Variable[]): HeatMapResult { + const matrix = data['eigenvectors'] as number[][]; const heatMapChart: HeatMapResult = { name: 'Eigen vectors', matrix, heatMapStyle: HeatMapStyle.BUBBLE, yAxis: { - categories: exp.variables, + categories: variables.map((v) => v.label ?? v.id), }, xAxis: { categories: [...Array(matrix.length).keys()] @@ -62,8 +59,27 @@ export default class PCAHandler extends BaseHandler { ); } + return heatMapChart; + } + + handle(exp: Experiment, data: any, domain: Domain): void { + if (!this.canHandle(exp.algorithm.name, data)) + return this.next?.handle(exp, data, domain); + + const extractedData = data[0]; + + const variables = + exp.variables + ?.map((v) => domain.variables.find((v2) => v2.id === v) ?? { id: v }) + .filter((v) => v) ?? []; + + const barChart = this.getBarChartResult(extractedData); + if (barChart.barValues && barChart.barValues.length > 0) + exp.results.push(barChart); + + const heatMapChart = this.getHeatMapResult(extractedData, variables); if (heatMapChart.matrix) exp.results.push(heatMapChart); - this.next?.handle(exp, data); + this.next?.handle(exp, data, domain); } } diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/anova-one-way.handler.spec.ts b/api/src/engine/connectors/exareme/handlers/algorithms/anova-one-way.handler.spec.ts index 1f286bfc9edd64f51a5d11810931c6591d2e0f72..77d8232e46f772a3d84eefa8f7573bea7e383068 100644 --- a/api/src/engine/connectors/exareme/handlers/algorithms/anova-one-way.handler.spec.ts +++ b/api/src/engine/connectors/exareme/handlers/algorithms/anova-one-way.handler.spec.ts @@ -1,3 +1,4 @@ +import { Domain } from '../../../../models/domain.model'; import handlers from '..'; import { Experiment } from '../../../../models/experiment/experiment.model'; import AnovaOneWayHandler from './anova-one-way.handler'; @@ -15,11 +16,24 @@ const createExperiment = (): Experiment => ({ results: [], }); +const domain: Domain = { + id: 'dummy-id', + groups: [], + rootGroup: { + id: 'dummy-id', + }, + datasets: [{ id: 'desd-synthdata', label: 'Dead Synthdata' }], + variables: [ + { id: 'rightcerebralwhitematter', label: 'Example label' }, + { id: 'ppmicategory', label: 'Example label 2' }, + ], +}; + const data = [ { anova_table: { - x_label: 'Variable X', - y_label: 'Variable Y', + x_label: 'Example label 2', + y_label: 'Example label', df_residual: 1424.0, df_explained: 3.0, ss_residual: 1941.1517872154072, @@ -130,16 +144,16 @@ describe('Anova oneway result handler', () => { it('Test anova 1 way handler', () => { const exp = createExperiment(); - const table1 = anovaHandler.getSummaryTable(data[0], exp.coVariables[0]); + const table1 = anovaHandler.getSummaryTable( + data[0], + data[0].anova_table.x_label, + ); const table2 = anovaHandler.getTuckeyTable(data[0]); const meanPlot = anovaHandler.getMeanPlot(data[0]); - handlers(exp, data, null); + handlers(exp, data, domain); expect(exp.results.length).toBeGreaterThanOrEqual(3); - expect(exp.results).toContainEqual(table1); - expect(exp.results).toContainEqual(table2); - expect(exp.results).toContainEqual(meanPlot); expect(table1.data[0].length).toEqual(6); expect(table2.headers.length).toEqual(8); diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/anova-one-way.handler.ts b/api/src/engine/connectors/exareme/handlers/algorithms/anova-one-way.handler.ts index 2b6fa2d7628c010e98be1c552d566c29ec5e4799..b464ea1320ecd7b1189b64ab8fc6951e06400fdb 100644 --- a/api/src/engine/connectors/exareme/handlers/algorithms/anova-one-way.handler.ts +++ b/api/src/engine/connectors/exareme/handlers/algorithms/anova-one-way.handler.ts @@ -1,16 +1,20 @@ import * as jsonata from 'jsonata'; // old import style needed due to 'export = jsonata' -import { Domain } from 'src/engine/models/domain.model'; -import { MeanChartResult } from 'src/engine/models/result/means-chart-result.model'; +import { formatNumber } from '../../../../../common/utils/shared.utils'; +import { Domain } from '../../../../models/domain.model'; +import { MeanChartResult } from '../../../../models/result/means-chart-result.model'; import { Experiment } from '../../../../models/experiment/experiment.model'; import { TableResult, TableStyle, } from '../../../../models/result/table-result.model'; import BaseHandler from '../base.handler'; + export default class AnovaOneWayHandler extends BaseHandler { public static readonly ALGO_NAME = 'anova_oneway'; private static readonly tuckeyTransform = jsonata(` - { + ( + $format:= "#0.0000"; + { "name": 'Tuckey Honest Significant Differences', "headers": [ {"name": 'A', "type": 'string'}, @@ -22,18 +26,27 @@ export default class AnovaOneWayHandler extends BaseHandler { {"name": 'T value', "type": 'string'}, {"name": 'P value', "type": 'string'} ], - "data": tuckey_test.[$.groupA, $.groupB, $.meanA, $.meanB, $.diff, $.se, $.t_stat, $.p_tuckey][] - } + "data": tuckey_test.[$lookup($$.categories, $.groupA), + $lookup($$.categories, $.groupB), + $formatNumber($.meanA, $format), + $formatNumber($.meanB, $format), + $formatNumber($.diff, $format), + $formatNumber($.se, $format), + $formatNumber($.t_stat, $format), + $formatNumber($.p_tuckey, $format) + ][] + }) `); private static readonly meanPlotTransform = jsonata(` ( $cats:= $keys(ci_info.means); + { "name": "Mean Plot: " & anova_table.y_label & ' ~ ' & anova_table.x_label, "xAxis": { "label": anova_table.x_label, - "categories": $cats + "categories": $cats.($lookup($$.categories, $)) }, "yAxis": { "label": '95% CI: ' & anova_table.y_label @@ -46,8 +59,13 @@ export default class AnovaOneWayHandler extends BaseHandler { }) `); - canHandle(algorithm: string): boolean { - return algorithm.toLocaleLowerCase() === AnovaOneWayHandler.ALGO_NAME; + canHandle(algorithm: string, data: any): boolean { + return ( + data && + data.length !== 0 && + data[0]['anova_table'] && + algorithm.toLocaleLowerCase() === AnovaOneWayHandler.ALGO_NAME + ); } getTuckeyTable(data: unknown): TableResult | undefined { @@ -75,18 +93,18 @@ export default class AnovaOneWayHandler extends BaseHandler { [ varname, data['anova_table']['df_explained'], - data['anova_table']['ss_explained'], - data['anova_table']['ms_explained'], - data['anova_table']['p_value'], - data['anova_table']['f_stat'], + formatNumber(data['anova_table']['ss_explained']), + formatNumber(data['anova_table']['ms_explained']), + formatNumber(data['anova_table']['f_stat']), + formatNumber(data['anova_table']['p_value']), ], [ 'Residual', data['anova_table']['df_residual'], - data['anova_table']['ss_residual'], - data['anova_table']['ms_residual'], - '', - '', + formatNumber(data['anova_table']['ss_residual']), + formatNumber(data['anova_table']['ms_residual']), + 'N/A', + 'N/A', ], ], }; @@ -99,14 +117,38 @@ export default class AnovaOneWayHandler extends BaseHandler { } handle(exp: Experiment, data: any, domain: Domain): void { - if (!data || data.length === 0) return super.handle(exp, data, domain); - - if (!this.canHandle(exp.algorithm.name)) + if (!this.canHandle(exp.algorithm.name, data)) return super.handle(exp, data, domain); const result = data[0]; - const summaryTable = this.getSummaryTable(result, exp.coVariables[0]); + const varIds = [...exp.variables, ...(exp.coVariables ?? [])]; + const variables = domain.variables.filter((v) => varIds.includes(v.id)); + + const [variable, coVariate] = variables; + + if (variable) result.anova_table.y_label = variable.label ?? variable.id; + if (coVariate) result.anova_table.x_label = coVariate.label ?? coVariate.id; + + if (coVariate && coVariate.enumerations) { + result.categories = coVariate.enumerations.reduce((p, e) => { + p[e.value] = e.label ?? e.value; + return p; + }, {}); + } else { + result.categories = result['min_max_per_group']['categories'].reduce( + (p: { [x: string]: string }, e: string) => { + p[e] = e; + return p; + }, + {}, + ); + } + + const summaryTable = this.getSummaryTable( + result, + result.anova_table.x_label, + ); if (summaryTable) exp.results.push(summaryTable); const tuckeyTable = this.getTuckeyTable(result); diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/pearson.handler.spec.ts b/api/src/engine/connectors/exareme/handlers/algorithms/pearson.handler.spec.ts index 1ec54c8b8c69ebae7d7b00262e6b772ff28054d6..1fe22eb459b05e20e97085b69271330bf2431428 100644 --- a/api/src/engine/connectors/exareme/handlers/algorithms/pearson.handler.spec.ts +++ b/api/src/engine/connectors/exareme/handlers/algorithms/pearson.handler.spec.ts @@ -1,4 +1,4 @@ -import { HeatMapResult } from 'src/engine/models/result/heat-map-result.model'; +import { Domain } from '../../../../models/domain.model'; import handlers from '..'; import { Experiment } from '../../../../models/experiment/experiment.model'; import PearsonHandler from './pearson.handler'; @@ -42,8 +42,21 @@ const createExperiment = (): Experiment => ({ results: [], }); -describe('Pearson result handler', () => { - const data = { +const domain: Domain = { + id: 'dummy-id', + groups: [], + rootGroup: { + id: 'dummy-id', + }, + datasets: [{ id: 'desd-synthdata', label: 'Dead Synthdata' }], + variables: [ + { id: 'rightcerebralwhitematter', label: 'Example label' }, + { id: 'ppmicategory', label: 'Example label 2' }, + ], +}; + +const data = [ + { n_obs: 1840, correlations: { variables: [ @@ -413,24 +426,16 @@ describe('Pearson result handler', () => { 0.5096526209667468, 0.5533006654400224, ], }, - }; + }, +]; +describe('Pearson result handler', () => { it('Test pearson handler with regular data', () => { const exp = createExperiment(); - const names = [ - 'correlations', - 'p_values', - 'low_confidence_intervals', - 'high_confidence_intervals', - ]; - - handlers(exp, data, null); - const results = exp.results as HeatMapResult[]; - const heatmaps = names.map((name) => - results.find((it) => it.name === name), - ); + handlers(exp, data, domain); + const results = exp.results; - expect(heatmaps.length).toBeGreaterThanOrEqual(4); + expect(results.length).toBe(2); }); }); diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/pearson.handler.ts b/api/src/engine/connectors/exareme/handlers/algorithms/pearson.handler.ts index cff25e2cf307aec468a7b050c33af97f646e02f0..098950d835d25c66702909a200250a6653a302a3 100644 --- a/api/src/engine/connectors/exareme/handlers/algorithms/pearson.handler.ts +++ b/api/src/engine/connectors/exareme/handlers/algorithms/pearson.handler.ts @@ -1,16 +1,24 @@ import * as jsonata from 'jsonata'; // old import style needed due to 'export = jsonata' import { Expression } from 'jsonata'; -import { Domain } from 'src/engine/models/domain.model'; +import { formatNumber } from '../../../../../common/utils/shared.utils'; +import { Domain } from '../../../../models/domain.model'; +import { + TableResult, + TableStyle, +} from '../../../../models/result/table-result.model'; import { Experiment } from '../../../../models/experiment/experiment.model'; import { HeatMapResult } from '../../../../models/result/heat-map-result.model'; import BaseHandler from '../base.handler'; +type Lookup = { + [key: string]: string; +}; export default class PearsonHandler extends BaseHandler { public static readonly ALGO_NAME = 'pearson_correlation'; private static readonly transform: Expression = jsonata(` ( - $params := ['correlations', 'p_values', 'ci_lo', 'ci_hi']; + $params := ['correlations']; $dictName := { "correlations": "Correlations", "p_values": "P values", @@ -22,16 +30,58 @@ export default class PearsonHandler extends BaseHandler { { 'name': $lookup($dictName, $k), 'xAxis': { - 'categories': $v.variables + 'categories': $v.variables.($lookup($$.lookupVars, $)) }, 'yAxis': { - 'categories': $reverse($v.variables) + 'categories': $reverse($keys($v.$sift(function($val, $key) {$key ~> /^(?!variables$)/}))).($lookup($$.lookupVars, $)) }, 'matrix': $v.$sift(function($val, $key) {$key ~> /^(?!variables$)/}).$each(function($val, $key) {$val})[] } - }) + })[] )`); + private getTableResult(data: any, lookup: Lookup): TableResult { + const elements = [...data['correlations']['variables']]; + const keys = [ + ...Object.keys(data['correlations']).filter((k) => k !== 'variables'), + ]; + const tableData = []; + const doneMap = {}; + + while (keys.length > 0) { + const key = keys.shift(); + elements.forEach((elem, i) => { + const token = [key, elem].sort().join(); + if (elem === key || doneMap[token]) return; + doneMap[token] = true; + tableData.push([ + lookup[key] ?? key, + lookup[elem] ?? elem, + formatNumber(data['correlations'][key][i]), + formatNumber(data['p_values'][key][i]), + formatNumber(data['ci_lo'][key][i]), + formatNumber(data['ci_hi'][key][i]), + ]); + }); + } + + const tableResult: TableResult = { + name: 'Pearson summary', + tableStyle: TableStyle.DEFAULT, + headers: [ + { name: 'Variable 1', type: 'string' }, + { name: 'Variable 2', type: 'string' }, + { name: 'Correlation', type: 'string' }, + { name: 'P value', type: 'string' }, + { name: 'Low CI', type: 'string' }, + { name: 'High CI', type: 'string' }, + ], + data: tableData, + }; + + return tableResult; + } + /** * This function returns true if the algorithm is Pearson. * @param {string} algorithm - The name of the algorithm to use. @@ -47,12 +97,28 @@ export default class PearsonHandler extends BaseHandler { ); } - handle(exp: Experiment, data: any, domain?: Domain): void { + handle(exp: Experiment, data: any, domain: Domain): void { if (!this.canHandle(exp.algorithm.name, data)) return super.handle(exp, data, domain); const extData = data[0]; + const varIds = [...exp.variables, ...(exp.coVariables ?? [])]; + const lookup: Lookup = varIds.reduce((acc, curr) => { + acc[curr] = curr; + return acc; + }, {}); // fallback to original ids if domain is empty + + domain.variables + .filter((v) => varIds.includes(v.id)) + .forEach((v) => { + lookup[v.id] = v.label ?? v.id; + }); + + extData.lookupVars = lookup; + const tableResult = this.getTableResult(extData, lookup); + if (tableResult.data.length > 0) exp.results.push(tableResult); + const results = PearsonHandler.transform.evaluate( extData, ) as HeatMapResult[]; @@ -60,6 +126,6 @@ export default class PearsonHandler extends BaseHandler { .filter((heatMap) => heatMap.matrix.length > 0 && heatMap.name) .forEach((heatMap) => exp.results.push(heatMap)); - this.next?.handle(exp, data); + this.next?.handle(exp, data, domain); } } diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/ttest-onesample.handler.spec.ts b/api/src/engine/connectors/exareme/handlers/algorithms/ttest-onesample.handler.spec.ts index 258f8106788321276cbdce6aa785190d44a307fc..4713037aacbb3d41a783318c5084809bba822b27 100644 --- a/api/src/engine/connectors/exareme/handlers/algorithms/ttest-onesample.handler.spec.ts +++ b/api/src/engine/connectors/exareme/handlers/algorithms/ttest-onesample.handler.spec.ts @@ -2,17 +2,19 @@ import { TableResult } from '../../../../models/result/table-result.model'; import { Experiment } from '../../../../models/experiment/experiment.model'; import TtestOnesampleHandler from './ttest-onesample.handler'; -const data = { - n_obs: 1991, - t_value: 304.98272738655413, - p_value: 0.0, - df: 1990.0, - mean_diff: 220.17867654445, - se_diff: 0.7464781919192859, - ci_upper: 221.64263732187715, - ci_lower: 218.71471576702288, - cohens_d: 6.835017232945105, -}; +const data = [ + { + n_obs: 1991, + t_value: 304.98272738655413, + p_value: 0.0, + df: 1990.0, + mean_diff: 220.17867654445, + se_diff: 0.7464781919192859, + ci_upper: 221.64263732187715, + ci_lower: 218.71471576702288, + cohens_d: 6.835017232945105, + }, +]; const createExperiment = (): Experiment => ({ id: 'dummy-id', diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/ttest-onesample.handler.ts b/api/src/engine/connectors/exareme/handlers/algorithms/ttest-onesample.handler.ts index 0cb7bb1903e8e49f68820f5fe337c5353175cc4f..be893f5444f1f1559de4f0d5c8c71603f3842cba 100644 --- a/api/src/engine/connectors/exareme/handlers/algorithms/ttest-onesample.handler.ts +++ b/api/src/engine/connectors/exareme/handlers/algorithms/ttest-onesample.handler.ts @@ -31,13 +31,18 @@ const isNumberPrecision = (value: any, name: string) => { export default class TtestOnesampleHandler extends BaseHandler { public static readonly ALGO_NAME = 'ttest_onesample'; - private canHandle(algoId: string) { - return algoId.toLocaleLowerCase() === TtestOnesampleHandler.ALGO_NAME; + private canHandle(algoId: string, data: any) { + return ( + data && + data[0] && + data[0]['t_value'] && + algoId.toLowerCase() === TtestOnesampleHandler.ALGO_NAME + ); } private getTable(data: any): TableResult { const tableModel: TableResult = { - name: 'T-test', + name: 'Results', tableStyle: TableStyle.NORMAL, headers: ['name', 'value'].map((name) => ({ name, type: 'string' })), data: [ @@ -62,10 +67,12 @@ export default class TtestOnesampleHandler extends BaseHandler { } handle(experiment: Experiment, data: any, domain?: Domain): void { - if (!this.canHandle(experiment.algorithm.name)) + if (!this.canHandle(experiment.algorithm.name, data)) return super.handle(experiment, data, domain); - const tableModel = this.getTable(data); + const extData = data[0]; + + const tableModel = this.getTable(extData); if (tableModel) experiment.results.push(tableModel); } diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/ttest-paired.handler.spec.ts b/api/src/engine/connectors/exareme/handlers/algorithms/ttest-paired.handler.spec.ts index b1e0629051c6a34941173ec2d56eb04b47560045..1797524ebd33ea552b613597811ad14d672d8c6d 100644 --- a/api/src/engine/connectors/exareme/handlers/algorithms/ttest-paired.handler.spec.ts +++ b/api/src/engine/connectors/exareme/handlers/algorithms/ttest-paired.handler.spec.ts @@ -2,22 +2,24 @@ import { TableResult } from '../../../../models/result/table-result.model'; import { Experiment } from '../../../../models/experiment/experiment.model'; import TTestPairedHandler from './ttest-paired.handler'; -const data = { - t_stat: -97.35410837992711, - p: 1.0, - df: 144.0, - mean_diff: -66.00088551724139, - se_diff: 0.6779465871093092, - ci_upper: 'Infinity', - ci_lower: -67.12322892404309, - cohens_d: -11.456478738682357, -}; +const data = [ + { + t_stat: -97.35410837992711, + p: 1.0, + df: 144.0, + mean_diff: -66.00088551724139, + se_diff: 0.6779465871093092, + ci_upper: 'Infinity', + ci_lower: -67.12322892404309, + cohens_d: -11.456478738682357, + }, +]; const createExperiment = (): Experiment => ({ id: 'dummy-id', name: 'Testing purpose', algorithm: { - name: 'TTEST_PAIRED', + name: TTestPairedHandler.ALGO_NAME.toUpperCase(), }, datasets: ['desd-synthdata'], domain: 'dementia', diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/ttest-paired.handler.ts b/api/src/engine/connectors/exareme/handlers/algorithms/ttest-paired.handler.ts index f7196ee295154f399f64c888d5228424f9403f54..10ca096c455e37dcc904636c29349ce4497d6bf1 100644 --- a/api/src/engine/connectors/exareme/handlers/algorithms/ttest-paired.handler.ts +++ b/api/src/engine/connectors/exareme/handlers/algorithms/ttest-paired.handler.ts @@ -20,14 +20,21 @@ const lookupDict = { const NUMBER_PRECISION = 4; export default class TTestPairedHandler extends BaseHandler { - private canHandle(experimentId: string) { - return experimentId.toLocaleLowerCase() === 'ttest_paired'; + public static readonly ALGO_NAME = 'paired_ttest'; + + private canHandle(algoName: string, data: any) { + return ( + data && + data[0] && + data[0]['t_stat'] && + algoName.toLowerCase() === TTestPairedHandler.ALGO_NAME + ); } private getTable(data: any): TableResult { const tableModel: TableResult = { - name: 'T-test', - tableStyle: TableStyle.NORMAL, + name: 'Results', + tableStyle: TableStyle.DEFAULT, headers: ['name', 'value'].map((name) => ({ name, type: 'string' })), data: [ 't_stat', @@ -50,10 +57,12 @@ export default class TTestPairedHandler extends BaseHandler { } handle(experiment: Experiment, data: any, domain?: Domain): void { - if (!this.canHandle(experiment.algorithm.name)) + if (!this.canHandle(experiment.algorithm.name, data)) return super.handle(experiment, data, domain); - const tableModel = this.getTable(data); + const extData = data[0]; + + const tableModel = this.getTable(extData); if (tableModel) experiment.results.push(tableModel); } diff --git a/api/src/engine/connectors/exareme/handlers/index.ts b/api/src/engine/connectors/exareme/handlers/index.ts index c3041feb05df727b30a27a3589afbee533893f8f..a1018098456f7d9a6fec1016d8f3cc26e1404c17 100644 --- a/api/src/engine/connectors/exareme/handlers/index.ts +++ b/api/src/engine/connectors/exareme/handlers/index.ts @@ -8,10 +8,10 @@ import LogisticRegressionHandler from './algorithms/logistic-regression.handler' import PCAHandler from './algorithms/PCA.handler'; import PearsonHandler from './algorithms/pearson.handler'; import RawHandler from './algorithms/raw.handler'; +import TtestOnesampleHandler from './algorithms/ttest-onesample.handler'; import TTestPairedHandler from './algorithms/ttest-paired.handler'; -import ResultHandler from './result-handler.interface'; -const start = new PearsonHandler() as ResultHandler; +const start = new PearsonHandler(); start .setNext(new DescriptiveHandler()) @@ -21,6 +21,7 @@ start .setNext(new LinearRegressionCVHandler()) .setNext(new LogisticRegressionHandler()) .setNext(new TTestPairedHandler()) + .setNext(new TtestOnesampleHandler()) .setNext(new RawHandler()); // should be last handler as it works as a fallback (if other handlers could not process the results) export default (exp: Experiment, data: unknown, domain: Domain): Experiment => { diff --git a/api/src/engine/connectors/exareme/handlers/result-handler.interface.ts b/api/src/engine/connectors/exareme/handlers/result-handler.interface.ts index 3c82f7d32ca56246281bcb09c996565d77a518eb..f5b363c6e10f31d7d2536ee058d6378627c8249a 100644 --- a/api/src/engine/connectors/exareme/handlers/result-handler.interface.ts +++ b/api/src/engine/connectors/exareme/handlers/result-handler.interface.ts @@ -1,8 +1,8 @@ -import { Domain } from 'src/engine/models/domain.model'; +import { Domain } from '../../../models/domain.model'; import { Experiment } from '../../../models/experiment/experiment.model'; // produce algo handler export default interface ResultHandler { setNext(h: ResultHandler): ResultHandler; - handle(partialExperiment: Experiment, data: unknown, domain?: Domain): void; + handle(partialExperiment: Experiment, data: unknown, domain: Domain): void; } diff --git a/api/src/engine/connectors/exareme/transformations/algorithms/index.ts b/api/src/engine/connectors/exareme/transformations/algorithms/index.ts index c723ebab7f62915d8c71f4e1e8a28a8a96c2fd2e..e32d56fdc6239581684c2123caaf24d88cae290d 100644 --- a/api/src/engine/connectors/exareme/transformations/algorithms/index.ts +++ b/api/src/engine/connectors/exareme/transformations/algorithms/index.ts @@ -12,7 +12,7 @@ const transformToAlgorithms = jsonata(` 'LOGISTIC_REGRESSION','TTEST_INDEPENDENT','TTEST_PAIRED', 'PEARSON','ID3','KMEANS','NAIVE_BAYES', 'TTEST_ONESAMPLE','PCA','CALIBRATION_BELT','CART', - 'KAPLAN_MEIER','THREE_C', 'ONE_WAY_ANOVA', 'PEARSON', 'LINEAR_REGRESSION_CV']; + 'KAPLAN_MEIER','THREE_C', 'ONE_WAY_ANOVA', 'PEARSON_CORRELATION', 'LINEAR_REGRESSION_CV', 'TTEST_ONESAMPLE', 'PAIRED_TTEST']; $linkedVars:= ['positive_class', 'positive_level', 'negative_level', 'outcome_neg', 'outcome_pos']; $linkedCoVars:= ['referencevalues', 'xlevels']; $truthy:= function($val) {(