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())