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..fa2de1c72f9136fa8a6cf163e296206c65afa42d 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,12 @@ 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 && + algorithm.toLocaleLowerCase() === AnovaOneWayHandler.ALGO_NAME + ); } getTuckeyTable(data: unknown): TableResult | undefined { @@ -75,18 +92,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 +116,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);