diff options
author | Eric Wieser <wieser.eric@gmail.com> | 2020-03-25 04:59:27 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-03-25 00:59:27 -0400 |
commit | b03c575834a68f3a8dad5e2974ccee7fa76003ac (patch) | |
tree | 548c378c3fa17186f59e0c149b93300da92746ab | |
parent | fed47a489e3da26fffb98a6dda32dbc9ba0f63c3 (diff) | |
download | numpy-b03c575834a68f3a8dad5e2974ccee7fa76003ac.tar.gz |
DEP: Do not cast boolean indices to integers in np.delete (#15815)
This expires a deprecation from 1.8.
The corresponding deprecation in `np.insert` has less clear semantics, so has been left to a future patch.
Co-authored-by: Sebastian Berg <sebastian@sipsolutions.net>
Co-authored-by: Warren Weckesser <warren.weckesser@gmail.com>
-rw-r--r-- | doc/release/upcoming_changes/15815.expired.rst | 6 | ||||
-rw-r--r-- | numpy/lib/function_base.py | 42 | ||||
-rw-r--r-- | numpy/lib/tests/test_function_base.py | 23 |
3 files changed, 41 insertions, 30 deletions
diff --git a/doc/release/upcoming_changes/15815.expired.rst b/doc/release/upcoming_changes/15815.expired.rst new file mode 100644 index 000000000..b853cc9e5 --- /dev/null +++ b/doc/release/upcoming_changes/15815.expired.rst @@ -0,0 +1,6 @@ +`numpy.delete` no longer casts boolean indices to integers +---------------------------------------------------------- +This concludes a deprecation from 1.8, where ``np.delete`` would cast boolean +arrays and scalars passed as an index argument into integer indices. The +behavior now is to treat boolean arrays as a mask, and to raise an error +on boolean scalars. diff --git a/numpy/lib/function_base.py b/numpy/lib/function_base.py index 7211b68cf..bcf7898ef 100644 --- a/numpy/lib/function_base.py +++ b/numpy/lib/function_base.py @@ -4207,12 +4207,17 @@ def delete(arr, obj, axis=None): Parameters ---------- arr : array_like - Input array. + Input array. obj : slice, int or array of ints - Indicate indices of sub-arrays to remove along the specified axis. + Indicate indices of sub-arrays to remove along the specified axis. + + .. versionchanged:: 1.19.0 + Boolean indices are now treated as a mask of elements to remove, + rather than being cast to the integers 0 and 1. + axis : int, optional - The axis along which to delete the subarray defined by `obj`. - If `axis` is None, `obj` is applied to the flattened array. + The axis along which to delete the subarray defined by `obj`. + If `axis` is None, `obj` is applied to the flattened array. Returns ------- @@ -4330,19 +4335,8 @@ def delete(arr, obj, axis=None): else: return new - _obj = obj - obj = np.asarray(obj) - # After removing the special handling of booleans and out of - # bounds values, the conversion to the array can be removed. - if obj.dtype == bool: - # 2012-10-11, NumPy 1.8 - warnings.warn("in the future insert will treat boolean arrays and " - "array-likes as boolean index instead of casting it " - "to integer", FutureWarning, stacklevel=3) - obj = obj.astype(intp) - if isinstance(_obj, (int, long, integer)): + if isinstance(obj, (int, integer)) and not isinstance(obj, bool): # optimization for a single value - obj = obj.item() if (obj < -N or obj >= N): raise IndexError( "index %i is out of bounds for axis %i with " @@ -4358,11 +4352,23 @@ def delete(arr, obj, axis=None): slobj2[axis] = slice(obj+1, None) new[tuple(slobj)] = arr[tuple(slobj2)] else: + _obj = obj + obj = np.asarray(obj) if obj.size == 0 and not isinstance(_obj, np.ndarray): obj = obj.astype(intp) - keep = ones(N, dtype=bool) - keep[obj, ] = False + if obj.dtype == bool: + if obj.shape != (N,): + raise ValueError('boolean array argument obj to delete ' + 'must be one dimensional and match the axis ' + 'length of {}'.format(N)) + + # optimization, the other branch is slower + keep = ~obj + else: + keep = ones(N, dtype=bool) + keep[obj,] = False + slobj[axis] = keep new = arr[tuple(slobj)] diff --git a/numpy/lib/tests/test_function_base.py b/numpy/lib/tests/test_function_base.py index fb10205d4..3e621f077 100644 --- a/numpy/lib/tests/test_function_base.py +++ b/numpy/lib/tests/test_function_base.py @@ -809,9 +809,6 @@ class TestDelete: a_del = delete(self.a, indices) nd_a_del = delete(self.nd_a, indices, axis=1) msg = 'Delete failed for obj: %r' % indices - # NOTE: The cast should be removed after warning phase for bools - if not isinstance(indices, (slice, int, long, np.integer)): - indices = np.asarray(indices, dtype=np.intp) assert_array_equal(setxor1d(a_del, self.a[indices, ]), self.a, err_msg=msg) xor = setxor1d(nd_a_del[0,:, 0], self.nd_a[0, indices, 0]) @@ -827,7 +824,6 @@ class TestDelete: self._check_inverse_of_slicing(s) def test_fancy(self): - # Deprecation/FutureWarning tests should be kept after change. self._check_inverse_of_slicing(np.array([[0, 1], [2, 1]])) with pytest.raises(IndexError): delete(self.a, [100]) @@ -836,14 +832,17 @@ class TestDelete: self._check_inverse_of_slicing([0, -1, 2, 2]) - with warnings.catch_warnings(record=True) as w: - warnings.filterwarnings('always', category=FutureWarning) - obj = np.array([True, False, False], dtype=bool) - self._check_inverse_of_slicing(obj) - # _check_inverse_of_slicing operates on two arrays, so warns twice - assert len(w) == 2 - assert_(w[0].category is FutureWarning) - assert_(w[1].category is FutureWarning) + self._check_inverse_of_slicing([True, False, False, True, False]) + + # not legal, indexing with these would change the dimension + with pytest.raises(ValueError): + delete(self.a, True) + with pytest.raises(ValueError): + delete(self.a, False) + + # not enough items + with pytest.raises(ValueError): + delete(self.a, [False]*4) def test_single(self): self._check_inverse_of_slicing(0) |