Repository: pydata/xarray

Issue:
Cannot use xarrays own times for indexing
I need to get the first Δt from the start of my dataset, i.e. `ds.sel(start_time, start_time + timedelta)`.  However, due to pandas using `M8[ns]` but datetime.datetime not supporting this, the index gets converted to an `int` and indexing fails.  Inspection tells me that by the time the index reaches `pandas` it is already an int.  This is ultimately due to the [numpy problem](https://github.com/numpy/numpy/issues/8546) that `timedelta64(0, 'ns').item()` is an `int`, but it would be very nice if `xarray` had a workaround so that we can use indexing such as shown below.  

```
In [282]: time = pd.date_range('2000-01-01', freq='H', periods=365 * 24)

In [283]: ds = xarray.Dataset({'foo': ('time', np.arange(365 * 24)), 'time': time})

In [284]: ds.sel(time=slice(ds["time"][0], ds["time"][10]))
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-284-a101e126e1b0> in <module>()
----> 1 ds.sel(time=slice(ds["time"][0], ds["time"][10]))

/dev/shm/gerrit/venv/stable-3.5/lib/python3.5/site-packages/xarray/core/dataset.py in sel(self, method, tolerance, drop, **indexers)
   1180         """
   1181         pos_indexers, new_indexes = indexing.remap_label_indexers(
-> 1182             self, indexers, method=method, tolerance=tolerance
   1183         )
   1184         result = self.isel(drop=drop, **pos_indexers)

/dev/shm/gerrit/venv/stable-3.5/lib/python3.5/site-packages/xarray/core/indexing.py in remap_label_indexers(data_obj, indexers, method, tolerance)
    286         else:
    287             idxr, new_idx = convert_label_indexer(index, label,
--> 288                                                   dim, method, tolerance)
    289             pos_indexers[dim] = idxr
    290             if new_idx is not None:

/dev/shm/gerrit/venv/stable-3.5/lib/python3.5/site-packages/xarray/core/indexing.py in convert_label_indexer(index, label, index_name, method, tolerance)
    183         indexer = index.slice_indexer(_try_get_item(label.start),
    184                                       _try_get_item(label.stop),
--> 185                                       _try_get_item(label.step))
    186         if not isinstance(indexer, slice):
    187             # unlike pandas, in xarray we never want to silently convert a slice

/dev/shm/gerrit/venv/stable-3.5/lib/python3.5/site-packages/pandas/tseries/index.py in slice_indexer(self, start, end, step, kind)
   1496 
   1497         try:
-> 1498             return Index.slice_indexer(self, start, end, step, kind=kind)
   1499         except KeyError:
   1500             # For historical reasons DatetimeIndex by default supports

/dev/shm/gerrit/venv/stable-3.5/lib/python3.5/site-packages/pandas/indexes/base.py in slice_indexer(self, start, end, step, kind)
   2995         """
   2996         start_slice, end_slice = self.slice_locs(start, end, step=step,
-> 2997                                                  kind=kind)
   2998 
   2999         # return a slice

/dev/shm/gerrit/venv/stable-3.5/lib/python3.5/site-packages/pandas/indexes/base.py in slice_locs(self, start, end, step, kind)
   3174         start_slice = None
   3175         if start is not None:
-> 3176             start_slice = self.get_slice_bound(start, 'left', kind)
   3177         if start_slice is None:
   3178             start_slice = 0

/dev/shm/gerrit/venv/stable-3.5/lib/python3.5/site-packages/pandas/indexes/base.py in get_slice_bound(self, label, side, kind)
   3113         # For datetime indices label may be a string that has to be converted
   3114         # to datetime boundary according to its resolution.
-> 3115         label = self._maybe_cast_slice_bound(label, side, kind)
   3116 
   3117         # we need to look up the label

/dev/shm/gerrit/venv/stable-3.5/lib/python3.5/site-packages/pandas/tseries/index.py in _maybe_cast_slice_bound(self, label, side, kind)
   1444 
   1445         if is_float(label) or isinstance(label, time) or is_integer(label):
-> 1446             self._invalid_indexer('slice', label)
   1447 
   1448         if isinstance(label, compat.string_types):

/dev/shm/gerrit/venv/stable-3.5/lib/python3.5/site-packages/pandas/indexes/base.py in _invalid_indexer(self, form, key)
   1282                         "indexers [{key}] of {kind}".format(
   1283                             form=form, klass=type(self), key=key,
-> 1284                             kind=type(key)))
   1285 
   1286     def get_duplicates(self):

TypeError: cannot do slice indexing on <class 'pandas.tseries.index.DatetimeIndex'> with these indexers [946684800000000000] of <class 'int'>
```


Discussion:

Patch:
diff --git a/asv_bench/benchmarks/rolling.py b/asv_bench/benchmarks/rolling.py
index 52814ad3..3f2a3810 100644
--- a/asv_bench/benchmarks/rolling.py
+++ b/asv_bench/benchmarks/rolling.py
@@ -8,27 +8,44 @@ import xarray as xr
 from . import parameterized, randn, requires_dask
 
 nx = 3000
+long_nx = 30000000
 ny = 2000
 nt = 1000
 window = 20
 
+randn_xy = randn((nx, ny), frac_nan=0.1)
+randn_xt = randn((nx, nt))
+randn_t = randn((nt, ))
+randn_long = randn((long_nx, ), frac_nan=0.1)
+
 
 class Rolling(object):
     def setup(self, *args, **kwargs):
         self.ds = xr.Dataset(
-            {'var1': (('x', 'y'), randn((nx, ny), frac_nan=0.1)),
-             'var2': (('x', 't'), randn((nx, nt))),
-             'var3': (('t', ), randn(nt))},
+            {'var1': (('x', 'y'), randn_xy),
+             'var2': (('x', 't'), randn_xt),
+             'var3': (('t', ), randn_t)},
             coords={'x': np.arange(nx),
                     'y': np.linspace(0, 1, ny),
                     't': pd.date_range('1970-01-01', periods=nt, freq='D'),
                     'x_coords': ('x', np.linspace(1.1, 2.1, nx))})
+        self.da_long = xr.DataArray(randn_long, dims='x',
+                                    coords={'x': np.arange(long_nx) * 0.1})
 
     @parameterized(['func', 'center'],
                    (['mean', 'count'], [True, False]))
     def time_rolling(self, func, center):
         getattr(self.ds.rolling(x=window, center=center), func)()
 
+    @parameterized(['func', 'pandas'],
+                   (['mean', 'count'], [True, False]))
+    def time_rolling_long(self, func, pandas):
+        if pandas:
+            se = self.da_long.to_series()
+            getattr(se.rolling(window=window), func)()
+        else:
+            getattr(self.da_long.rolling(x=window), func)()
+
     @parameterized(['window_', 'min_periods'],
                    ([20, 40], [5, None]))
     def time_rolling_np(self, window_, min_periods):
@@ -47,3 +64,4 @@ class RollingDask(Rolling):
         requires_dask()
         super(RollingDask, self).setup(**kwargs)
         self.ds = self.ds.chunk({'x': 100, 'y': 50, 't': 50})
+        self.da_long = self.da_long.chunk({'x': 10000})
diff --git a/doc/whats-new.rst b/doc/whats-new.rst
index 40e206aa..b28beb9e 100644
--- a/doc/whats-new.rst
+++ b/doc/whats-new.rst
@@ -37,9 +37,17 @@ Documentation
 Enhancements
 ~~~~~~~~~~~~
 
+ - Some speed improvement to construct :py:class:`~xarray.DataArrayRolling`
+ object (:issue:`1993`)
+ By `Keisuke Fujii <https://github.com/fujiisoup>`_.
+
 Bug fixes
 ~~~~~~~~~
 
+- Fixed labeled indexing with slice bounds given by xarray objects with
+  datetime64 or timedelta64 dtypes (:issue:`1240`).
+  By `Stephan Hoyer <https://github.com/shoyer>`_.
+
 .. _whats-new.0.10.2:
 
 v0.10.2 (13 March 2018)
diff --git a/xarray/core/indexing.py b/xarray/core/indexing.py
index f7477a3e..2c1f0837 100644
--- a/xarray/core/indexing.py
+++ b/xarray/core/indexing.py
@@ -48,11 +48,25 @@ def _expand_slice(slice_, size):
     return np.arange(*slice_.indices(size))
 
 
-def _try_get_item(x):
-    try:
-        return x.item()
-    except AttributeError:
-        return x
+def _sanitize_slice_element(x):
+    from .variable import Variable
+    from .dataarray import DataArray
+
+    if isinstance(x, (Variable, DataArray)):
+        x = x.values
+
+    if isinstance(x, np.ndarray):
+        if x.ndim != 0:
+            raise ValueError('cannot use non-scalar arrays in a slice for '
+                             'xarray indexing: {}'.format(x))
+        x = x[()]
+
+    if isinstance(x, np.timedelta64):
+        # pandas does not support indexing with np.timedelta64 yet:
+        # https://github.com/pandas-dev/pandas/issues/20393
+        x = pd.Timedelta(x)
+
+    return x
 
 
 def _asarray_tuplesafe(values):
@@ -119,9 +133,9 @@ def convert_label_indexer(index, label, index_name='', method=None,
             raise NotImplementedError(
                 'cannot use ``method`` argument if any indexers are '
                 'slice objects')
-        indexer = index.slice_indexer(_try_get_item(label.start),
-                                      _try_get_item(label.stop),
-                                      _try_get_item(label.step))
+        indexer = index.slice_indexer(_sanitize_slice_element(label.start),
+                                      _sanitize_slice_element(label.stop),
+                                      _sanitize_slice_element(label.step))
         if not isinstance(indexer, slice):
             # unlike pandas, in xarray we never want to silently convert a
             # slice indexer into an array indexer
diff --git a/xarray/core/rolling.py b/xarray/core/rolling.py
index fb09c9e0..079c60f3 100644
--- a/xarray/core/rolling.py
+++ b/xarray/core/rolling.py
@@ -151,34 +151,21 @@ class DataArrayRolling(Rolling):
         """
         super(DataArrayRolling, self).__init__(obj, min_periods=min_periods,
                                                center=center, **windows)
-        self.window_indices = None
-        self.window_labels = None
 
-        self._setup_windows()
+        self.window_labels = self.obj[self.dim]
 
     def __iter__(self):
-        for (label, indices) in zip(self.window_labels, self.window_indices):
-            window = self.obj.isel(**{self.dim: indices})
+        stops = np.arange(1, len(self.window_labels) + 1)
+        starts = stops - int(self.window)
+        starts[:int(self.window)] = 0
+        for (label, start, stop) in zip(self.window_labels, starts, stops):
+            window = self.obj.isel(**{self.dim: slice(start, stop)})
 
             counts = window.count(dim=self.dim)
             window = window.where(counts >= self._min_periods)
 
             yield (label, window)
 
-    def _setup_windows(self):
-        """
-        Find the indices and labels for each window
-        """
-        self.window_labels = self.obj[self.dim]
-        window = int(self.window)
-        dim_size = self.obj[self.dim].size
-
-        stops = np.arange(dim_size) + 1
-        starts = np.maximum(stops - window, 0)
-
-        self.window_indices = [slice(start, stop)
-                               for start, stop in zip(starts, stops)]
-
     def construct(self, window_dim, stride=1, fill_value=dtypes.NA):
         """
         Convert this rolling object to xr.DataArray,


Test patch:
diff --git a/xarray/tests/test_dataarray.py b/xarray/tests/test_dataarray.py
index 059e93fc..3fd229cf 100644
--- a/xarray/tests/test_dataarray.py
+++ b/xarray/tests/test_dataarray.py
@@ -779,6 +779,22 @@ class TestDataArray(TestCase):
         assert 'new_dim' in actual.coords
         assert_equal(actual['new_dim'].drop('x'), ind['new_dim'])
 
+    def test_sel_invalid_slice(self):
+        array = DataArray(np.arange(10), [('x', np.arange(10))])
+        with raises_regex(ValueError, 'cannot use non-scalar arrays'):
+            array.sel(x=slice(array.x))
+
+    def test_sel_dataarray_datetime(self):
+        # regression test for GH1240
+        times = pd.date_range('2000-01-01', freq='D', periods=365)
+        array = DataArray(np.arange(365), [('time', times)])
+        result = array.sel(time=slice(array.time[0], array.time[-1]))
+        assert_equal(result, array)
+
+        array = DataArray(np.arange(365), [('delta', times - times[0])])
+        result = array.sel(delta=slice(array.delta[0], array.delta[-1]))
+        assert_equal(result, array)
+
     def test_sel_no_index(self):
         array = DataArray(np.arange(10), dims='x')
         assert_identical(array[0], array.sel(x=0))