Repository: pandas-dev/pandas

Issue:
rolling( window='10D') does not work for df with MultiIndex

```python
# Your code here

tdf.rolling( '10D' ).mean()
```

```
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-49-707b0ff61efc> in <module>()
     10 tdf = df.groupby(level=1).get_group( 64413 )
     11 
---> 12 tdf.rolling( '10D' ).mean()
     13 
     14 tdf.reset_index().set_index('time').rolling( '10D' ).mean()

/home/firdaus/.conda/envs/tsconda/lib/python3.5/site-packages/pandas/core/generic.py in rolling(self, window, min_periods, freq, center, win_type, on, axis)
   5502                                    min_periods=min_periods, freq=freq,
   5503                                    center=center, win_type=win_type,
-> 5504                                    on=on, axis=axis)
   5505 
   5506         cls.rolling = rolling

/home/firdaus/.conda/envs/tsconda/lib/python3.5/site-packages/pandas/core/window.py in rolling(obj, win_type, **kwds)
   1797         return Window(obj, win_type=win_type, **kwds)
   1798 
-> 1799     return Rolling(obj, **kwds)
   1800 
   1801 

/home/firdaus/.conda/envs/tsconda/lib/python3.5/site-packages/pandas/core/window.py in __init__(self, obj, window, min_periods, freq, center, win_type, axis, on, **kwargs)
     76         self.win_type = win_type
     77         self.axis = obj._get_axis_number(axis) if axis is not None else None
---> 78         self.validate()
     79 
     80     @property

/home/firdaus/.conda/envs/tsconda/lib/python3.5/site-packages/pandas/core/window.py in validate(self)
   1055 
   1056         elif not is_integer(self.window):
-> 1057             raise ValueError("window must be an integer")
   1058         elif self.window < 0:
   1059             raise ValueError("window must be non-negative")

ValueError: window must be an integer
```


#### Problem description

The offset feature of specifying timelike windows in 'rolling' doesn't work if the dataframe has multindex with level_0 = 'time' and level_1 = something else.


#### Expected Output
``` python
tdf.reset_index().set_index('time').rolling( '10D' ).mean()
```
Works correctly.

#### Output of ``pd.show_versions()``

<details>
INSTALLED VERSIONS
------------------
commit: None
python: 3.5.2.final.0
python-bits: 64
OS: Linux
OS-release: 3.16.0-0.bpo.4-amd64
machine: x86_64
processor: 
byteorder: little
LC_ALL: None
LANG: en_US.UTF-8
LOCALE: en_US.UTF-8

pandas: 0.19.2.post+ts3
nose: None
pip: 9.0.1
setuptools: 27.2.0
Cython: 0.25.2
numpy: 1.11.3
scipy: 0.18.1
statsmodels: 0.6.1
xarray: None
IPython: 5.1.0
sphinx: 1.5.1
patsy: 0.4.1
dateutil: 2.6.0
pytz: 2016.10
blosc: None
bottleneck: 1.2.0
tables: 3.3.0
numexpr: 2.6.1
matplotlib: 1.5.3
openpyxl: 2.4.0
xlrd: 1.0.0
xlwt: None
xlsxwriter: 0.9.6
lxml: None
bs4: 4.5.3
html5lib: None
httplib2: None
apiclient: None
sqlalchemy: 1.1.4
pymysql: None
psycopg2: None
jinja2: 2.9.4
boto: None
pandas_datareader: None
</details>



Discussion:
can you show your frame construction itself.
I suspect you are doing something like this

```
In [7]: df = pd.DataFrame({'value': range(60)},index=pd.MultiIndex.from_product([range(6),pd.date_range('20160101',periods=10)],names=['one','two']))

In [4]: df.rolling(window='10d').sum()
ValueError: window must be an integer

In [5]: df.rolling(window='10d',level='two').sum()
```

So I thought we had an issue about this, but guess not. We need to:

- accept a ``level`` argument
- have ``on`` interpret as per https://github.com/pandas-dev/pandas/pull/14432
```python
            time_index = pd.date_range( start, end, freq='1D',  )
            mi = pd.MultiIndex( levels=[time_index, [0]],
                             labels=[np.arange( len( time_index ) ), np.zeros( len( time_index ) )],
                             names=['time', 'int_index'] )
```

Resolving patch:
diff --git a/pandas/core/window/rolling.py b/pandas/core/window/rolling.py
--- a/pandas/core/window/rolling.py
+++ b/pandas/core/window/rolling.py
@@ -70,7 +70,7 @@ def __init__(
         center: Optional[bool] = False,
         win_type: Optional[str] = None,
         axis: Axis = 0,
-        on: Optional[str] = None,
+        on: Optional[Union[str, Index]] = None,
         closed: Optional[str] = None,
         **kwargs
     ):
@@ -126,7 +126,7 @@ def _create_blocks(self):
         obj = self._selected_obj
 
         # filter out the on from the object
