Skip to content

Commit

Permalink
Merge pull request numpy#13708 from eric-wieser/deprecate-nonzero
Browse files Browse the repository at this point in the history
DEP: Deprecate nonzero(0d) in favor of calling atleast_1d explicitly
  • Loading branch information
mattip authored Jun 13, 2019
2 parents 53c60d4 + dcf9d96 commit 02c8e80
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 14 deletions.
7 changes: 7 additions & 0 deletions doc/release/1.17.0-notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
==============
Expand Down
23 changes: 12 additions & 11 deletions numpy/core/fromnumeric.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
----------
Expand All @@ -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
--------
Expand Down
20 changes: 19 additions & 1 deletion numpy/core/src/multiarray/item_selection.c
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down
7 changes: 7 additions & 0 deletions numpy/core/tests/test_deprecations.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)))
16 changes: 14 additions & 2 deletions numpy/core/tests/test_numeric.py
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down

0 comments on commit 02c8e80

Please sign in to comment.