Skip to content

Commit 4161e77

Browse files
committed
fix: xBoxPlot deals correctly with very close numbers
1 parent 51dfbf5 commit 4161e77

File tree

6 files changed

+92
-0
lines changed

6 files changed

+92
-0
lines changed

src/x/__tests__/xBoxPlot.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,3 +144,16 @@ test('outliers', () => {
144144
max: 100,
145145
});
146146
});
147+
148+
test('close values', () => {
149+
const array = [1.4029999999999998, 1.403];
150+
const result = xBoxPlot(array);
151+
152+
expect(result).toStrictEqual({
153+
min: 1.4029999999999998,
154+
q1: 1.403,
155+
median: 1.403,
156+
q3: 1.403,
157+
max: 1.403,
158+
});
159+
});

src/x/__tests__/xBoxPlotWithOutliers.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,22 @@ test('outliers', () => {
3939
outliers: [100],
4040
});
4141
});
42+
43+
test('close values', () => {
44+
const array = [1.4029999999999998, 1.403];
45+
const result = xBoxPlotWithOutliers(array);
46+
47+
expect(result).toStrictEqual({
48+
min: 1.4029999999999998,
49+
q1: 1.403,
50+
median: 1.403,
51+
q3: 1.403,
52+
max: 1.403,
53+
lowerWhisker: 1.4029999999999998,
54+
upperWhisker: 1.403,
55+
minWhisker: 1.4029999999999998,
56+
maxWhisker: 1.403,
57+
iqr: 0,
58+
outliers: [],
59+
});
60+
});

src/x/__tests__/xRobustDistributionStats.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,28 @@ test('one element', () => {
3232
});
3333
});
3434

35+
test('two close elements', () => {
36+
const data = [1.4029999999999998, 1.403];
37+
const stats = xRobustDistributionStats(data);
38+
39+
expect(stats).toStrictEqual({
40+
min: 1.4029999999999998,
41+
q1: 1.403,
42+
median: 1.403,
43+
q3: 1.403,
44+
max: 1.403,
45+
lowerWhisker: 1.4029999999999998,
46+
upperWhisker: 1.403,
47+
minWhisker: 1.4029999999999998,
48+
maxWhisker: 1.403,
49+
iqr: 0,
50+
outliers: [],
51+
mean: 1.403,
52+
nb: 2,
53+
sd: 0,
54+
});
55+
});
56+
3557
test('4 elements', () => {
3658
const data = [15, 13, 17, 7];
3759
const stats = xRobustDistributionStats(data);

src/x/xBoxPlot.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,28 @@ export function xBoxPlot(array: NumberArray): XBoxPlot {
3232
// and sort typed array that is much faster than sorting a normal array
3333
array = Float64Array.from(array).sort();
3434

35+
// need to deal with very close points otherwise it yields to incorrect results
36+
if ((array.at(-1) as number) - array[0] <= Number.EPSILON) {
37+
// if one of the 2 numbers is an integer let's take this one
38+
const shortTestNumber =
39+
String(array[0]).length < String(array.at(-1)).length
40+
? array[0]
41+
: (array.at(-1) as number);
42+
return {
43+
min: array[0],
44+
q1: shortTestNumber,
45+
median: shortTestNumber,
46+
q3: shortTestNumber,
47+
max: array.at(-1) as number,
48+
};
49+
}
3550
const posQ1 = (array.length - 1) / 4;
3651
const posQ3 = (array.length - 1) * (3 / 4);
3752
const medianPos = (array.length - 1) / 2;
3853

3954
const q1MinProportion = posQ1 % 1;
4055
const q3MinProportion = posQ3 % 1;
56+
4157
const medianMinProportion = medianPos % 1;
4258
return {
4359
min: array[0],

src/x/xBoxPlotWithOutliers.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,18 @@ export interface XBoxPlotWithOutliers {
5858
export function xBoxPlotWithOutliers(array: NumberArray): XBoxPlotWithOutliers {
5959
const boxPlot = xBoxPlot(array);
6060

61+
if (boxPlot.max - boxPlot.min <= Number.EPSILON) {
62+
return {
63+
...boxPlot,
64+
lowerWhisker: boxPlot.min,
65+
upperWhisker: boxPlot.max,
66+
minWhisker: boxPlot.min,
67+
maxWhisker: boxPlot.max,
68+
iqr: 0,
69+
outliers: [],
70+
};
71+
}
72+
6173
const iqr = boxPlot.q3 - boxPlot.q1;
6274
const lowerWhisker = boxPlot.q1 - 1.5 * iqr;
6375
const upperWhisker = boxPlot.q3 + 1.5 * iqr;

src/x/xRobustDistributionStats.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,16 @@ export function xRobustDistributionStats(
2626
): XRobustDistributionStats {
2727
const boxPlot = xBoxPlotWithOutliers(array);
2828
let filteredArray: NumberArray;
29+
30+
if (boxPlot.max - boxPlot.min <= Number.EPSILON) {
31+
return {
32+
...boxPlot,
33+
mean: boxPlot.median,
34+
sd: array.length === 1 ? Number.NaN : 0,
35+
nb: array.length,
36+
};
37+
}
38+
2939
if (boxPlot.outliers.length === 0) {
3040
filteredArray = array;
3141
} else {

0 commit comments

Comments
 (0)