Repository: pydata/xarray (Python)

Issue:
Array indexing with dask arrays
#### Code example
```python
da = xr.DataArray(np.ones((10, 10))).chunk(2)
indc = xr.DataArray(np.random.randint(0, 9, 10)).chunk(2)

# This fails:
da[{'dim_1' : indc}].values
```

#### Problem description
Indexing with chunked arrays fails, whereas it's fine with "normal" arrays. In case the indices are the result of a lazy calculation, I would like to continue lazily.

#### Expected Output
I would expect an output just like in the "un-chunked" case:

```
da[{'dim_1' : indc.compute()}].values
# Returns: array([1., 1., 1., 1., 1., 1., 1., 1., 1., 1.])
```


#### Output of ``xr.show_versions()``

<details>
INSTALLED VERSIONS
------------------
commit: None
python: 3.7.0.final.0
python-bits: 64
OS: Linux
OS-release: 4.18.14-arch1-1-ARCH
machine: x86_64
processor:
byteorder: little
LC_ALL: None
LANG: de_DE.utf8
LOCALE: de_DE.UTF-8

xarray: 0.10.9
pandas: 0.23.4
numpy: 1.15.2
scipy: 1.1.0
netCDF4: None
h5netcdf: 0.6.2
h5py: 2.8.0
Nio: None
zarr: None
cftime: None
PseudonetCDF: None
rasterio: None
iris: None
bottleneck: 1.2.1
cyordereddict: None
dask: 0.19.4
distributed: None
matplotlib: 2.2.3
cartopy: 0.16.0
seaborn: None
setuptools: 40.4.3
pip: 18.0
conda: None
pytest: 3.8.2
IPython: 6.5.0
sphinx: 1.8.0
</details>

isel with 0d dask array index fails
<!-- Please include a self-contained copy-pastable example that generates the issue if possible.

Please be concise with code posted. See guidelines below on how to provide a good bug report:

- Craft Minimal Bug Reports: http://matthewrocklin.com/blog/work/2018/02/28/minimal-bug-reports
- Minimal Complete Verifiable Examples: https://stackoverflow.com/help/mcve

Bug reports that follow these guidelines are easier to diagnose, and so are often handled much more quickly.
-->

**What happened**:
If a 0d dask array is passed as an argument to `isel()`, an error occurs because dask arrays do not have a `.item()` method. I came across this when trying to use the result of `da.argmax()` from a dask-backed array to select from the DataArray.

**What you expected to happen**:
`isel()` returns the value at the index contained in the 0d dask array.

**Minimal Complete Verifiable Example**:

```python
import dask.array as daskarray
import numpy as np
import xarray as xr

a = daskarray.from_array(np.linspace(0., 1.))
da = xr.DataArray(a, dims="x")

x_selector = da.argmax(dim=...)

da_max = da.isel(x_selector)
```

**Anything else we need to know?**:

I think the problem is here
https://github.com/pydata/xarray/blob/a198218ddabe557adbb04311b3234ec8d20419e7/xarray/core/variable.py#L546-L548
and `k.values.item()` or `int(k.data)` would fix my issue, but I don't know the reason for using `.item()` in the first place, so I'm not sure if either of these would have some undesirable side-effect.

May be related to #2511, but from the code snippet above, I think this is a specific issue of 0d dask arrays rather than a generic dask-indexing issue like #2511.

I'd like to fix this because it breaks the nice new features of `argmin()` and `argmax()` if the `DataArray` is dask-backed.

**Environment**:

<details><summary>Output of <tt>xr.show_versions()</tt></summary>

<!-- Paste the output here xr.show_versions() here -->
INSTALLED VERSIONS
------------------
commit: None
python: 3.7.6 | packaged by conda-forge | (default, Jun  1 2020, 18:57:50) 
[GCC 7.5.0]
python-bits: 64
OS: Linux
OS-release: 5.4.0-42-generic
machine: x86_64
processor: x86_64
byteorder: little
LC_ALL: None
LANG: en_GB.UTF-8
LOCALE: en_GB.UTF-8
libhdf5: 1.10.5
libnetcdf: 4.7.4

xarray: 0.16.0
pandas: 1.0.5
numpy: 1.18.5
scipy: 1.4.1
netCDF4: 1.5.3
pydap: None
h5netcdf: None
h5py: 2.10.0
Nio: None
zarr: None
cftime: 1.2.1
nc_time_axis: None
PseudoNetCDF: None
rasterio: None
cfgrib: None
iris: None
bottleneck: None
dask: 2.19.0
distributed: 2.21.0
matplotlib: 3.2.2
cartopy: None
seaborn: None
numbagg: None
pint: 0.13
setuptools: 49.2.0.post20200712
pip: 20.1.1
conda: 4.8.3
pytest: 5.4.3
IPython: 7.15.0
sphinx: None


</details>

Fancy indexing a Dataset with dask DataArray triggers multiple computes
It appears that boolean arrays (or any slicing array presumably) are evaluated many more times than necessary when applied to multiple variables in a Dataset.  Is this intentional?  Here is an example that demonstrates this:

```python

# Use a custom array type to know when data is being evaluated
class Array():
    
    def __init__(self, x):
        self.shape = (x.shape[0],)
        self.ndim = x.ndim
        self.dtype = 'bool'
        self.x = x
        
    def __getitem__(self, idx):
        if idx[0].stop > 0:
            print('Evaluating')
        return (self.x > .5).__getitem__(idx)

# Control case -- this shows that the print statement is only reached once
da.from_array(Array(np.random.rand(100))).compute();
# Evaluating

# This usage somehow results in two evaluations of this one array?
ds = xr.Dataset(dict(
    a=('x', da.from_array(Array(np.random.rand(100))))
))
ds.sel(x=ds.a)
# Evaluating
# Evaluating
# <xarray.Dataset>
# Dimensions:  (x: 51)
# Dimensions without coordinates: x
# Data variables:
#     a        (x) bool dask.array<chunksize=(51,), meta=np.ndarray>

# The array is evaluated an extra time for each new variable
ds = xr.Dataset(dict(
    a=('x', da.from_array(Array(np.random.rand(100)))),
    b=(('x', 'y'), da.random.random((100, 10))),
    c=(('x', 'y'), da.random.random((100, 10))),
    d=(('x', 'y'), da.random.random((100, 10))),
))
ds.sel(x=ds.a)
# Evaluating
# Evaluating
# Evaluating
# Evaluating
# Evaluating
# <xarray.Dataset>
# Dimensions:  (x: 48, y: 10)
# Dimensions without coordinates: x, y
# Data variables:
#     a        (x) bool dask.array<chunksize=(48,), meta=np.ndarray>
#     b        (x, y) float64 dask.array<chunksize=(48, 10), meta=np.ndarray>
#     c        (x, y) float64 dask.array<chunksize=(48, 10), meta=np.ndarray>
#     d        (x, y) float64 dask.array<chunksize=(48, 10), meta=np.ndarray>
```

