diff --git a/src/x/__tests__/xBoxPlot.test.ts b/src/x/__tests__/xBoxPlot.test.ts index 0d8edb52..8b0e18ac 100644 --- a/src/x/__tests__/xBoxPlot.test.ts +++ b/src/x/__tests__/xBoxPlot.test.ts @@ -10,6 +10,7 @@ test('test xBoxPlot even', () => { q3: 8.5, min: 0, max: 11, + outliers: [], }); }); @@ -21,6 +22,7 @@ test('test xBoxPlot even small', () => { q3: 4, min: 0, max: 5, + outliers: [], }); }); @@ -32,6 +34,7 @@ test('test xBoxPlot odd', () => { q3: 8, min: 0, max: 10, + outliers: [], }); }); @@ -43,6 +46,7 @@ test('test xBoxPlot odd small', () => { q3: 3.5, min: 0, max: 4, + outliers: [], }); }); @@ -69,6 +73,7 @@ test('test xBoxPlot with one element', () => { q3: 42, min: 42, max: 42, + outliers: [], }); }); @@ -81,6 +86,7 @@ test('test xBoxPlot with 2 elements', () => { q3: 44, min: 40, max: 44, + outliers: [], }); }); @@ -93,5 +99,30 @@ test('test xBoxPlot with 3 elements', () => { q3: 44, min: 40, max: 44, + outliers: [], + }); +}); + +test('outliers', () => { + const array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 100]; + expect(xBoxPlot(array)).toStrictEqual({ + q1: 2, + median: 5, + q3: 8, + min: 0, + max: 100, + outliers: [], + }); +}); + +test('outliers', () => { + const array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 100]; + expect(xBoxPlot(array, { calculateOutliers: true })).toStrictEqual({ + q1: 2, + median: 5, + q3: 8, + min: 0, + max: 9, + outliers: [100], }); }); diff --git a/src/x/xBoxPlot.ts b/src/x/xBoxPlot.ts index 2d0248f7..6eeef07d 100644 --- a/src/x/xBoxPlot.ts +++ b/src/x/xBoxPlot.ts @@ -6,6 +6,12 @@ export interface XBoxPlotOptions { * @default false */ allowSmallArray?: boolean; + + /** + * Calculate outliers (value < min-1.5IQR or value > max+1.5IQR). The min and max are recalculated without the outliers. + * @default false + */ + calculateOutliers?: boolean; } export interface XBoxPlot { @@ -14,6 +20,7 @@ export interface XBoxPlot { q3: number; min: number; max: number; + outliers: number[]; } /** @@ -25,7 +32,7 @@ export function xBoxPlot( array: NumberArray, options: XBoxPlotOptions = {}, ): XBoxPlot { - const { allowSmallArray = false } = options; + const { allowSmallArray = false, calculateOutliers = false } = options; if (array.length < 5) { if (allowSmallArray) { if (array.length === 0) { @@ -46,6 +53,7 @@ export function xBoxPlot( q3: 0, min: array[0], max: array.at(-1) as number, + outliers: [], }; let q1max, q3min; if (array.length % 2 === 1) { @@ -68,5 +76,22 @@ export function xBoxPlot( const middleOver = (array.length + q3min) / 2; info.q3 = (array[middleOver] + array[middleOver - 1]) / 2; } + + if (calculateOutliers) { + const iqr = info.q3 - info.q1; + const min = info.q1 - 1.5 * iqr; + const max = info.q3 + 1.5 * iqr; + // we need to recalculate the min and the max because they could be outliers + info.min = info.median; + info.max = info.median; + for (const value of array) { + if (value < min || value > max) { + info.outliers.push(value); + } else { + if (value < info.min) info.min = value; + if (value > info.max) info.max = value; + } + } + } return info; }