diff --git a/end-to-end-test/local/screenshots/reference/shows_table_when_selecting_table_visualisation_element_chrome_1600x1000.png b/end-to-end-test/local/screenshots/reference/shows_table_when_selecting_table_visualisation_element_chrome_1600x1000.png index ee6deea4f3b..7e458790358 100644 Binary files a/end-to-end-test/local/screenshots/reference/shows_table_when_selecting_table_visualisation_element_chrome_1600x1000.png and b/end-to-end-test/local/screenshots/reference/shows_table_when_selecting_table_visualisation_element_chrome_1600x1000.png differ diff --git a/src/pages/groupComparison/ClinicalNumericalDataVisualisation.tsx b/src/pages/groupComparison/ClinicalNumericalDataVisualisation.tsx index 6098a1c75cd..ec3ca27005f 100644 --- a/src/pages/groupComparison/ClinicalNumericalDataVisualisation.tsx +++ b/src/pages/groupComparison/ClinicalNumericalDataVisualisation.tsx @@ -1,11 +1,14 @@ import BoxScatterPlot, { + IBaseBoxScatterPlotPoint, + IBoxScatterPlotData, IBoxScatterPlotProps, toBoxPlotData, + toDataDescriptive, } from 'shared/components/plots/BoxScatterPlot'; import { IBoxScatterPlotPoint } from 'shared/components/plots/PlotsTabUtils'; import React from 'react'; import { computed, makeObservable } from 'mobx'; -import { SummaryStatisticsTable } from './SummaryStatisticsTable'; +import { DescriptiveDataTable } from './SummaryStatisticsTable'; export enum ClinicalNumericalVisualisationType { Plot = 'Plot', @@ -43,9 +46,17 @@ export class ClinicalNumericalDataVisualisation extends React.Component< this.props.excludeLimitValuesFromBoxPlot, this.props.logScale ); + const dataDescription = toDataDescriptive( + this.props.data, + this.props.logScale + ); const groupLabels = this.props.data.map(d => d.label); return ( - + ); } diff --git a/src/pages/groupComparison/GroupComparisonUtils.tsx b/src/pages/groupComparison/GroupComparisonUtils.tsx index d881c49a46f..25c9334282c 100644 --- a/src/pages/groupComparison/GroupComparisonUtils.tsx +++ b/src/pages/groupComparison/GroupComparisonUtils.tsx @@ -47,6 +47,10 @@ import { import { GroupComparisonMutation } from 'shared/model/GroupComparisonMutation'; import { getTwoTailedPValue } from 'shared/lib/calculation/FisherExactTestCalculator'; import { calculateQValues } from 'shared/lib/calculation/BenjaminiHochbergFDRCalculator'; +import { + IBaseBoxScatterPlotPoint, + IBoxScatterPlotData, +} from 'shared/components/plots/BoxScatterPlot'; type Omit = Pick>; diff --git a/src/pages/groupComparison/SummaryStatisticsTable.tsx b/src/pages/groupComparison/SummaryStatisticsTable.tsx index e34d3c248a2..dce8268ee77 100644 --- a/src/pages/groupComparison/SummaryStatisticsTable.tsx +++ b/src/pages/groupComparison/SummaryStatisticsTable.tsx @@ -1,8 +1,31 @@ import { FunctionComponent } from 'react'; import _ from 'lodash'; import * as React from 'react'; +import { + BoxModel, + IBoxScatterPlotData, +} from 'shared/components/plots/BoxScatterPlot'; -type SummaryStatisticsTableProps = { data: any[]; labels: string[] }; +type SummaryStatisticsTableProps = { + data: BoxModel[]; + labels: string[]; +}; + +type DescriptiveDataTableProps = { + descriptiveData: DataDescriptiveValues[]; + dataBoxplot: BoxModel[]; + labels: string[]; +}; + +type DataDescriptiveValues = { + mad: number; + stdDeviation: number; + median: number; + mean: number; + count: number; + maximum: number; + minimum: number; +}; export const SummaryStatisticsTable: FunctionComponent = ( props: SummaryStatisticsTableProps @@ -53,3 +76,87 @@ export const SummaryStatisticsTable: FunctionComponent ); }; + +export const DescriptiveDataTable: FunctionComponent = ( + props: DescriptiveDataTableProps +) => { + const headers = + props.labels.length === 1 ? ( + {props.labels[0]} + ) : ( + [, props.labels.map(label => {label})] + ); + return ( +
+

Data description

+ + + {headers} + + + + + {props.descriptiveData.map((d, index) => ( + + ))} + + + + {props.descriptiveData.map((d, index) => { + return ; + })} + + + + {props.descriptiveData.map((d, index) => { + return ; + })} + + + + {props.descriptiveData.map((d, index) => { + return ; + })} + + + + {props.descriptiveData.map((d, index) => { + return ( + + ); + })} + + + + {props.descriptiveData.map((d, index) => { + return ; + })} + + + + {props.descriptiveData.map((d, index) => { + return ; + })} + + + + {props.dataBoxplot.map((d, index) => ( + + ))} + + + + {props.dataBoxplot.map((d, index) => ( + + ))} + + +
Count{d.count}
Minimum{_.round(d.minimum, 2)}
Maximum{_.round(d.maximum, 2)}
Mean{_.round(d.mean, 2)}
Standard Deviation + {_.round(d.stdDeviation, 2)} +
Median{_.round(d.median, 2)}
Mean absolute deviation{_.round(d.mad, 2)}
25% (q1){_.round(d.q1, 2)}
75% (q3){_.round(d.q3, 2)}
+
+ ); +}; diff --git a/src/shared/components/plots/BoxScatterPlot.tsx b/src/shared/components/plots/BoxScatterPlot.tsx index 0681bf22e1a..a417df682fb 100644 --- a/src/shared/components/plots/BoxScatterPlot.tsx +++ b/src/shared/components/plots/BoxScatterPlot.tsx @@ -93,7 +93,7 @@ export interface IBoxScatterPlotProps { qValue?: number | null; } -type BoxModel = { +export type BoxModel = { min: number; max: number; median: number; @@ -957,7 +957,7 @@ export function toBoxPlotData( data: IBoxScatterPlotData[], boxCalculationFilter?: (d: D) => boolean, excludeLimitValuesFromBoxPlot?: any, - logScale?: any, + logScale?: IAxisLogScaleParams, calcBoxSizes?: (box: BoxModel, i: number) => void ) { // when limit values are shown in the legend, exclude @@ -1014,3 +1014,67 @@ export function toBoxPlotData( ); }); } + +export function toDataDescriptive( + data: IBoxScatterPlotData[], + logScale?: IAxisLogScaleParams +) { + return data.map(d => { + const scatterValues = d.data.map(x => + logScale ? logScale.fLogScale(x.value, 0) : x.value + ); + const count = scatterValues.length; + // Calculate minimum and maximum + const minimum = Math.min(...scatterValues); + const maximum = Math.max(...scatterValues); + const mean = _.mean(scatterValues); + + // Calculate standard deviation + const squaredDifferences = scatterValues.map((val: number) => { + const difference = val - mean; + return difference * difference; + }); + const variance = + squaredDifferences.reduce( + (sum: number, val: number) => sum + val, + 0 + ) / scatterValues.length; + const stdDeviation = Math.sqrt(variance); + + // Calculate median + const scatterValuesSorted = scatterValues + .slice() + .sort((a: number, b: number) => a - b); + const mid = Math.floor(scatterValuesSorted.length / 2); + const median = + scatterValuesSorted.length % 2 !== 0 + ? scatterValuesSorted[mid] + : (scatterValuesSorted[mid - 1] + scatterValuesSorted[mid]) / 2; + + // Calculate median absolute deviation (MAD) + const absoluteDeviations = scatterValues.map(val => + Math.abs(val - median) + ); + const absoluteDeviationsSorted = absoluteDeviations + .slice() + .sort((a, b) => a - b); + const madMid = Math.floor(absoluteDeviationsSorted.length / 2); + const mad = + absoluteDeviationsSorted.length % 2 !== 0 + ? absoluteDeviationsSorted[madMid] + : (absoluteDeviationsSorted[madMid - 1] + + absoluteDeviationsSorted[madMid]) / + 2; + + // Return an object with the descriptive statistics + return { + count, + minimum, + maximum, + mean, + stdDeviation, + median, + mad, + }; + }); +}