Given that slicing is already not lazy, why does the same predicate array need to be computed more than once? 

@tomwhite originally pointed this out in https://github.com/pystatgen/sgkit/issues/299.


Discussion: For reference, here's the current stacktrace/error message:
```python-traceback
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-7-74fe4ba70f9d> in <module>()
----> 1 da[{'dim_1' : indc}]

/usr/local/lib/python3.6/dist-packages/xarray/core/dataarray.py in __getitem__(self, key)
    472         else:
    473             # xarray-style array indexing
--> 474             return self.isel(indexers=self._item_key_to_dict(key))
    475 
    476     def __setitem__(self, key, value):

/usr/local/lib/python3.6/dist-packages/xarray/core/dataarray.py in isel(self, indexers, drop, **indexers_kwargs)
    817         """
    818         indexers = either_dict_or_kwargs(indexers, indexers_kwargs, 'isel')
--> 819         ds = self._to_temp_dataset().isel(drop=drop, indexers=indexers)
    820         return self._from_temp_dataset(ds)
    821 

/usr/local/lib/python3.6/dist-packages/xarray/core/dataset.py in isel(self, indexers, drop, **indexers_kwargs)
   1537         for name, var in iteritems(self._variables):
   1538             var_indexers = {k: v for k, v in indexers_list if k in var.dims}
-> 1539             new_var = var.isel(indexers=var_indexers)
   1540             if not (drop and name in var_indexers):
   1541                 variables[name] = new_var

/usr/local/lib/python3.6/dist-packages/xarray/core/variable.py in isel(self, indexers, drop, **indexers_kwargs)
    905             if dim in indexers:
    906                 key[i] = indexers[dim]
--> 907         return self[tuple(key)]
    908 
    909     def squeeze(self, dim=None):

/usr/local/lib/python3.6/dist-packages/xarray/core/variable.py in __getitem__(self, key)
    614         array `x.values` directly.
    615         """
--> 616         dims, indexer, new_order = self._broadcast_indexes(key)
    617         data = as_indexable(self._data)[indexer]
    618         if new_order:

/usr/local/lib/python3.6/dist-packages/xarray/core/variable.py in _broadcast_indexes(self, key)
    487             return self._broadcast_indexes_outer(key)
    488 
--> 489         return self._broadcast_indexes_vectorized(key)
    490 
    491     def _broadcast_indexes_basic(self, key):

/usr/local/lib/python3.6/dist-packages/xarray/core/variable.py in _broadcast_indexes_vectorized(self, key)
    599             new_order = None
    600 
--> 601         return out_dims, VectorizedIndexer(tuple(out_key)), new_order
    602 
    603     def __getitem__(self, key):

/usr/local/lib/python3.6/dist-packages/xarray/core/indexing.py in __init__(self, key)
    423             else:
    424                 raise TypeError('unexpected indexer type for {}: {!r}'
--> 425                                 .format(type(self).__name__, k))
    426             new_key.append(k)
    427 

TypeError: unexpected indexer type for VectorizedIndexer: dask.array<xarray-<this-array>, shape=(10,), dtype=int64, chunksize=(2,)>
```

It looks like we could support this relatively easily since dask.array supports indexing with dask arrays now. This would be a welcome enhancement!
It seem's working fine with the following change but it has a lot of dublicated code...
```
diff --git a/xarray/core/indexing.py b/xarray/core/indexing.py
index d51da471..9fe93581 100644
--- a/xarray/core/indexing.py
+++ b/xarray/core/indexing.py
@@ -7,6 +7,7 @@ from datetime import timedelta
 
 import numpy as np
 import pandas as pd
+import dask.array as da
 
 from . import duck_array_ops, nputils, utils
 from .pycompat import (
@@ -420,6 +421,19 @@ class VectorizedIndexer(ExplicitIndexer):
                                      'have different numbers of dimensions: {}'
                                      .format(ndims))
                 k = np.asarray(k, dtype=np.int64)
+            elif isinstance(k, dask_array_type):
+                if not np.issubdtype(k.dtype, np.integer):
+                    raise TypeError('invalid indexer array, does not have '
+                                    'integer dtype: {!r}'.format(k))
+                if ndim is None:
+                    ndim = k.ndim
+                elif ndim != k.ndim:
+                    ndims = [k.ndim for k in key
+                             if isinstance(k, (np.ndarray) + dask_array_type)]
+                    raise ValueError('invalid indexer key: ndarray arguments '
+                                     'have different numbers of dimensions: {}'
+                                     .format(ndims))
+                k = da.array(k, dtype=np.int64)
             else:
                 raise TypeError('unexpected indexer type for {}: {!r}'
                                 .format(type(self).__name__, k))
```
As of version 0.12 indexing with dask arrays works out of the box... I think this can be closed now.
Even though the example from above does work, sadly, the following does not:
``` python
import xarray as xr
import dask.array as da
import numpy as np
da = xr.DataArray(np.random.rand(3*4*5).reshape((3,4,5))).chunk(dict(dim_0=1))
idcs = da.argmax('dim_2')
da[dict(dim_2=idcs)]
```
results in
``` python
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-4-3542cdd6d61c> in <module>
----> 1 da[dict(dim_2=idcs)]

~/src/xarray/xarray/core/dataarray.py in __getitem__(self, key)
    604         else:
    605             # xarray-style array indexing
--> 606             return self.isel(indexers=self._item_key_to_dict(key))
    607 
    608     def __setitem__(self, key: Any, value: Any) -> None:

~/src/xarray/xarray/core/dataarray.py in isel(self, indexers, drop, **indexers_kwargs)
    986         """
    987         indexers = either_dict_or_kwargs(indexers, indexers_kwargs, "isel")
--> 988         ds = self._to_temp_dataset().isel(drop=drop, indexers=indexers)
    989         return self._from_temp_dataset(ds)
    990 

~/src/xarray/xarray/core/dataset.py in isel(self, indexers, drop, **indexers_kwargs)
   1901                     indexes[name] = new_index
   1902             else:
-> 1903                 new_var = var.isel(indexers=var_indexers)
   1904 
   1905             variables[name] = new_var

~/src/xarray/xarray/core/variable.py in isel(self, indexers, drop, **indexers_kwargs)
    984             if dim in indexers:
    985                 key[i] = indexers[dim]
--> 986         return self[tuple(key)]
    987 
    988     def squeeze(self, dim=None):

~/src/xarray/xarray/core/variable.py in __getitem__(self, key)
    675         array `x.values` directly.
    676         """
--> 677         dims, indexer, new_order = self._broadcast_indexes(key)
    678         data = as_indexable(self._data)[indexer]
    679         if new_order:

~/src/xarray/xarray/core/variable.py in _broadcast_indexes(self, key)
    532             if isinstance(k, Variable):
    533                 if len(k.dims) > 1:
--> 534                     return self._broadcast_indexes_vectorized(key)
    535                 dims.append(k.dims[0])
    536             elif not isinstance(k, integer_types):

~/src/xarray/xarray/core/variable.py in _broadcast_indexes_vectorized(self, key)
    660             new_order = None
    661 
--> 662         return out_dims, VectorizedIndexer(tuple(out_key)), new_order
    663 
    664     def __getitem__(self, key):

~/src/xarray/xarray/core/indexing.py in __init__(self, key)
    460                 raise TypeError(
    461                     "unexpected indexer type for {}: {!r}".format(
--> 462                         type(self).__name__, k
    463                     )
    464                 )

TypeError: unexpected indexer type for VectorizedIndexer: dask.array<arg_agg-aggregate, shape=(3, 4), dtype=int64, chunksize=(1, 4)>
```
Yes, something seems to be going wrong here...
I think the problem is somewhere here:

