10000 BUG ensure monotonic property of lerp in numpy.percentile by glemaitre · Pull Request #15098 · numpy/numpy · GitHub
[go: up one dir, main page]

Skip to content

BUG ensure monotonic property of lerp in numpy.percentile #15098

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 15 commits into from
Closed
30 changes: 22 additions & 8 deletions numpy/lib/function_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3929,21 +3929,35 @@ def _quantile_ureduce_func(a, q, axis=None, out=None, overwrite_input=False,
indices_above = indices_above[:-1]
n = np.isnan(ap[-1:, ...])

x1 = take(ap, indices_below, axis=axis) * weights_below
x2 = take(ap, indices_above, axis=axis) * weights_above
x1 = take(ap, indices_below, axis=axis)
x2 = take(ap, indices_above, axis=axis)

# the linear interpolation below ensure monotonicity with floating
# point operations and was proposed in:
# https://math.stackexchange.com/a/1798323 by Pedro Gimeno
diff_x2_x1 = x2 - x1
r_above = x1 + diff_x2_x1 * we 10000 ights_above
r_below = x2 - diff_x2_x1 * weights_below

# ensure axis with q-th is first
x1 = np.moveaxis(x1, axis, 0)
x2 = np.moveaxis(x2, axis, 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you kept these lines, and did them before the computation, then you'd be able to do:

r_above = np.add(x1, diff_x2_x1 * weights_above, out=out)
r_below = np.subtract(x2, diff_x2_x1 * weights_below, out=r_above, where=weights_above < 0.5)

r_above = np.moveaxis(r_above, axis, 0)
r_below = np.moveaxis(r_below, axis, 0)

if zerod:
x1 = x1.squeeze(0)
x2 = x2.squeeze(0)
weights_above = weights_above.squeeze(0)
weights_below = weights_below.squeeze(0)
r_above = r_above.squeeze(0)
r_below = r_below.squeeze(0)

r = np.where(weights_above < 0.5, r_above, r_below)

if out is not None:
r = add(x1, x2, out=out)
out[...] = r
r = out
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assignment to out should not be necessary here, the above operation is already in-place (i.e. similar to the old branch, which did not do this).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@seberg is the double-assignment cleanup a blocker?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not on its own, but I think the above also needs some cleanups, maybe I should just put it on my todo list to do it myself.

else:
r = add(x1, x2)
# get the element stored in the 0-d array for backward
# compatibility
r = r[()]

if np.any(n):
if zerod:
Expand Down
7 changes: 7 additions & 0 deletions numpy/lib/tests/test_function_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2931,6 +2931,13 @@ def test_nan_behavior(self):
assert_equal(np.percentile(
a, [0.3, 0.6], (0, 2), interpolation='nearest'), b)

def test_percentile_monotonic_property(self):
# non-regression test for gh-14685
a = np.array(
[0, 1, 1, 2, 2, 3, 3, 4, 5, 5, 1, 1, 9, 9, 9, 8, 8, 7]) * 0.1
p = np.percentile(a, [89, 90, 95, 96, 98, 99])
assert (np.diff(p) >= 0).all()


class TestQuantile(object):
# most of this is already tested by TestPercentile
Expand Down
0