From b2b831628776c46a8b5db5388da14f7e369e6b5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Fri, 3 Jul 2026 09:51:01 +0200 Subject: [PATCH 1/3] fix: improve perf of distribution stats Avoid computing the mean twice. --- src/x/xDistributionStats.ts | 5 +++-- src/x/xRobustDistributionStats.ts | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/x/xDistributionStats.ts b/src/x/xDistributionStats.ts index 73c1bdf5c..b789ce667 100644 --- a/src/x/xDistributionStats.ts +++ b/src/x/xDistributionStats.ts @@ -20,10 +20,11 @@ export interface XDistributionStats extends XBoxPlotWithOutliers { * @returns q1, median, q3, min, max. */ export function xDistributionStats(array: NumberArray): XDistributionStats { + const mean = xMean(array); return { ...xBoxPlotWithOutliers(array), - mean: xMean(array), - sd: xStandardDeviation(array), + mean, + sd: xStandardDeviation(array, { mean }), nb: array.length, }; } diff --git a/src/x/xRobustDistributionStats.ts b/src/x/xRobustDistributionStats.ts index 6430f6b11..d67e268c4 100644 --- a/src/x/xRobustDistributionStats.ts +++ b/src/x/xRobustDistributionStats.ts @@ -47,10 +47,11 @@ export function xRobustDistributionStats( } } + const mean = xMean(filteredArray); return { ...boxPlot, - mean: xMean(filteredArray), - sd: xStandardDeviation(filteredArray), + mean, + sd: xStandardDeviation(filteredArray, { mean }), nb: filteredArray.length, }; } From abc56474d5c71a2807831ce85ec402b662f8a406 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Fri, 3 Jul 2026 09:51:36 +0200 Subject: [PATCH 2/3] fix: correct robust distribution stats The array used to compute the robust stats was not constructed correctly. --- .../xRobustDistributionStats.test.ts | 25 +++++++++++++++++++ src/x/xRobustDistributionStats.ts | 3 ++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/x/__tests__/xRobustDistributionStats.test.ts b/src/x/__tests__/xRobustDistributionStats.test.ts index 111fc0b6a..7e91a1e25 100644 --- a/src/x/__tests__/xRobustDistributionStats.test.ts +++ b/src/x/__tests__/xRobustDistributionStats.test.ts @@ -128,3 +128,28 @@ test('typed array', () => { xRobustDistributionStats(data), ); }); + +test('more complex case with multiple outliers', () => { + const data = [ + 1050, 1052, 1052, 1052, 1053, 1055, 1055, 1055, 1055, 1056, 1056, 1056, + 1056, 1056, 1056, 1056, 1056, 1056, 1056, 1057, 1057, 1057, 1057, 1057, + 1057, 1057, 1057, 1057, 1057, 1058, 1058, 1058, 1059, 1065, 1072, 1074, + ]; + + expect(xRobustDistributionStats(data)).toStrictEqual({ + min: 1050, + q1: 1055.75, + median: 1056, + q3: 1057, + max: 1074, + lowerWhisker: 1053.875, + upperWhisker: 1058.875, + minWhisker: 1055, + maxWhisker: 1058, + iqr: 1.25, + outliers: [1050, 1052, 1052, 1052, 1053, 1059, 1065, 1072, 1074], + mean: 1056.4444444444443, + sd: 0.8915558282417289, + nb: 27, + }); +}); diff --git a/src/x/xRobustDistributionStats.ts b/src/x/xRobustDistributionStats.ts index d67e268c4..d02fa5d62 100644 --- a/src/x/xRobustDistributionStats.ts +++ b/src/x/xRobustDistributionStats.ts @@ -39,9 +39,10 @@ export function xRobustDistributionStats( filteredArray = array; } else { filteredArray = new Float64Array(array.length - boxPlot.outliers.length); + const outliersSet = new Set(boxPlot.outliers); let j = 0; for (const element of array) { - if (element >= boxPlot.min && element <= boxPlot.max) { + if (!outliersSet.has(element)) { filteredArray[j++] = element; } } From db903b855821425f7e9788af22489bae2a3d9731 Mon Sep 17 00:00:00 2001 From: Luc Patiny Date: Fri, 3 Jul 2026 11:06:34 +0200 Subject: [PATCH 3/3] chore: seems the fix was just to use lowerWhisker and upperWhisker and not to create a Set --- src/x/xRobustDistributionStats.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/x/xRobustDistributionStats.ts b/src/x/xRobustDistributionStats.ts index d02fa5d62..d474c0468 100644 --- a/src/x/xRobustDistributionStats.ts +++ b/src/x/xRobustDistributionStats.ts @@ -39,10 +39,9 @@ export function xRobustDistributionStats( filteredArray = array; } else { filteredArray = new Float64Array(array.length - boxPlot.outliers.length); - const outliersSet = new Set(boxPlot.outliers); let j = 0; for (const element of array) { - if (!outliersSet.has(element)) { + if (element >= boxPlot.lowerWhisker && element <= boxPlot.upperWhisker) { filteredArray[j++] = element; } }