https://github.com/pydata/xarray/blob/aaeea6250b89e3605ee1d1a160ad50d6ed657c7e/xarray/core/utils.py#L85-L103

I don't think `pandas.Index` can hold lazy arrays. Could there be a way around exploiting `dask.dataframe` indexing methods?
I'm having similar issue, here is an example:

```
import numpy as np
import dask.array as da
import xarray as xr

darr = xr.DataArray(data=[0.2, 0.4, 0.6], coords={"z": range(3)}, dims=("z",))
good_indexer = xr.DataArray(
    data=np.random.randint(0, 3, 8).reshape(4, 2).astype(int),
    coords={"y": range(4), "x": range(2)},
    dims=("y", "x")
)
bad_indexer = xr.DataArray(
    data=da.random.randint(0, 3, 8).reshape(4, 2).astype(int),
    coords={"y": range(4), "x": range(2)},
    dims=("y", "x")
)

In [5]: darr                                                                                                                                                            
Out[5]: 
<xarray.DataArray (z: 3)>
array([0.2, 0.4, 0.6])
Coordinates:
  * z        (z) int64 0 1 2

In [6]: good_indexer                                                     
Out[6]: 
<xarray.DataArray (y: 4, x: 2)>
array([[0, 1],
       [2, 2],
       [1, 2],
       [1, 0]])
Coordinates:
  * y        (y) int64 0 1 2 3
  * x        (x) int64 0 1

In [7]: bad_indexer                                                      
Out[7]: 
<xarray.DataArray 'reshape-417766b2035dcb1227ddde8505297039' (y: 4, x: 2)>
dask.array<reshape, shape=(4, 2), dtype=int64, chunksize=(4, 2), chunktype=numpy.ndarray>
Coordinates:
  * y        (y) int64 0 1 2 3
  * x        (x) int64 0 1

In [8]: darr[good_indexer]                                                                                                                                              
Out[8]: 
<xarray.DataArray (y: 4, x: 2)>
array([[0.2, 0.4],
       [0.6, 0.6],
       [0.4, 0.6],
       [0.4, 0.2]])
Coordinates:
    z        (y, x) int64 0 1 2 2 1 2 1 0
  * y        (y) int64 0 1 2 3
  * x        (x) int64 0 1

In [9]: darr[bad_indexer]                                                                                                                                               
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-8-2a57c1a2eade> in <module>
----> 1 darr[bad_indexer]

~/.virtualenvs/py3/local/lib/python3.7/site-packages/xarray/core/dataarray.py in __getitem__(self, key)
    638         else:
    639             # xarray-style array indexing
--> 640             return self.isel(indexers=self._item_key_to_dict(key))
    641 
    642     def __setitem__(self, key: Any, value: Any) -> None:

~/.virtualenvs/py3/local/lib/python3.7/site-packages/xarray/core/dataarray.py in isel(self, indexers, drop, **indexers_kwargs)
   1012         """
   1013         indexers = either_dict_or_kwargs(indexers, indexers_kwargs, "isel")
-> 1014         ds = self._to_temp_dataset().isel(drop=drop, indexers=indexers)
   1015         return self._from_temp_dataset(ds)
   1016 

~/.virtualenvs/py3/local/lib/python3.7/site-packages/xarray/core/dataset.py in isel(self, indexers, drop, **indexers_kwargs)
   1920             if name in self.indexes:
   1921                 new_var, new_index = isel_variable_and_index(
-> 1922                     name, var, self.indexes[name], var_indexers
   1923                 )
   1924                 if new_index is not None:

~/.virtualenvs/py3/local/lib/python3.7/site-packages/xarray/core/indexes.py in isel_variable_and_index(name, variable, index, indexers)
     79         )
     80 
---> 81     new_variable = variable.isel(indexers)
     82 
     83     if new_variable.dims != (name,):

~/.virtualenvs/py3/local/lib/python3.7/site-packages/xarray/core/variable.py in isel(self, indexers, **indexers_kwargs)
   1052 
   1053         key = tuple(indexers.get(dim, slice(None)) for dim in self.dims)
-> 1054         return self[key]
   1055 
   1056     def squeeze(self, dim=None):

~/.virtualenvs/py3/local/lib/python3.7/site-packages/xarray/core/variable.py in __getitem__(self, key)
    700         array `x.values` directly.
    701         """
--> 702         dims, indexer, new_order = self._broadcast_indexes(key)
    703         data = as_indexable(self._data)[indexer]
    704         if new_order:

~/.virtualenvs/py3/local/lib/python3.7/site-packages/xarray/core/variable.py in _broadcast_indexes(self, key)
    557             if isinstance(k, Variable):
    558                 if len(k.dims) > 1:
--> 559                     return self._broadcast_indexes_vectorized(key)
    560                 dims.append(k.dims[0])
    561             elif not isinstance(k, integer_types):

~/.virtualenvs/py3/local/lib/python3.7/site-packages/xarray/core/variable.py in _broadcast_indexes_vectorized(self, key)
    685             new_order = None
    686 
--> 687         return out_dims, VectorizedIndexer(tuple(out_key)), new_order
    688 
    689     def __getitem__(self: VariableType, key) -> VariableType:

~/.virtualenvs/py3/local/lib/python3.7/site-packages/xarray/core/indexing.py in __init__(self, key)
    447             else:
    448                 raise TypeError(
--> 449                     f"unexpected indexer type for {type(self).__name__}: {k!r}"
    450                 )
    451             new_key.append(k)

TypeError: unexpected indexer type for VectorizedIndexer: dask.array<reshape, shape=(4, 2), dtype=int64, chunksize=(4, 2), chunktype=numpy.ndarray>

In [10]: xr.__version__                                                   
Out[10]: '0.14.1'

In [11]: import dask; dask.__version__                                    
Out[11]: '2.9.0'
```
I'm just curious if there's been any progress on this issue. I'm also getting the same error: `TypeError: unexpected indexer type for VectorizedIndexer` and I would greatly benefit from lazy vectorized indexing.
I don't think any one is working on it. We would appreciate it if you could try to fix it.
I wrote a very naive fix, it works but seems to perform **really** slowly, I would appreciate some feedback (I'm a beginner with Dask).
Basically, I added `k = dask.array.asarray(k, dtype=np.int64)` to do the exact same thing as with numpy.
_I can create a PR if it's better to review this_

The patch:
```
class VectorizedIndexer(ExplicitIndexer):
    """Tuple for vectorized indexing.

    All elements should be slice or N-dimensional np.ndarray objects with an
    integer dtype and the same number of dimensions. Indexing follows proposed
    rules for np.ndarray.vindex, which matches NumPy's advanced indexing rules
    (including broadcasting) except sliced axes are always moved to the end:
    https://github.com/numpy/numpy/pull/6256
    """

    __slots__ = ()

    def __init__(self, key):
        if not isinstance(key, tuple):
            raise TypeError(f"key must be a tuple: {key!r}")

        new_key = []
        ndim = None
        for k in key:
            if isinstance(k, slice):
                k = as_integer_slice(k)
            elif isinstance(k, np.ndarray) or isinstance(k, dask.array.Array):
                if not np.issubdtype(k.dtype, np.integer):
                    raise TypeError(
                        f"invalid indexer array, does not have integer dtype: {k!r}"
                    )
                if ndim is None:
                    ndim = k.ndim
                elif ndim != k.ndim:
                    ndims = [k.ndim for k in key if isinstance(k, np.ndarray)]
                    raise ValueError(
                        "invalid indexer key: ndarray arguments "
                        f"have different numbers of dimensions: {ndims}"
                    )
                if isinstance(k, dask.array.Array):
                    k = dask.array.asarray(k, dtype=np.int64)
                else:
                    k = np.asarray(k, dtype=np.int64)
            else:
                raise TypeError(
                    f"unexpected indexer type for {type(self).__name__}: {k!r}"
                )
            new_key.append(k)

        super().__init__(new_key)

```

@bzah I've been testing your solution and doesn't seems to slow as you are mentioning. Do you have a specific test to be conducted so that we can make a more robust comparison?


@pl-marasco Ok that's strange.
I should have saved my use case :/ 
I will try to reproduce it and will provide a gist of it soon.
> @pl-marasco Ok that's strange. I should have saved my use case :/ I will try to reproduce it and will provide a gist of it soon.

What I noticed, on my use case, is that it provoke a computation. Is that the reason for what you consider slow? Could be possible that is related to  #3237 ?
Okay I could re do my test.
If I manually call `compute()` before doing `isel(......)` my whole computation takes about **5.65 seconds**.
However if I try with my naive patch it takes **32.34 seconds**.

I'm sorry I cannot share as is my code, the relevant portion is really in the middle of many things.
I'll try to get a minimalist version of it to share with you.
@bzah I tested your patch with the following code:

```
import xarray as xr
from distributed import Client
client = Client()

da = xr.DataArray(np.random.rand(20*3500*3500).reshape((20,3500,3500)), dims=('time', 'x', 'y')).chunk(dict(time=-1, x=100, y=100))

idx = da.argmax('time').compute()
da.isel(time=idx)
```

In my case seems that with or without it takes the same time but I would like to know if is the same for you. 

L.
@pl-marasco Thanks for the example !
With it I have the same result as you, it takes the same time with patch or with compute.

However, I could construct an example giving very different results. It is quite close to my original code:

```
    time_start = time.perf_counter()
    COORDS = dict(
        time=pd.date_range("2042-01-01", periods=200,
                           freq=pd.DateOffset(days=1)),
    )
    da = xr.DataArray(
        np.random.rand(200 * 3500 * 350).reshape((200, 3500, 350)),
        dims=('time', 'x', 'y'),
        coords=COORDS
    ).chunk(dict(time=-1, x=100, y=100))

    resampled = da.resample(time="MS")

    for label, sample in resampled:
        # sample = sample.compute()
        idx = sample.argmax('time')
        sample.isel(time=idx)

    time_elapsed = time.perf_counter() - time_start
    print(time_elapsed, " secs")
```
(Basically I want for each month the first event occurring in it).

Without the patch and uncommenting `sample = sample.compute()`, it takes 5.7 secs.
With the patch it takes 53.9 seconds.

Hello! First off thank you for all the hard work on xarray! Use it every day and love it :) 

