Skip to content

Commit b149202

Browse files
committed
Address PR feedback
1 parent c194c6c commit b149202

3 files changed

Lines changed: 36 additions & 15 deletions

File tree

‎Lib/statistics.py‎

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1206,10 +1206,13 @@ def quantiles(data, *, n=4, method='exclusive'):
12061206
result = []
12071207
for i in range(1, n):
12081208
j, delta = divmod(i * m, n)
1209-
if (data[j] == data[j + 1]) or not delta:
1210-
interpolated = data[j] / 1
1209+
a, b = data[j], data[j + 1]
1210+
if a == b and type(a) is type(b):
1211+
interpolated = a / 1
1212+
elif 2 * delta <= n:
1213+
interpolated = a + (b - a) * delta / n
12111214
else:
1212-
interpolated = (data[j] * (n - delta) + data[j + 1] * delta) / n
1215+
interpolated = b - (b - a) * (n - delta) / n
12131216
result.append(interpolated)
12141217
return result
12151218

@@ -1220,13 +1223,13 @@ def quantiles(data, *, n=4, method='exclusive'):
12201223
j = i * m // n # rescale i to m/n
12211224
j = 1 if j < 1 else ld-1 if j > ld-1 else j # clamp to 1 .. ld-1
12221225
delta = i*m - j*n # exact integer math
1223-
# When the endpoints are equal or delta is zero, avoid
1224-
# the interpolation formula which can be off by 1 ULP
1225-
# due to floating-point rounding
1226-
if (data[j - 1] == data[j]) or not delta:
1227-
interpolated = data[j - 1] / 1
1226+
a, b = data[j - 1], data[j]
1227+
if a == b and type(a) is type(b):
1228+
interpolated = a / 1
1229+
elif 2 * delta <= n:
1230+
interpolated = a + (b - a) * delta / n
12281231
else:
1229-
interpolated = (data[j - 1] * (n - delta) + data[j] * delta) / n
1232+
interpolated = b - (b - a) * (n - delta) / n
12301233
result.append(interpolated)
12311234
return result
12321235

‎Lib/test/test_statistics.py‎

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2657,14 +2657,31 @@ def test_monotonic_with_duplicate_floats(self):
26572657
1e-300, # small magnitude
26582658
float.fromhex('0x1.fffffffffffffp+1023'), # near max float
26592659
sys.float_info.min, # smallest normal
2660+
float('inf'),
2661+
float('-inf'),
26602662
):
26612663
for method in ('exclusive', 'inclusive'):
26622664
for n in range(2, 20):
2663-
result = quantiles([x, x], n=n, method=method)
2664-
self.assertEqual(result, sorted(result),
2665-
msg=f'x={x}, n={n}, method={method!r}')
2666-
self.assertTrue(all(v == x for v in result),
2667-
msg=f'x={x}, n={n}, method={method!r}')
2665+
with self.subTest(x=x, n=n, method=method):
2666+
result = quantiles([x, x], n=n, method=method)
2667+
self.assertEqual(result, sorted(result))
2668+
self.assertTrue(all(v == x for v in result))
2669+
2670+
result = quantiles([0.09999999999999999, 0.1, 0.1],
2671+
n=9, method='inclusive')
2672+
self.assertEqual(result, sorted(result))
2673+
2674+
def test_mixed_types(self):
2675+
data = [Fraction(1, 2), 0.5, 2.0]
2676+
for method, expected in [
2677+
('inclusive', [0.5, 0.5, 0.8, 1.4]),
2678+
('exclusive', [0.5, 0.5, 1.1, 2.3]),
2679+
]:
2680+
with self.subTest(method=method):
2681+
self.assertEqual(
2682+
statistics.quantiles(data, n=5, method=method),
2683+
expected,
2684+
)
26682685

26692686
def test_equal_sized_groups(self):
26702687
quantiles = statistics.quantiles
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
Fix ``quantiles(method='exclusive')`` returning unsorted cut points for duplicate floats.
1+
Fix :func:`statistics.quantiles` returning unsorted cut points for duplicate
2+
floats.

0 commit comments

Comments
 (0)