Repository: pydata/sparse

Issue:
Grabbing nonzero indices
Is the preferred way to access the coordinates/indices to use something like

```
i, j, k = arr.coords  # for a 3d array
```

Is there a reason why `coo` arrays don't implement `nonzero` as a method? I think this would make `np.argwhere(arr)` work as well without converting to a dense array.

Huge thanks for developing this package -- it will be very useful to me.

Discussion:
codecov-io: # [Codecov](https://codecov.io/gh/pydata/sparse/pull/148?src=pr&el=h1) Report
> Merging [#148](https://codecov.io/gh/pydata/sparse/pull/148?src=pr&el=desc) into [master](https://codecov.io/gh/pydata/sparse/commit/8f2a9aebe595762eace6bc48531119462f979e21?src=pr&el=desc) will **increase** coverage by `<.01%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/pydata/sparse/pull/148/graphs/tree.svg?width=650&height=150&src=pr&token=H212u0Uxxw)](https://codecov.io/gh/pydata/sparse/pull/148?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #148      +/-   ##
==========================================
+ Coverage   95.81%   95.82%   +<.01%     
==========================================
  Files          10       10              
  Lines        1195     1197       +2     
==========================================
+ Hits         1145     1147       +2     
  Misses         50       50
```


| [Impacted Files](https://codecov.io/gh/pydata/sparse/pull/148?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [sparse/coo/core.py](https://codecov.io/gh/pydata/sparse/pull/148/diff?src=pr&el=tree#diff-c3BhcnNlL2Nvby9jb3JlLnB5) | `93.71% <100%> (+0.03%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/pydata/sparse/pull/148?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/pydata/sparse/pull/148?src=pr&el=footer). Last update [8f2a9ae...22cc851](https://codecov.io/gh/pydata/sparse/pull/148?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/docs/generated/sparse.COO.nonzero.rst b/docs/generated/sparse.COO.nonzero.rst
new file mode 100644
index 0000000..8f0cdd2
--- /dev/null
+++ b/docs/generated/sparse.COO.nonzero.rst
@@ -0,0 +1,6 @@
+COO.nonzero
+===========
+
+.. currentmodule:: sparse
+
+.. automethod:: COO.nonzero
\ No newline at end of file
diff --git a/docs/generated/sparse.COO.rst b/docs/generated/sparse.COO.rst
index 1e2452c..d2438ab 100644
--- a/docs/generated/sparse.COO.rst
+++ b/docs/generated/sparse.COO.rst
@@ -61,6 +61,7 @@ COO
       COO.dot
       COO.reshape
       COO.transpose
+      COO.nonzero
 
    .. rubric:: Utility functions
    .. autosummary::
diff --git a/sparse/coo/core.py b/sparse/coo/core.py
index 831e6f7..331e21f 100644
--- a/sparse/coo/core.py
+++ b/sparse/coo/core.py
@@ -1437,6 +1437,27 @@ class COO(SparseArray, NDArrayOperatorsMixin):
             raise ValueError("Operation would require converting "
                              "large sparse array to dense")
 
+    def nonzero(self):
+        """
+        Get the indices where this array is nonzero.
+
+        Returns
+        -------
+        idx : tuple[numpy.ndarray]
+            The indices where this array is nonzero.
+
+        See Also
+        --------
+        :obj:`numpy.ndarray.nonzero` : NumPy equivalent function
+
+        Examples
+        --------
+        >>> s = COO.from_numpy(np.eye(5))
+        >>> s.nonzero()
+        (array([0, 1, 2, 3, 4], dtype=uint8), array([0, 1, 2, 3, 4], dtype=uint8))
+        """
+        return tuple(self.coords)
+
 
 def _keepdims(original, new, axis):
     shape = list(original.shape)


Test patch:
diff --git a/sparse/tests/test_coo.py b/sparse/tests/test_coo.py
index 4a3e8c8..74bcce0 100644
--- a/sparse/tests/test_coo.py
+++ b/sparse/tests/test_coo.py
@@ -1374,3 +1374,24 @@ def test_two_arg_where():
 
     with pytest.raises(ValueError):
         sparse.where(cs, xs)
+
+
+def test_nonzero():
+    s = sparse.random((2, 3, 4), density=0.5)
+    x = s.todense()
+
+    expected = x.nonzero()
+    actual = s.nonzero()
+
+    assert isinstance(actual, tuple)
+    assert len(expected) == len(actual)
+
+    for e, a in zip(expected, actual):
+        assert_eq(e, a, compare_dtype=False)
+
+
+def test_argwhere():
+    s = sparse.random((2, 3, 4), density=0.5)
+    x = s.todense()
+
+    assert_eq(np.argwhere(s), np.argwhere(x), compare_dtype=False)