I am also having issues indexing with dask arrays and get the following error. 

```
 Traceback (most recent call last):
  File "~/phd-comps/scripts/sfire-pbl.py", line 64, in <module>
    PBLH = height.isel(gradT2.argmax(dim=['interp_level']))
  File "~/miniconda3/envs/cr/lib/python3.7/site-packages/xarray/core/dataarray.py", line 1184, in isel
    indexers, drop=drop, missing_dims=missing_dims
  File "~/miniconda3/envs/cr/lib/python3.7/site-packages/xarray/core/dataset.py", line 2389, in _isel_fancy
    new_var = var.isel(indexers=var_indexers)
  File "~/miniconda3/envs/cr/lib/python3.7/site-packages/xarray/core/variable.py", line 1156, in isel
    return self[key]
  File "~/miniconda3/envs/cr/lib/python3.7/site-packages/xarray/core/variable.py", line 776, in __getitem__
    dims, indexer, new_order = self._broadcast_indexes(key)
  File "~/miniconda3/envs/cr/lib/python3.7/site-packages/xarray/core/variable.py", line 632, in _broadcast_indexes
    return self._broadcast_indexes_vectorized(key)
  File "~/miniconda3/envs/cr/lib/python3.7/site-packages/xarray/core/variable.py", line 761, in _broadcast_indexes_vectorized
    return out_dims, VectorizedIndexer(tuple(out_key)), new_order
  File "~/miniconda3/envs/cr/lib/python3.7/site-packages/xarray/core/indexing.py", line 323, in __init__
    f"unexpected indexer type for {type(self).__name__}: {k!r}"
TypeError: unexpected indexer type for VectorizedIndexer: dask.array<getitem, shape=(240, 399, 159), dtype=int64, chunksize=(60, 133, 53), chunktype=numpy.ndarray>

```
```
dask                      2021.9.1           pyhd8ed1ab_0    conda-forge
xarray                    0.19.0             pyhd8ed1ab_0    conda-forge
```

