Repository: dask/dask

Issue:
Error when using a Numpy boolean mask on a Dask array
I am trying to extract values from a Dask array using a boolean Numpy array and am running into the following error:

```python
In [8]: import numpy as np                                                                                                                                                                                                                                                                                                                                                                                                                                                                              

In [9]: from dask.array import random                                                                                                                                                                                                                                                                                                                                                                                                                                                                   

In [10]: array = random.random((4, 3, 2))                                                                                                                                                                                                                                                                                                                                                                                                                                                               

In [11]: mask = np.random.random((4, 3, 2)) > 0.5                                                                                                                                                                                                                                                                                                                                                                                                                                                       

In [12]: array[mask]                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-12-38297b4b7b0f> in <module>
----> 1 array[mask]

~/python/dev/lib/python3.8/site-packages/dask/array/core.py in __getitem__(self, index)
   1537         )
   1538 
-> 1539         index2 = normalize_index(index, self.shape)
   1540 
   1541         dependencies = {self.name}

~/python/dev/lib/python3.8/site-packages/dask/array/slicing.py in normalize_index(idx, shape)
    813     for i, d in zip(idx, none_shape):
    814         if d is not None:
--> 815             check_index(i, d)
    816     idx = tuple(map(sanitize_index, idx))
    817     idx = tuple(map(normalize_slice, idx, none_shape))

~/python/dev/lib/python3.8/site-packages/dask/array/slicing.py in check_index(ind, dimension)
    867         if x.dtype == bool:
    868             if x.size != dimension:
--> 869                 raise IndexError(
    870                     "Boolean array length %s doesn't equal dimension %s"
    871                     % (x.size, dimension)

IndexError: Boolean array length 24 doesn't equal dimension 4
```

Note that converting the mask to a dask array fixes this:

```python
In [14]: from dask.array import asarray                                                                                                                                                                                                                                                                                                                                                                                                                                                                 

In [15]: array[asarray(mask)]                                                                                                                                                                                                                                                                                                                                                                                                                                                                           
Out[15]: dask.array<getitem, shape=(nan,), dtype=float64, chunksize=(nan,), chunktype=numpy.ndarray>

In [16]: array[asarray(mask)].compute()                                                                                                                                                                                                                                                                                                                                                                                                                                                                 
Out[16]: 
array([0.87541677, 0.72561386, 0.80415762, 0.90781457, 0.41807167,
       0.88501242, 0.33921153, 0.5392456 , 0.31133289, 0.86372707,
       0.32839977, 0.77310795])
```

but I thought I should report this as a bug.


Discussion:
Thanks for posting, I agree that this looks like a bug.
I'm taking a look at this now, but in the meantime as a workaround you can use a dask array rather than a numpy array:

```python
import numpy as np
import dask.array as da

array = da.random.random((4, 3, 2))
mask = np.random.random((4, 3, 2)) > 0.5  

array[da.from_array(mask)].compute()
```
Maybe a cheap solution to this is for us to turn numpy arrays into dask
arrays under certain conditions?

I haven't thought deeply about the implications here though.

On Mon, Jun 1, 2020 at 9:07 AM Julia Signell <notifications@github.com>
wrote:

> I'm taking a look at this now, but in the meantime as a workaround you can
> use a dask array rather than a numpy array:
>
> import numpy as npimport dask.array as da
> array = da.random.random((4, 3, 2))mask = np.random.random((4, 3, 2)) > 0.5
> array[da.from_array(mask)].compute()
>
> —
> You are receiving this because you are subscribed to this thread.
> Reply to this email directly, view it on GitHub
> <https://github.com/dask/dask/issues/6089#issuecomment-636949507>, or
> unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AACKZTEFYF5LPTLFDKYL5ALRUPG2XANCNFSM4MG7ZJGA>
> .
>

> Maybe a cheap solution to this is for us to turn numpy arrays into dask arrays under certain conditions?

This would be helpful for #6270 
> Maybe a cheap solution to this is for us to turn numpy arrays into dask
arrays under certain conditions?

Yeah, that might be the best approach. I'm struggling with figuring out a different solution which would work with more than one partition.

Resolving patch:
diff --git a/dask/array/slicing.py b/dask/array/slicing.py
--- a/dask/array/slicing.py
+++ b/dask/array/slicing.py
@@ -769,9 +769,10 @@ def normalize_index(idx, shape):
     1.  Replaces ellipses with many full slices
     2.  Adds full slices to end of index
     3.  Checks bounding conditions
-    4.  Replaces numpy arrays with lists
-    5.  Posify's integers and lists
-    6.  Normalizes slices to canonical form
+    4.  Replace multidimensional numpy arrays with dask arrays
+    5.  Replaces numpy arrays with lists
+    6.  Posify's integers and lists
+    7.  Normalizes slices to canonical form
 
     Examples
     --------
@@ -785,9 +786,20 @@ def normalize_index(idx, shape):
     (slice(7, None, None),)
     >>> normalize_index((Ellipsis, None), (10,))
     (slice(None, None, None), None)
+    >>> normalize_index(np.array([[True, False], [False, True], [True, True]]), (3, 2))
+    (dask.array<array, shape=(3, 2), dtype=bool, chunksize=(3, 2), chunktype=numpy.ndarray>,)
     """
+    from .core import from_array
+
     if not isinstance(idx, tuple):
         idx = (idx,)
+
+    # if a > 1D numpy.array is provided, cast it to a dask array
+    if len(idx) > 0 and len(shape) > 1:
+        i = idx[0]
+        if isinstance(i, np.ndarray) and i.shape == shape:
+            idx = (from_array(i), *idx[1:])
+
     idx = replace_ellipsis(len(shape), idx)
     n_sliced_dims = 0
     for i in idx:


Test patch:
diff --git a/dask/array/tests/test_slicing.py b/dask/array/tests/test_slicing.py
--- a/dask/array/tests/test_slicing.py
+++ b/dask/array/tests/test_slicing.py
@@ -948,3 +948,13 @@ def test_gh4043(lock, asarray, fancy):
     a2 = da.from_array(np.ones(3), chunks=1, asarray=asarray, lock=lock, fancy=fancy)
     al = da.stack([a1, a2])
     assert_eq(al, al)
+
+
+def test_slice_array_3d_with_bool_numpy_array():
+    # https://github.com/dask/dask/issues/6089
+    array = da.arange(0, 24).reshape((4, 3, 2))
+    mask = np.arange(0, 24).reshape((4, 3, 2)) > 12
+
+    actual = array[mask].compute()
+    expected = np.arange(13, 24)
+    assert_eq(actual, expected)