diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/anova-two-way.handler.spec.ts b/api/src/engine/connectors/exareme/handlers/algorithms/anova-two-way.handler.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..3a76e8edf04ff81760059d4bd1b666d961f9fb7c --- /dev/null +++ b/api/src/engine/connectors/exareme/handlers/algorithms/anova-two-way.handler.spec.ts @@ -0,0 +1,72 @@ +import { Domain } from '../../../../models/domain.model'; +import handlers from '..'; +import { Experiment } from '../../../../models/experiment/experiment.model'; +import AnovaTwoWayHandler from './anova-two-way.handler'; + +const createExperiment = (): Experiment => ({ + id: 'dummy-id', + name: 'Testing purpose', + algorithm: { + name: AnovaTwoWayHandler.ALGO_NAME, + }, + datasets: ['desd-synthdata'], + domain: 'dementia', + variables: ['parkinsonbroadcategory'], + coVariables: ['gender'], + results: [], +}); + +const domain: Domain = { + id: 'dummy-id', + groups: [], + rootGroup: { + id: 'dummy-id', + }, + datasets: [{ id: 'desd-synthdata', label: 'Dead Synthdata' }], + variables: [ + { id: 'parkinsonbroadcategory', label: 'Example label' }, + { id: 'gender', label: 'Example label 2' }, + { id: 'parkinsonbroadcategory:gender', label: 'Example label 3' }, + ], +}; + +const data = [ + { + terms: [ + 'parkinsonbroadcategory', + 'gender', + 'parkinsonbroadcategory:gender', + 'Residuals', + ], + sum_sq: [ + 0.7427619881331298, 0.004764818481136857, 0.008175662839327025, + 5.6058179597692295, + ], + df: [2, 1, 2, 74], + f_stat: [4.902441313320338, 0.06289832636995628, 0.05396171035628005, null], + f_pvalue: [ + 0.010014025893247736, + 0.8026673646098741, + 0.9475056310046991, + null, + ], + }, +]; + +describe('Anova 2 way result handler', () => { + const anovaHandler = new AnovaTwoWayHandler(); + + it('Test anova 2 way handler', () => { + const exp = createExperiment(); + const summaryTable = anovaHandler.getSummaryTable( + data[0], + domain.variables, + ); + + handlers(exp, data, domain); + + expect(exp.results.length).toBeGreaterThanOrEqual(1); + + expect(summaryTable.data[0].length).toEqual(4); + }); +}); diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/anova-two-way.handler.ts b/api/src/engine/connectors/exareme/handlers/algorithms/anova-two-way.handler.ts new file mode 100644 index 0000000000000000000000000000000000000000..cf12cbdb6e82360d1c82dbf3910de5f41fdc0b3a --- /dev/null +++ b/api/src/engine/connectors/exareme/handlers/algorithms/anova-two-way.handler.ts @@ -0,0 +1,57 @@ +import { Domain } from '../../../../models/domain.model'; +import { Experiment } from '../../../../models/experiment/experiment.model'; +import { + TableResult, + TableStyle, +} from '../../../../models/result/table-result.model'; +import { Variable } from '../../../../models/variable.model'; +import BaseHandler from '../base.handler'; + +const NUMBER_PRECISION = 4; + +export default class AnovaTwoWayHandler extends BaseHandler { + public static readonly ALGO_NAME = 'anova_twoway'; + + private canHandle(algorithm: string, data: any): boolean { + return ( + algorithm === AnovaTwoWayHandler.ALGO_NAME && + !!data && + !!data[0] && + !!data[0]['terms'] + ); + } + + getSummaryTable(data: any, variables: Variable[]): TableResult | undefined { + return { + name: 'Anova two way Summary', + tableStyle: TableStyle.DEFAULT, + headers: ['', 'DF', 'Sum Sq', 'F value', 'Pr(>F)'].map((name) => ({ + name, + type: 'string', + })), + data: [ + data['terms'].map((term: string, index: number) => [ + variables.find((variable) => variable.id == term)?.label ?? term, + data['df'][index]?.toPrecision(NUMBER_PRECISION) ?? '', + data['sum_sq'][index]?.toPrecision(NUMBER_PRECISION) ?? '', + data['f_stat'][index]?.toPrecision(NUMBER_PRECISION) ?? '', + data['f_pvalue'][index]?.toPrecision(NUMBER_PRECISION) ?? '', + ]), + ], + }; + } + + handle(exp: Experiment, data: any, domain: Domain): void { + if (!this.canHandle(exp.algorithm.name, data)) + return super.handle(exp, data, domain); + + const result = data[0]; + + const varIds = [...exp.variables, ...(exp.coVariables ?? [])]; + const variables = domain.variables.filter((v) => varIds.includes(v.id)); + + const summaryTable = this.getSummaryTable(result, variables); + + if (summaryTable) exp.results.push(summaryTable); + } +} diff --git a/api/src/engine/connectors/exareme/handlers/index.ts b/api/src/engine/connectors/exareme/handlers/index.ts index fd465c382338cc4cdba6315c5341a3a66980e7bd..6863edfd62e4d8a5ee97941e83f15424eb65d178 100644 --- a/api/src/engine/connectors/exareme/handlers/index.ts +++ b/api/src/engine/connectors/exareme/handlers/index.ts @@ -1,6 +1,7 @@ import { Domain } from '../../../../engine/models/domain.model'; import { Experiment } from '../../../../engine/models/experiment/experiment.model'; import AnovaOneWayHandler from './algorithms/anova-one-way.handler'; +import AnovaTwoWayHandler from './algorithms/anova-two-way.handler'; import DescriptiveHandler from './algorithms/descriptive.handler'; import HistogramHandler from './algorithms/histogram.handler'; import LinearRegressionCVHandler from './algorithms/linear-regression-cv.handler'; @@ -20,6 +21,7 @@ start .setNext(new HistogramHandler()) .setNext(new DescriptiveHandler()) .setNext(new AnovaOneWayHandler()) + .setNext(new AnovaTwoWayHandler()) .setNext(new PCAHandler()) .setNext(new LinearRegressionHandler()) .setNext(new LinearRegressionCVHandler())