In order to get it to work, I first need to manually call compute to load to NumPy array before using argmax with isel. Not sure what info I can provide to help solve the issue please let me know and ill send whatever I can. 


@bzah I've been testing your code and I can confirm the increment of timing once the .compute() isn't in use. 
I've noticed that using your modification, seems that dask array is computed more than one time per sample. 
I've made some tests using a modified version from #3237 and here are my observations:

Assuming that we have only one sample object after the resample the expected result should be 1 compute and that's what we obtain if we call the computation before the .argmax() 
If .compute() is removed then I got 3 total computations.
Just as a confirmation if you increase the sample you will get a multiple of 3 as a result of computes.

I still don't know the reason and if is correct or not but sounds weird to me; though it could explain the time increase. 

@dcherian @shyer do you know if all this make any sense? should the .isel() automatically trig the computation or should give back a lazy array? 

Here is the code I've been using (works only adding the modification proposed by @bzah) 

```
import numpy as np
import dask
import xarray as xr

class Scheduler:
    """ From: https://stackoverflow.com/questions/53289286/ """

    def __init__(self, max_computes=20):
        self.max_computes = max_computes
        self.total_computes = 0

    def __call__(self, dsk, keys, **kwargs):
        self.total_computes += 1
        if self.total_computes > self.max_computes:
            raise RuntimeError(
                "Too many dask computations were scheduled: {}".format(
                    self.total_computes
                )
            )
        return dask.get(dsk, keys, **kwargs)

scheduler = Scheduler()

with dask.config.set(scheduler=scheduler):

    COORDS = dict(dim_0=pd.date_range("2042-01-01", periods=31, freq='D'),
                  dim_1= range(0,500),
                  dim_2= range(0,500))

    da = xr.DataArray(np.random.rand(31 * 500 * 500).reshape((31, 500, 500)),
                      coords=COORDS).chunk(dict(dim_0=-1, dim_1=100, dim_2=100))

    print(da)

    resampled = da.resample(dim_0="MS")

    for label, sample in resampled:

        #sample = sample.compute()
        idx = sample.argmax('dim_0')
        sampled = sample.isel(dim_0=idx)

    print("Total number of computes: %d" % scheduler.total_computes)
```

I'll drop a PR, it might be easier to try and play with this than a piece of code lost in an issue.



Thanks for the great example!

This looks like a duplicate of https://github.com/pydata/xarray/issues/2801. If you agree, can we move the conversation there?

I like using our `raise_if_dask_computes` context since it points out where the compute is happening

``` python
import dask.array as da
import numpy as np

from xarray.tests import raise_if_dask_computes

# Use a custom array type to know when data is being evaluated
class Array():
    
    def __init__(self, x):
        self.shape = (x.shape[0],)
        self.ndim = x.ndim
        self.dtype = 'bool'
        self.x = x
        
    def __getitem__(self, idx):
        if idx[0].stop > 0:
            print('Evaluating')
        return (self.x > .5).__getitem__(idx)
    

with raise_if_dask_computes(max_computes=1):
    ds = xr.Dataset(dict(
        a=('x', da.from_array(Array(np.random.rand(100)))),
        b=(('x', 'y'), da.random.random((100, 10))),
        c=(('x', 'y'), da.random.random((100, 10))),
        d=(('x', 'y'), da.random.random((100, 10))),
    ))
    ds.sel(x=ds.a)
```

