title: xarray equivalent of pandas `qcut()` function
body: <p>I want to calculate the <a href="https://github.com/royalosyin/Calculate-Precipitation-based-Agricultural-Drought-Indices-with-Python/blob/master/" rel="nofollow noreferrer">Decile Index</a> - see the <code>ex1-Calculate Decile Index (DI) with Python.ipynb</code>.</p>
  
  <p>The <code>pandas</code> implementation is simple enough but I need help with applying the bin labels to a new <code>variable</code> / <code>coordinate</code> using the <code>groupby_bins()</code> functionality.</p>
  
  <h1>working example (test dataset)</h1>
  
  <pre><code>import pandas as pd
  import numpy as np
  import xarray as xr
  
  time = pd.date_range('2010-01-01','2011-12-31',freq='M')
  lat = np.linspace(-5.175003, -4.7250023, 10)
  lon = np.linspace(33.524994, 33.97499, 10)
  precip = np.random.normal(0, 1, size=(len(time), len(lat), len(lon)))
  
  ds = xr.Dataset(
      {'precip': (['time', 'lat', 'lon'], precip)},
      coords={
          'lon': lon,
          'lat': lat,
          'time': time,
      }
  )
  </code></pre>
  
  <p>This looks like:</p>
  
  <pre><code>Out[]:
  &lt;xarray.Dataset&gt;
  Dimensions:  (lat: 10, lon: 10, time: 24)
  Coordinates:
    * lon      (lon) float64 33.52 33.57 33.62 33.67 ... 33.82 33.87 33.92 33.97
    * lat      (lat) float64 -5.175 -5.125 -5.075 -5.025 ... -4.825 -4.775 -4.725
    * time     (time) datetime64[ns] 2010-01-31 2010-02-28 ... 2011-12-31
  Data variables:
      precip   (time, lat, lon) float64 0.1638 -1.031 0.2087 ... -0.1147 -0.6863
  </code></pre>
  
  <p>Calculating the cumulative frequency distribution (normalised rank)</p>
  
  <pre class="lang-py prettyprint-override"><code># calculate a cumsum over some window size
  rolling_window = 3
  ds_window = (
      ds.rolling(time=rolling_window, center=True)
      .sum()
      .dropna(dim='time', how='all')
  )
  # construct a cumulative frequency distribution ranking the precip values
  # per month
  def rank_norm(ds, dim='time'):
      return (ds.rank(dim=dim) - 1) / (ds.sizes[dim] - 1) * 100
  
  result = ds_window.groupby('time.month').apply(rank_norm, args=('time',))
  result = result.rename({variable:'rank_norm'}).drop('month')
  
  Out[]:
  &lt;xarray.Dataset&gt;
  Dimensions:    (lat: 10, lon: 10, time: 108)
  Coordinates:
    * lat        (lat) float64 -5.175 -5.125 -5.075 ... -4.825 -4.775 -4.725
    * lon        (lon) float64 33.52 33.57 33.62 33.67 ... 33.82 33.87 33.92 33.97
    * time       (time) datetime64[ns] 2010-01-31 2010-02-28 ... 2018-12-31
  Data variables:
      rank_norm  (time, lat, lon) float64 75.0 75.0 12.5 100.0 ... 87.5 0.0 25.0
  </code></pre>
  
  <h1>Pandas Solution</h1>
  
  <p>I want to create a variable which will create a new <code>variable</code> or <code>coordinate</code>
  in <code>ds</code> that will have the the integers corresponding to the bins from the <code>bins = [20., 40., 60., 80., np.Inf]</code>.</p>
  
  <p>Trying to do it in Pandas is relatively simple with the <code>.qcut</code> functionality.</p>
  
  <pre class="lang-py prettyprint-override"><code>test = result.to_dataframe()
  bins = pd.qcut(test['rank_norm'], 5, labels=[1, 2, 3, 4, 5])
  result = bins.to_xarray().to_dataset().rename({'rank_norm': 'rank_bins'})
  
  Out[]:
  &lt;xarray.Dataset&gt;
  Dimensions:   (lat: 10, lon: 10, time: 108)
  Coordinates:
    * lat       (lat) float64 -5.175 -5.125 -5.075 -5.025 ... -4.825 -4.775 -4.725
    * lon       (lon) float64 33.52 33.57 33.62 33.67 ... 33.82 33.87 33.92 33.97
    * time      (time) datetime64[ns] 2010-01-31 2010-02-28 ... 2018-12-31
  Data variables:
      rank_bins  (lat, lon, time) int64 4 4 1 4 3 4 5 1 1 2 ... 2 1 1 4 2 4 3 1 2 2
  
  </code></pre>
  
  <h1>My <code>xarray</code> attempt</h1>
  
  <pre class="lang-py prettyprint-override"><code># assign bins to variable xarray
  bins = [20., 40., 60., 80., np.Inf]
  decile_index_gpby = rank_norm.groupby_bins('rank_norm', bins=bins)
  out = decile_index_gpby.assign()  # assign_coords()
  </code></pre>
  
  <p>The error message I get is as follows:</p>
  
  <pre><code>---------------------------------------------------------------------------
  IndexError                                Traceback (most recent call last)
  &lt;ipython-input-166-8d48b9fc1d56&gt; in &lt;module&gt;
        1 bins = [20., 40., 60., 80., np.Inf]
        2 decile_index_gpby = rank_norm.groupby_bins('rank_norm', bins=bins)
  ----&gt; 3 out = decile_index_gpby.assign()  # assign_coords()
  
  ~/miniconda3/lib/python3.7/site-packages/xarray/core/groupby.py in assign(self, **kwargs)
      772         Dataset.assign
      773         """
  --&gt; 774         return self.apply(lambda ds: ds.assign(**kwargs))
      775
      776
  
  ~/miniconda3/lib/python3.7/site-packages/xarray/core/groupby.py in apply(self, func, args, **kwargs)
      684         kwargs.pop('shortcut', None)  # ignore shortcut if set (for now)
      685         applied = (func(ds, *args, **kwargs) for ds in self._iter_grouped())
  --&gt; 686         return self._combine(applied)
      687
      688     def _combine(self, applied):
  
  ~/miniconda3/lib/python3.7/site-packages/xarray/core/groupby.py in _combine(self, applied)
      691         coord, dim, positions = self._infer_concat_args(applied_example)
      692         combined = concat(applied, dim)
  --&gt; 693         combined = _maybe_reorder(combined, dim, positions)
      694         if coord is not None:
      695             combined[coord.name] = coord
  
  ~/miniconda3/lib/python3.7/site-packages/xarray/core/groupby.py in _maybe_reorder(xarray_obj, dim, positions)
      468
      469 def _maybe_reorder(xarray_obj, dim, positions):
  --&gt; 470     order = _inverse_permutation_indices(positions)
      471
      472     if order is None:
  
  ~/miniconda3/lib/python3.7/site-packages/xarray/core/groupby.py in _inverse_permutation_indices(positions)
      110         positions = [np.arange(sl.start, sl.stop, sl.step) for sl in positions]
      111
  --&gt; 112     indices = nputils.inverse_permutation(np.concatenate(positions))
      113     return indices
      114
  
  ~/miniconda3/lib/python3.7/site-packages/xarray/core/nputils.py in inverse_permutation(indices)
       58     # use intp instead of int64 because of windows :(
       59     inverse_permutation = np.empty(len(indices), dtype=np.intp)
  ---&gt; 60     inverse_permutation[indices] = np.arange(len(indices), dtype=np.intp)
       61     return inverse_permutation
       62
  
  IndexError: index 1304 is out of bounds for axis 0 with size 1000
  </code></pre>