-        if self.on is not None:
+        if self.on is not None and not isinstance(self.on, Index):
             if obj.ndim == 2:
                 obj = obj.reindex(columns=obj.columns.difference([self.on]), copy=False)
         blocks = obj._to_dict_of_blocks(copy=False).values()
@@ -637,10 +637,10 @@ class Window(_Window):
         Provide a window type. If ``None``, all points are evenly weighted.
         See the notes below for further information.
     on : str, optional
-        For a DataFrame, a datetime-like column on which to calculate the rolling
-        window, rather than the DataFrame's index. Provided integer column is
-        ignored and excluded from result since an integer index is not used to
-        calculate the rolling window.
+        For a DataFrame, a datetime-like column or MultiIndex level on which
+        to calculate the rolling window, rather than the DataFrame's index.
+        Provided integer column is ignored and excluded from result since
+        an integer index is not used to calculate the rolling window.
     axis : int or str, default 0
     closed : str, default None
         Make the interval closed on the 'right', 'left', 'both' or
@@ -1651,18 +1651,19 @@ def is_datetimelike(self):
 
     @cache_readonly
     def _on(self):
-
         if self.on is None:
             if self.axis == 0:
                 return self.obj.index
             elif self.axis == 1:
                 return self.obj.columns
+        elif isinstance(self.on, Index):
+            return self.on
         elif isinstance(self.obj, ABCDataFrame) and self.on in self.obj.columns:
             return Index(self.obj[self.on])
         else:
             raise ValueError(
                 "invalid on specified as {0}, "
-                "must be a column (if DataFrame) "
+                "must be a column (of DataFrame), an Index "
                 "or None".format(self.on)
             )
 
@@ -1706,10 +1707,12 @@ def validate(self):
 
     def _validate_monotonic(self):
         """
-        Validate on is_monotonic.
+        Validate monotonic (increasing or decreasing).
         """
-        if not self._on.is_monotonic:
-            formatted = self.on or "index"
+        if not (self._on.is_monotonic_increasing or self._on.is_monotonic_decreasing):
+            formatted = self.on
+            if self.on is None:
+                formatted = "index"
             raise ValueError("{0} must be monotonic".format(formatted))
 
     def _validate_freq(self):


Test patch:
diff --git a/pandas/tests/window/test_timeseries_window.py b/pandas/tests/window/test_timeseries_window.py
--- a/pandas/tests/window/test_timeseries_window.py
+++ b/pandas/tests/window/test_timeseries_window.py
@@ -1,7 +1,15 @@
 import numpy as np
 import pytest
 
-from pandas import DataFrame, Index, Series, Timestamp, date_range, to_datetime
+from pandas import (
+    DataFrame,
+    Index,
+    MultiIndex,
+    Series,
+    Timestamp,
+    date_range,
+    to_datetime,
+)
 import pandas.util.testing as tm
 
 import pandas.tseries.offsets as offsets
@@ -105,8 +113,16 @@ def test_monotonic_on(self):
         assert df.index.is_monotonic
         df.rolling("2s").sum()
 
-        # non-monotonic
-        df.index = reversed(df.index.tolist())
+    def test_non_monotonic_on(self):
+        # GH 19248
+        df = DataFrame(
+            {"A": date_range("20130101", periods=5, freq="s"), "B": range(5)}
+        )
+        df = df.set_index("A")
+        non_monotonic_index = df.index.to_list()
+        non_monotonic_index[0] = non_monotonic_index[3]
+        df.index = non_monotonic_index
+
         assert not df.index.is_monotonic
 
         with pytest.raises(ValueError):
@@ -690,3 +706,34 @@ def test_rolling_cov_offset(self):
 
         expected2 = ss.rolling(3, min_periods=1).cov()
         tm.assert_series_equal(result, expected2)
+
+    def test_rolling_on_decreasing_index(self):
+        # GH-19248
+        index = [
+            Timestamp("20190101 09:00:00"),
+            Timestamp("20190101 09:00:02"),
+            Timestamp("20190101 09:00:03"),
+            Timestamp("20190101 09:00:05"),
+            Timestamp("20190101 09:00:06"),
+        ]
+
+        df = DataFrame({"column": [3, 4, 4, 2, 1]}, index=reversed(index))
+        result = df.rolling("2s").min()
+        expected = DataFrame(
+            {"column": [3.0, 3.0, 3.0, 2.0, 1.0]}, index=reversed(index)
+        )
+        tm.assert_frame_equal(result, expected)
+
+    def test_rolling_on_multi_index_level(self):
+        # GH-15584
+        df = DataFrame(
+            {"column": range(6)},
+            index=MultiIndex.from_product(
+                [date_range("20190101", periods=3), range(2)], names=["date", "seq"]
+            ),
+        )
+        result = df.rolling("10d", on=df.index.get_level_values("date")).sum()
+        expected = DataFrame(
+            {"column": [0.0, 1.0, 3.0, 6.0, 10.0, 15.0]}, index=df.index
+        )
+        tm.assert_frame_equal(result, expected)