```python
--------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
<ipython-input-76-8efd3a1c3fe5> in <module>
     26         d=(('x', 'y'), da.random.random((100, 10))),
     27     ))
---> 28     ds.sel(x=ds.a)

/project/mrgoodbar/dcherian/python/xarray/xarray/core/dataset.py in sel(self, indexers, method, tolerance, drop, **indexers_kwargs)
   2211             self, indexers=indexers, method=method, tolerance=tolerance
   2212         )
-> 2213         result = self.isel(indexers=pos_indexers, drop=drop)
   2214         return result._overwrite_indexes(new_indexes)
   2215 

/project/mrgoodbar/dcherian/python/xarray/xarray/core/dataset.py in isel(self, indexers, drop, missing_dims, **indexers_kwargs)
   2058         indexers = either_dict_or_kwargs(indexers, indexers_kwargs, "isel")
   2059         if any(is_fancy_indexer(idx) for idx in indexers.values()):
-> 2060             return self._isel_fancy(indexers, drop=drop, missing_dims=missing_dims)
   2061 
   2062         # Much faster algorithm for when all indexers are ints, slices, one-dimensional

/project/mrgoodbar/dcherian/python/xarray/xarray/core/dataset.py in _isel_fancy(self, indexers, drop, missing_dims)
   2122                     indexes[name] = new_index
   2123             elif var_indexers:
-> 2124                 new_var = var.isel(indexers=var_indexers)
   2125             else:
   2126                 new_var = var.copy(deep=False)

/project/mrgoodbar/dcherian/python/xarray/xarray/core/variable.py in isel(self, indexers, missing_dims, **indexers_kwargs)
   1118 
   1119         key = tuple(indexers.get(dim, slice(None)) for dim in self.dims)
-> 1120         return self[key]
   1121 
   1122     def squeeze(self, dim=None):

/project/mrgoodbar/dcherian/python/xarray/xarray/core/variable.py in __getitem__(self, key)
    766         array `x.values` directly.
    767         """
--> 768         dims, indexer, new_order = self._broadcast_indexes(key)
    769         data = as_indexable(self._data)[indexer]
    770         if new_order:

/project/mrgoodbar/dcherian/python/xarray/xarray/core/variable.py in _broadcast_indexes(self, key)
    625                 dims.append(d)
    626         if len(set(dims)) == len(dims):
--> 627             return self._broadcast_indexes_outer(key)
    628 
    629         return self._broadcast_indexes_vectorized(key)

/project/mrgoodbar/dcherian/python/xarray/xarray/core/variable.py in _broadcast_indexes_outer(self, key)
    680                 k = k.data
    681             if not isinstance(k, BASIC_INDEXING_TYPES):
--> 682                 k = np.asarray(k)
    683                 if k.size == 0:
    684                     # Slice by empty list; numpy could not infer the dtype

~/miniconda3/envs/dcpy_old_dask/lib/python3.7/site-packages/numpy/core/_asarray.py in asarray(a, dtype, order)
     83 
     84     """
---> 85     return array(a, dtype, copy=False, order=order)
     86 
     87 

~/miniconda3/envs/dcpy_old_dask/lib/python3.7/site-packages/dask/array/core.py in __array__(self, dtype, **kwargs)
   1374 
   1375     def __array__(self, dtype=None, **kwargs):
-> 1376         x = self.compute()
   1377         if dtype and x.dtype != dtype:
   1378             x = x.astype(dtype)

~/miniconda3/envs/dcpy_old_dask/lib/python3.7/site-packages/dask/base.py in compute(self, **kwargs)
    165         dask.base.compute
    166         """
--> 167         (result,) = compute(self, traverse=False, **kwargs)
    168         return result
    169 

~/miniconda3/envs/dcpy_old_dask/lib/python3.7/site-packages/dask/base.py in compute(*args, **kwargs)
    450         postcomputes.append(x.__dask_postcompute__())
    451 
--> 452     results = schedule(dsk, keys, **kwargs)
    453     return repack([f(r, *a) for r, (f, a) in zip(results, postcomputes)])
    454 

/project/mrgoodbar/dcherian/python/xarray/xarray/tests/__init__.py in __call__(self, dsk, keys, **kwargs)
    112             raise RuntimeError(
    113                 "Too many computes. Total: %d > max: %d."
--> 114                 % (self.total_computes, self.max_computes)
    115             )
    116         return dask.get(dsk, keys, **kwargs)

RuntimeError: Too many computes. Total: 2 > max: 1.
```

So here it looks like we don't support indexing by dask arrays, so as we loop through the dataset the `indexer` array gets computed each time. As you say, this should be avoided.
I commented too soon.

```
ds.sel(x=ds.a.data)
```

only computes once (!) so there's something else going on possibly
I think the solution is to handle this case (dask-backed DataArray) in `_isel_fancy` in `dataset.py`.
> I like using our raise_if_dask_computes context since it points out where the compute is happening

Oo nice, great to know about that.

> This looks like a duplicate of #2801. If you agree, can we move the conversation there?

Defining a general strategy for handling unknown chunk sizes seems like a good umbrella for it.  I would certainly mention the multiple executions though, that seems somewhat orthogonal.

Have there been prior discussions about the fact that dask doesn't support consecutive slicing operations well (i.e. applying filters one after the other)?  I am wondering what the thinking is on how far off that is in dask vs simply trying to support the current behavior well.  I.e. maybe forcing evaluation of indexer arrays is the practical solution for the foreseeable future if xarray didn't do so more than once.
From @alimanfoo in #5054

-------------------------

I have a dataset comprising several variables. All variables are dask arrays (e.g., backed by zarr). I would like to use one of these variables, which is a 1d boolean array, to index the other variables along a large single dimension. The boolean indexing array is about ~40 million items long, with ~20 million true values.

If I do this all via dask (i.e., not using xarray) then I can index one dask array with another dask array via fancy indexing. The indexing array is not loaded into memory or computed. If I need to know the shape and chunks of the resulting arrays I can call compute_chunk_sizes(), but still very little memory is required.

If I do this via xarray.Dataset.isel() then a substantial amount of memory (several GB) is allocated during isel() and retained. This is problematic as in a real-world use case there are many arrays to be indexed and memory runs out on standard systems.

There is a follow-on issue which is if I then want to run a computation over one of the indexed arrays, if the indexing was done via xarray then that leads to a further blow-up of multiple GB of memory usage, if using dask distributed cluster.

I think the underlying issue here is that the indexing array is loaded into memory, and then gets copied multiple times when the dask graph is constructed. If using a distributed scheduler, further copies get made during scheduling of any subsequent computation.

