diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/linear-regression-cv.handler.spec.ts b/api/src/engine/connectors/exareme/handlers/algorithms/linear-regression-cv.handler.spec.ts index a2388797da8531010fbbd958421719dcdb8a87e1..728ecb5da4bc104ddfa5987d180d6118077df58e 100644 --- a/api/src/engine/connectors/exareme/handlers/algorithms/linear-regression-cv.handler.spec.ts +++ b/api/src/engine/connectors/exareme/handlers/algorithms/linear-regression-cv.handler.spec.ts @@ -83,9 +83,6 @@ describe('Linear regression CV result handler', () => { const dataPoints = experiment.results[0] as TableResult; const scoresData = experiment.results[1] as TableResult; - console.log(JSON.stringify(dataPoints)); - console.log(JSON.stringify(scoresData)); - expect(dataPoints.data).toStrictEqual(expectedDataPoints); expect(scoresData.data).toStrictEqual(expectedScoresData); diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/linear-regression.handler.spec.ts b/api/src/engine/connectors/exareme/handlers/algorithms/linear-regression.handler.spec.ts index 04e9c0f20e4f16a0b2d571de4e991ece91101099..b4dee3ed2cc6b8b67d5ad4aea07effe2439e7f08 100644 --- a/api/src/engine/connectors/exareme/handlers/algorithms/linear-regression.handler.spec.ts +++ b/api/src/engine/connectors/exareme/handlers/algorithms/linear-regression.handler.spec.ts @@ -1,4 +1,4 @@ -import { Domain } from 'src/engine/models/domain.model'; +import { Domain } from '../../../../models/domain.model'; import { Experiment } from '../../../../models/experiment/experiment.model'; import LinearRegressionHandler from './linear-regression.handler'; diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/linear-regression.handler.ts b/api/src/engine/connectors/exareme/handlers/algorithms/linear-regression.handler.ts index b58526d32447c3040571da65a6f6f138156efc00..d871b1fe34752454720e984510bf7b6f3dea8e86 100644 --- a/api/src/engine/connectors/exareme/handlers/algorithms/linear-regression.handler.ts +++ b/api/src/engine/connectors/exareme/handlers/algorithms/linear-regression.handler.ts @@ -1,5 +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 { Variable } from '../../../../models/variable.model'; import { isNumber } from '../../../../../common/utils/shared.utils'; import { Experiment } from '../../../../models/experiment/experiment.model'; import { @@ -31,7 +31,7 @@ const lookupDict = { export default class LinearRegressionHandler extends BaseHandler { private getModel(data: any): TableResult | undefined { - const excepts = ['n_obs']; + const exclude = ['n_obs']; const tableModel: TableResult = { name: 'Model', tableStyle: TableStyle.DEFAULT, @@ -48,7 +48,7 @@ export default class LinearRegressionHandler extends BaseHandler { 'f_pvalue', ].map((name) => [ lookupDict[name], - isNumber(data[name]) && !excepts.includes(name) + isNumber(data[name]) && !exclude.includes(name) ? data[name].toPrecision(NUMBER_PRECISION) : data[name], ]), diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/logistic-regression.handler.spec.ts b/api/src/engine/connectors/exareme/handlers/algorithms/logistic-regression.handler.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..617f12859d60c04985bcbea78cfd31dc2737346b --- /dev/null +++ b/api/src/engine/connectors/exareme/handlers/algorithms/logistic-regression.handler.spec.ts @@ -0,0 +1,81 @@ +import { Domain } from '../../../../models/domain.model'; +import { Experiment } from '../../../../models/experiment/experiment.model'; +import LogisticRegressionHandler from './logistic-regression.handler'; + +const data = [ + { + dependent_var: 'ppmicategory', + indep_vars: ['Intercept', 'righthippocampus'], + summary: { + n_obs: 714, + coefficients: [2.6245273240614644, -1.392061918138407], + stderr: [1.3675179284969878, 0.41070496937876727], + lower_ci: [-0.05575856400545254, -2.197028866392417], + upper_ci: [5.304813212128382, -0.5870949698843975], + z_scores: [1.9191904320742843, -3.389445032145682], + pvalues: [0.05496023796478015, 0.0007003424825299484], + df_model: 1, + df_resid: 712, + r_squared_cs: 0.016553598383202917, + r_squared_mcf: 0.02359922514841184, + ll0: -252.5122763647322, + ll: -246.55318230206288, + aic: 497.10636460412576, + bic: 506.24813052880495, + }, + }, +]; + +const domain: Domain = { + id: 'dummy-id', + groups: [], + rootGroup: { + id: 'dummy-id', + }, + datasets: [{ id: 'desd-synthdata', label: 'Dead Synthdata' }], + variables: [ + { id: 'ppmicategory', label: 'PPMI Category' }, + { id: 'righthippocampus', label: 'Right Hippo Campus' }, + ], +}; + +const createExperiment = (): Experiment => ({ + id: 'dummy-id', + name: 'Testing purpose', + algorithm: { + name: 'LOGISTIC_REGRESSION', + }, + datasets: ['desd-synthdata'], + domain: 'dementia', + variables: ['ppmicategory'], + coVariables: ['righthippocampus'], + results: [], +}); + +describe('Logistic Regression result Handler', () => { + let logisticHandler: LogisticRegressionHandler; + let experiment: Experiment; + + beforeEach(() => { + logisticHandler = new LogisticRegressionHandler(); + experiment = createExperiment(); + }); + + describe('handle', () => { + it('with standard logistic algo data', () => { + logisticHandler.handle(experiment, data, domain); + + const json = JSON.stringify(experiment.results); + + expect(json.includes(domain.variables[0].label)).toBeTruthy(); + expect(experiment.results.length).toBe(2); + }); + + it('Should be empty with another algo', () => { + experiment.algorithm.name = 'dummy_algo'; + logisticHandler.handle(experiment, data, domain); + + expect(experiment.results.length).toBe(0); + }); + }); +}); diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/logistic-regression.handler.ts b/api/src/engine/connectors/exareme/handlers/algorithms/logistic-regression.handler.ts new file mode 100644 index 0000000000000000000000000000000000000000..a36e0b921a6cc36d28d82a7558d953e250d6a9a6 --- /dev/null +++ b/api/src/engine/connectors/exareme/handlers/algorithms/logistic-regression.handler.ts @@ -0,0 +1,117 @@ +import { isNumber } from '../../../../../common/utils/shared.utils'; +import { Domain } from '../../../../models/domain.model'; +import { Experiment } from '../../../../models/experiment/experiment.model'; +import { + TableResult, + TableStyle, +} from '../../../../models/result/table-result.model'; +import BaseHandler from '../base.handler'; + +const lookupDict = { + dependent_var: 'Dependent variable', + indep_vars: 'Independent variables', + n_obs: 'Number of observations', + df_resid: 'Residual degrees of freedom', + df_model: 'Model degrees of freedom', + coefficients: 'Coefficients', + stderr: 'Std.Err.', + z_scores: 'z-scores', + pvalues: 'P{>|z|}', + lower_ci: 'Lower 95% c.i.', + upper_ci: 'Upper 59% c.i.', + r_squared_mcf: 'McFadden R^2', + r_squared_cs: 'Cox-Snell R^2', + ll0: 'log(L) of null-model', + ll: 'log(L)', + aic: 'AIC', + bic: 'BIC', +}; +const EXCLUDE_NUMBER_LIST = ['n_obs', 'df_resid', 'df_model']; +const NUMBER_PRECISION = 4; +const roundNumber = (val: any, name: string) => + isNumber(val) && !EXCLUDE_NUMBER_LIST.includes(name) + ? val.toPrecision(NUMBER_PRECISION) + : val; + +export default class LogisticRegressionHandler extends BaseHandler { + private getModel(data: any): TableResult | undefined { + return { + name: 'Model', + tableStyle: TableStyle.DEFAULT, + headers: ['Name', 'Value'].map((name) => ({ name, type: 'string' })), + data: [ + 'dependent_var', + 'n_obs', + 'df_resid', + 'df_model', + 'df_model', + 'r_squared_mcf', + 'r_squared_cs', + 'll0', + 'll', + 'aic', + 'bic', + ].map((name) => [lookupDict[name], roundNumber(data[name], name)]), + }; + } + + private getCoefficients(data: any): TableResult | undefined { + const fields = [ + 'indep_vars', + 'coefficients', + 'stderr', + 'z_scores', + 'pvalues', + 'lower_ci', + 'upper_ci', + ]; + + return { + name: 'Coefficients', + tableStyle: TableStyle.DEFAULT, + headers: fields.map((name) => ({ + name: lookupDict[name], + type: 'string', + })), + data: [fields.map((name) => roundNumber(data[name], name))], + }; + } + + canHandle(exp: Experiment, data: any): boolean { + return ( + exp.algorithm.name.toLowerCase() === 'logistic_regression' && + !!data && + !!data[0] && + !!data[0]['summary'] + ); + } + + handle(experiment: Experiment, data: any, domain?: Domain): void { + if (!this.canHandle(experiment, data)) + return super.handle(experiment, data, domain); + + const extractedData = { + ...data[0], + ...data[0]['summary'], + summary: undefined, + }; + + const varIds = [...experiment.variables, ...(experiment.coVariables ?? [])]; + const variables = domain.variables.filter((v) => varIds.includes(v.id)); + + let jsonData = JSON.stringify(extractedData); + + variables.forEach((v) => { + const regEx = new RegExp(v.id, 'gi'); + jsonData = jsonData.replaceAll(regEx, v.label); + }); + + const improvedData = JSON.parse(jsonData); + + const model = this.getModel(improvedData); + if (model) experiment.results.push(model); + + const coefs = this.getCoefficients(improvedData); + if (coefs) experiment.results.push(coefs); + } +}