diff --git a/doc/release/1.17.0-notes.rst b/doc/release/1.17.0-notes.rst index 297dbbf2d1d6..95de953b51dc 100644 --- a/doc/release/1.17.0-notes.rst +++ b/doc/release/1.17.0-notes.rst @@ -43,6 +43,13 @@ from python. This deprecation should not affect many users since arrays created in such a manner are very rare in practice and only available through the NumPy C-API. +`numpy.nonzero` should no longer be called on 0d arrays +------------------------------------------------------- +The behavior of nonzero on 0d arrays was surprising, making uses of it almost +always incorrect. If the old behavior was intended, it can be preserved without +a warning by using ``nonzero(atleast_1d(arr))`` instead of ``nonzero(arr)``. +In a future release, it is most likely this will raise a `ValueError`. + Future Changes ============== diff --git a/numpy/core/fromnumeric.py b/numpy/core/fromnumeric.py index f262f8552108..08f17aae499b 100644 --- a/numpy/core/fromnumeric.py +++ b/numpy/core/fromnumeric.py @@ -1764,17 +1764,17 @@ def nonzero(a): Returns a tuple of arrays, one for each dimension of `a`, containing the indices of the non-zero elements in that dimension. The values in `a` are always tested and returned in - row-major, C-style order. The corresponding non-zero - values can be obtained with:: + row-major, C-style order. - a[nonzero(a)] + To group the indices by element, rather than dimension, use `argwhere`, + which returns a row for each non-zero element. - To group the indices by element, rather than dimension, use:: - - transpose(nonzero(a)) + .. note:: + When called on a zero-d array or scalar, ``nonzero(a)`` is treated + as ``nonzero(atleast1d(a))``. - The result of this is always a 2-D array, with a row for - each non-zero element. + ..deprecated:: 1.17.0 + Use `atleast1d` explicitly if this behavior is deliberate. Parameters ---------- @@ -1795,11 +1795,12 @@ def nonzero(a): Equivalent ndarray method. count_nonzero : Counts the number of non-zero elements in the input array. - + Notes ----- - To obtain the non-zero values, it is recommended to use ``x[x.astype(bool)]`` - which will correctly handle 0-d arrays. + While the nonzero values can be obtained with ``a[nonzero(a)]``, it is + recommended to use ``x[x.astype(bool)]`` or ``x[x != 0]`` instead, which + will correctly handle 0-d arrays. Examples -------- diff --git a/numpy/core/src/multiarray/item_selection.c b/numpy/core/src/multiarray/item_selection.c index a6889ef8f674..11c45dce54ab 100644 --- a/numpy/core/src/multiarray/item_selection.c +++ b/numpy/core/src/multiarray/item_selection.c @@ -2213,8 +2213,26 @@ PyArray_Nonzero(PyArrayObject *self) NpyIter_GetMultiIndexFunc *get_multi_index; char **dataptr; - /* Special case - nonzero(zero_d) is nonzero(atleast1d(zero_d)) */ + /* Special case - nonzero(zero_d) is nonzero(atleast_1d(zero_d)) */ if (ndim == 0) { + char const* msg; + if (PyArray_ISBOOL(self)) { + msg = + "Calling nonzero on 0d arrays is deprecated, as it behaves " + "surprisingly. Use `atleast_1d(cond).nonzero()` if the old " + "behavior was intended. If the context of this warning is of " + "the form `arr[nonzero(cond)]`, just use `arr[cond]`."; + } + else { + msg = + "Calling nonzero on 0d arrays is deprecated, as it behaves " + "surprisingly. Use `atleast_1d(arr).nonzero()` if the old " + "behavior was intended."; + } + if (DEPRECATE(msg) < 0) { + return NULL; + } + static npy_intp const zero_dim_shape[1] = {1}; static npy_intp const zero_dim_strides[1] = {0}; diff --git a/numpy/core/tests/test_deprecations.py b/numpy/core/tests/test_deprecations.py index 346a0038a6f2..6d71fcbd658e 100644 --- a/numpy/core/tests/test_deprecations.py +++ b/numpy/core/tests/test_deprecations.py @@ -541,3 +541,10 @@ class TestShape1Fields(_DeprecationTestCase): # 2019-05-20, 1.17.0 def test_shape_1_fields(self): self.assert_deprecated(np.dtype, args=([('a', int, 1)],)) + + +class TestNonZero(_DeprecationTestCase): + # 2019-05-26, 1.17.0 + def test_zerod(self): + self.assert_deprecated(lambda: np.nonzero(np.array(0))) + self.assert_deprecated(lambda: np.nonzero(np.array(1))) diff --git a/numpy/core/tests/test_numeric.py b/numpy/core/tests/test_numeric.py index 1db89e81fb68..935b84234b01 100644 --- a/numpy/core/tests/test_numeric.py +++ b/numpy/core/tests/test_numeric.py @@ -1011,12 +1011,24 @@ def test_nonzero_trivial(self): assert_equal(np.count_nonzero(np.array([], dtype='?')), 0) assert_equal(np.nonzero(np.array([])), ([],)) + assert_equal(np.count_nonzero(np.array([0])), 0) + assert_equal(np.count_nonzero(np.array([0], dtype='?')), 0) + assert_equal(np.nonzero(np.array([0])), ([],)) + + assert_equal(np.count_nonzero(np.array([1])), 1) + assert_equal(np.count_nonzero(np.array([1], dtype='?')), 1) + assert_equal(np.nonzero(np.array([1])), ([0],)) + + def test_nonzero_zerod(self): assert_equal(np.count_nonzero(np.array(0)), 0) assert_equal(np.count_nonzero(np.array(0, dtype='?')), 0) - assert_equal(np.nonzero(np.array(0)), ([],)) + with assert_warns(DeprecationWarning): + assert_equal(np.nonzero(np.array(0)), ([],)) + assert_equal(np.count_nonzero(np.array(1)), 1) assert_equal(np.count_nonzero(np.array(1, dtype='?')), 1) - assert_equal(np.nonzero(np.array(1)), ([0],)) + with assert_warns(DeprecationWarning): + assert_equal(np.nonzero(np.array(1)), ([0],)) def test_nonzero_onedim(self): x = np.array([1, 0, 2, -1, 0, 0, 8])