I made a notebook which illustrates the increased memory usage during Dataset.isel() here:
[colab.research.google.com/drive/1bn7Sj0An7TehwltWizU8j_l2OvPeoJyo?usp=sharing](
https://colab.research.google.com/drive/1bn7Sj0An7TehwltWizU8j_l2OvPeoJyo?usp=sharing)

This is possibly the same underlying issue (and use case) as raised by @eric-czech in #4663, so feel free to close this if you think it's a duplicate.
I would start by trying to fix 

``` python
import dask.array as da
import numpy as np
from xarray.tests import raise_if_dask_computes

with raise_if_dask_computes(max_computes=0):
    ds = xr.Dataset(
        dict(
            a=("x", da.from_array(np.random.randint(0, 100, 100))),
            b=(("x", "y"), da.random.random((100, 10))),
        )
    )
    ds.b.sel(x=ds.a.data)
```

specifically this `np.asarray` call
```
~/work/python/xarray/xarray/core/dataset.py in _validate_indexers(self, indexers, missing_dims)
   1960                 yield k, np.empty((0,), dtype="int64")
   1961             else:
-> 1962                 v = np.asarray(v)
   1963 
   1964                 if v.dtype.kind in "US":
```

Then the next issue is the multiple computes that happen when we pass a `DataArray` instead of a `dask.array` (`ds.b.sel(x=ds.a)`)
In general, I guess we can only support this for unindexed dimensions since we wouldn't know what index labels to use without computing the indexer array. This seems to be the case in that colab notebook.
Thanks @dcherian.

Just to add that if we make progress with supporting indexing with dask arrays then at some point I think we'll hit a separate issue, which is that xarray will require that the chunk sizes of the indexed arrays are computed, but currently calling the dask array method `compute_chunk_sizes()` is inefficient for n-d arrays. Raised here: https://github.com/dask/dask/issues/7416

In case anyone needs a workaround for indexing a dataset with a 1d boolean dask array, I'm currently using [this hacked implementation](https://github.com/malariagen/malariagen-data-python/blob/e39dac2404f8f8c37449169bee0f61dd9c6fcb8c/malariagen_data/util.py#L129) of a compress() style function that operates on an xarray dataset, which includes more efficient computation of chunk sizes. 
> currently calling the dask array method compute_chunk_sizes() is inefficient for n-d arrays. Raised here: dask/dask#7416

ouch. thanks for raising that issue.

> I'm currently using this hacked implementation of a compress() style function that operates on an xarray dataset, which includes more efficient computation of chunk sizes.

I think we'd be open to adding a `.compress` for Dataset, DataArray, & Variable without the chunks hack :)





Fix (patch):
diff --git a/doc/whats-new.rst b/doc/whats-new.rst
index 0266b2dc0f5..1dcdd25ccea 100644
--- a/doc/whats-new.rst
+++ b/doc/whats-new.rst
@@ -25,6 +25,9 @@ New Features
 
 - Fix :py:meth:`xr.cov` and :py:meth:`xr.corr` now support complex valued arrays  (:issue:`7340`, :pull:`7392`).
   By `Michael Niklas <https://github.com/headtr1ck>`_.
+- Allow indexing along unindexed dimensions with dask arrays
+  (:issue:`2511`, :issue:`4276`, :issue:`4663`, :pull:`5873`).
+  By `Abel Aoun <https://github.com/bzah>`_ and `Deepak Cherian <https://github.com/dcherian>`_.
 - Support dask arrays in ``first`` and ``last`` reductions.
   By `Deepak Cherian <https://github.com/dcherian>`_.
 
diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py
index 82d5ef088a4..0bd335f3f0a 100644
--- a/xarray/core/dataset.py
+++ b/xarray/core/dataset.py
@@ -73,7 +73,7 @@
 )
 from xarray.core.missing import get_clean_interp_index
 from xarray.core.options import OPTIONS, _get_keep_attrs
-from xarray.core.pycompat import array_type, is_duck_dask_array
+from xarray.core.pycompat import array_type, is_duck_array, is_duck_dask_array
 from xarray.core.types import QuantileMethods, T_Dataset
 from xarray.core.utils import (
     Default,
@@ -2292,7 +2292,8 @@ def _validate_indexers(
             elif isinstance(v, Sequence) and len(v) == 0:
                 yield k, np.empty((0,), dtype="int64")
             else:
-                v = np.asarray(v)
+                if not is_duck_array(v):
+                    v = np.asarray(v)
 
                 if v.dtype.kind in "US":
                     index = self._indexes[k].to_pandas_index()
diff --git a/xarray/core/indexing.py b/xarray/core/indexing.py
index becf1554453..e39c8fe49ea 100644
--- a/xarray/core/indexing.py
+++ b/xarray/core/indexing.py
@@ -17,7 +17,12 @@
 from xarray.core import duck_array_ops
 from xarray.core.nputils import NumpyVIndexAdapter
 from xarray.core.options import OPTIONS
-from xarray.core.pycompat import array_type, integer_types, is_duck_dask_array
+from xarray.core.pycompat import (
+    array_type,
+    integer_types,
+    is_duck_array,
+    is_duck_dask_array,
+)
 from xarray.core.types import T_Xarray
 from xarray.core.utils import (
     NDArrayMixin,
@@ -368,17 +373,17 @@ def __init__(self, key):
                 k = int(k)
             elif isinstance(k, slice):
                 k = as_integer_slice(k)
-            elif isinstance(k, np.ndarray):
+            elif is_duck_array(k):
                 if not np.issubdtype(k.dtype, np.integer):
                     raise TypeError(
                         f"invalid indexer array, does not have integer dtype: {k!r}"
                     )
-                if k.ndim != 1:
+                if k.ndim > 1:
                     raise TypeError(
-                        f"invalid indexer array for {type(self).__name__}; must have "
-                        f"exactly 1 dimension: {k!r}"
+                        f"invalid indexer array for {type(self).__name__}; must be scalar "
+                        f"or have 1 dimension: {k!r}"
                     )
-                k = np.asarray(k, dtype=np.int64)
+                k = k.astype(np.int64)
             else:
                 raise TypeError(
                     f"unexpected indexer type for {type(self).__name__}: {k!r}"
@@ -409,7 +414,13 @@ def __init__(self, key):
         for k in key:
             if isinstance(k, slice):
                 k = as_integer_slice(k)
-            elif isinstance(k, np.ndarray):
+            elif is_duck_dask_array(k):
+                raise ValueError(
+                    "Vectorized indexing with Dask arrays is not supported. "
+                    "Please pass a numpy array by calling ``.compute``. "
+                    "See https://github.com/dask/dask/issues/8958."
+                )
+            elif is_duck_array(k):
                 if not np.issubdtype(k.dtype, np.integer):
                     raise TypeError(
                         f"invalid indexer array, does not have integer dtype: {k!r}"
@@ -422,7 +433,7 @@ def __init__(self, key):
                         "invalid indexer key: ndarray arguments "
                         f"have different numbers of dimensions: {ndims}"
                     )
-                k = np.asarray(k, dtype=np.int64)
+                k = k.astype(np.int64)
             else:
                 raise TypeError(
                     f"unexpected indexer type for {type(self).__name__}: {k!r}"
@@ -1351,8 +1362,9 @@ def __getitem__(self, key):
             rewritten_indexer = False
             new_indexer = []
             for idim, k in enumerate(key.tuple):
-                if isinstance(k, Iterable) and duck_array_ops.array_equiv(
-                    k, np.arange(self.array.shape[idim])
+                if isinstance(k, Iterable) and (
+                    not is_duck_dask_array(k)
+                    and duck_array_ops.array_equiv(k, np.arange(self.array.shape[idim]))
                 ):
                     new_indexer.append(slice(None))
                     rewritten_indexer = True
diff --git a/xarray/core/nputils.py b/xarray/core/nputils.py
index 2bc413dc21f..9e17ab93e80 100644
--- a/xarray/core/nputils.py
+++ b/xarray/core/nputils.py
@@ -7,6 +7,7 @@
 from numpy.core.multiarray import normalize_axis_index  # type: ignore[attr-defined]
 
 from xarray.core.options import OPTIONS
+from xarray.core.pycompat import is_duck_array
 
 try:
     import bottleneck as bn
@@ -121,7 +122,10 @@ def _advanced_indexer_subspaces(key):
         return (), ()
 
     non_slices = [k for k in key if not isinstance(k, slice)]
-    ndim = len(np.broadcast(*non_slices).shape)
+    broadcasted_shape = np.broadcast_shapes(
+        *[item.shape if is_duck_array(item) else (0,) for item in non_slices]
+    )
+    ndim = len(broadcasted_shape)
     mixed_positions = advanced_index_positions[0] + np.arange(ndim)
     vindex_positions = np.arange(ndim)
     return mixed_positions, vindex_positions
diff --git a/xarray/core/pycompat.py b/xarray/core/pycompat.py
index 95387523bc8..4a3f3638d14 100644
--- a/xarray/core/pycompat.py
+++ b/xarray/core/pycompat.py
@@ -7,7 +7,7 @@
 import numpy as np
 from packaging.version import Version
 
-from xarray.core.utils import is_duck_array, module_available
+from xarray.core.utils import is_duck_array, is_scalar, module_available
 
 integer_types = (int, np.integer)
 
@@ -79,3 +79,7 @@ def is_dask_collection(x):
 
 def is_duck_dask_array(x):
     return is_duck_array(x) and is_dask_collection(x)
+
+
+def is_0d_dask_array(x):
+    return is_duck_dask_array(x) and is_scalar(x)
diff --git a/xarray/core/variable.py b/xarray/core/variable.py
index 831ece25b67..1c436d49b1d 100644
--- a/xarray/core/variable.py
+++ b/xarray/core/variable.py
@@ -26,7 +26,12 @@
     as_indexable,
 )
 from xarray.core.options import OPTIONS, _get_keep_attrs
-from xarray.core.pycompat import array_type, integer_types, is_duck_dask_array
+from xarray.core.pycompat import (
+    array_type,
+    integer_types,
+    is_0d_dask_array,
+    is_duck_dask_array,
+)
 from xarray.core.utils import (
     Frozen,
     NdimSizeLenMixin,
@@ -687,11 +692,12 @@ def _broadcast_indexes(self, key):
         key = self._item_key_to_tuple(key)  # key is a tuple
         # key is a tuple of full size
         key = indexing.expanded_indexer(key, self.ndim)
-        # Convert a scalar Variable to an integer
+        # Convert a scalar Variable to a 0d-array
         key = tuple(
-            k.data.item() if isinstance(k, Variable) and k.ndim == 0 else k for k in key
+            k.data if isinstance(k, Variable) and k.ndim == 0 else k for k in key
         )
-        # Convert a 0d-array to an integer
+        # Convert a 0d numpy arrays to an integer
+        # dask 0d arrays are passed through
         key = tuple(
             k.item() if isinstance(k, np.ndarray) and k.ndim == 0 else k for k in key
         )
@@ -732,7 +738,8 @@ def _validate_indexers(self, key):
         for dim, k in zip(self.dims, key):
             if not isinstance(k, BASIC_INDEXING_TYPES):
                 if not isinstance(k, Variable):
-                    k = np.asarray(k)
+                    if not is_duck_array(k):
+                        k = np.asarray(k)
                     if k.ndim > 1:
                         raise IndexError(
                             "Unlabeled multi-dimensional array cannot be "
@@ -749,6 +756,13 @@ def _validate_indexers(self, key):
                             "{}-dimensional boolean indexing is "
                             "not supported. ".format(k.ndim)
                         )
+                    if is_duck_dask_array(k.data):
+                        raise KeyError(
+                            "Indexing with a boolean dask array is not allowed. "
+                            "This will result in a dask array of unknown shape. "
+                            "Such arrays are unsupported by Xarray."
+                            "Please compute the indexer first using .compute()"
+                        )
                     if getattr(k, "dims", (dim,)) != (dim,):
                         raise IndexError(
                             "Boolean indexer should be unlabeled or on the "
@@ -759,10 +773,11 @@ def _validate_indexers(self, key):
                         )
 
     def _broadcast_indexes_outer(self, key):
+        # drop dim if k is integer or if k is a 0d dask array
         dims = tuple(
             k.dims[0] if isinstance(k, Variable) else dim
             for k, dim in zip(key, self.dims)
-            if not isinstance(k, integer_types)
+            if (not isinstance(k, integer_types) and not is_0d_dask_array(k))
         )
 
         new_key = []
@@ -770,7 +785,8 @@ def _broadcast_indexes_outer(self, key):
             if isinstance(k, Variable):
                 k = k.data
             if not isinstance(k, BASIC_INDEXING_TYPES):
-                k = np.asarray(k)
+                if not is_duck_array(k):
+                    k = np.asarray(k)
                 if k.size == 0:
                     # Slice by empty list; numpy could not infer the dtype
                     k = k.astype(int)