Repository: pandas-dev/pandas

Issue:
Grouped timeseries operations fail on multi-indexed DataFrames
This might be a potential bug: doing grouped timeseries operations fails silently on a multi-indexed DataFrame.

``` python
    import pandas as pd
    import pandas.io.data as web

    # Get some market data
    df = web.DataReader(['AAPL', 'GOOG'], 'yahoo', pd.Timestamp('2013'), pd.Timestamp('2014')).to_frame()
    df.index.names = ('dt', 'symbol')

    In [21]: df.head()
    Out[21]: 
                            Open       High        Low      Close     Volume  \
    dt         symbol                                                          
    2013-01-02 AAPL    553.82001  555.00000  541.62994  549.03003  140129500   
    2013-01-03 AAPL    547.88000  549.67004  541.00000  542.10004   88241300   
    2013-01-04 AAPL    536.96997  538.63000  525.82996  527.00000  148583400   
    2013-01-07 AAPL    522.00000  529.30005  515.20001  523.90002  121039100   
    2013-01-08 AAPL    529.21002  531.89001  521.25000  525.31000  114676800   

                       Adj Close  
    dt         symbol             
    2013-01-02 AAPL     74.63931  
    2013-01-03 AAPL     73.69719  
    2013-01-04 AAPL     71.64438  
    2013-01-07 AAPL     71.22294  
    2013-01-08 AAPL     71.41463 
```

Let's say we want to resample this to monthly data. This fails and returns an empty DataFrame:

``` python
    df_M = df.groupby(level='symbol').resample('M', how='mean')
    In [23]: df_M
    Out[23]: 
    Empty DataFrame
    Columns: []
    Index: []
```

This, however, works, but requires a seemingly-unnecessary re-indexing:

``` python
    df_M = df.reset_index().set_index('dt').groupby('symbol').resample('M', how='mean')
    In [26]: df_M.head()
    Out[26]: 
                       Adj Close       Close        High         Low        Open  \
    symbol dt                                                                      
    AAPL   2013-01-31  67.677750  497.822382  504.407623  492.969997  500.083329   
           2013-02-28  62.388477  456.808942  463.231056  452.106325  458.503692   
           2013-03-31  60.417287  441.841000  446.803495  437.337996  442.011512   
           2013-04-30  57.398619  419.765001  425.553183  414.722271  419.766820   
           2013-05-31  61.340151  446.452734  451.658190  441.495455  446.400919   

                             Volume  
    symbol dt                        
    AAPL   2013-01-31  1.562312e+08  
           2013-02-28  1.229478e+08  
           2013-03-31  1.147110e+08  
           2013-04-30  1.245851e+08  
           2013-05-31  1.073583e+08  
```

The fact that you need to do the `reset_index().set_index('dt')` and then `groupby('symbol')` instead of `groupby(level='symbol')` seems to defeat the purpose of a multi-index! What gives?

I also realize that data like this is perhaps more well-suited to a Panel than a DataFrame, but when dealing with very large amounts of (often sparse) data, the 3D Panel structure presents performance and memory issues compared to the flat DataFrame.



Discussion:

Resolving patch:
diff --git a/pandas/core/groupby.py b/pandas/core/groupby.py
--- a/pandas/core/groupby.py
+++ b/pandas/core/groupby.py
@@ -280,7 +280,10 @@ def _set_grouper(self, obj, sort=False):
         return self.grouper
 
     def _get_binner_for_grouping(self, obj):
-        raise AbstractMethodError(self)
+        """ default to the standard binner here """
+        group_axis = obj._get_axis(self.axis)
+        return Grouping(group_axis, None, obj=obj, name=self.key,
+                        level=self.level, sort=self.sort, in_axis=False)
 
     @property
     def groups(self):
@@ -1964,8 +1967,12 @@ def __init__(self, index, grouper=None, obj=None, name=None, level=None,
                 if self.name is None:
                     self.name = grouper.name
 
+            # we are done
+            if isinstance(self.grouper, Grouping):
+                self.grouper = self.grouper.grouper
+
             # no level passed
-            if not isinstance(self.grouper, (Series, Index, Categorical, np.ndarray)):
+            elif not isinstance(self.grouper, (Series, Index, Categorical, np.ndarray)):
                 if getattr(self.grouper, 'ndim', 1) != 1:
                     t = self.name or str(type(self.grouper))
                     raise ValueError("Grouper for '%s' not 1-dimensional" % t)
@@ -2834,7 +2841,7 @@ def _wrap_applied_output(self, keys, values, not_indexed_same=False):
                     v = next(v for v in values if v is not None)
                 except StopIteration:
                     # If all values are None, then this will throw an error.
-                    # We'd prefer it return an empty dataframe. 
+                    # We'd prefer it return an empty dataframe.
                     return DataFrame()
                 if v is None:
                     return DataFrame()


Test patch:
diff --git a/pandas/tests/test_groupby.py b/pandas/tests/test_groupby.py
--- a/pandas/tests/test_groupby.py
+++ b/pandas/tests/test_groupby.py
@@ -430,6 +430,21 @@ def test_grouper_creation_bug(self):
         expected = s.groupby(level='one').sum()
         assert_series_equal(result, expected)
 
+    def test_grouper_getting_correct_binner(self):
+
+        # GH 10063
+        # using a non-time-based grouper and a time-based grouper
+        # and specifying levels
+        df = DataFrame({'A' : 1 },
+                       index=pd.MultiIndex.from_product([list('ab'),
+                                                         date_range('20130101',periods=80)],
+                                                        names=['one','two']))
+        result = df.groupby([pd.Grouper(level='one'),pd.Grouper(level='two',freq='M')]).sum()
+        expected = DataFrame({'A' : [31,28,21,31,28,21]},
+                              index=MultiIndex.from_product([list('ab'),date_range('20130101',freq='M',periods=3)],
+                                                            names=['one','two']))
+        assert_frame_equal(result, expected)
+
     def test_grouper_iter(self):
         self.assertEqual(sorted(self.df.groupby('A').grouper), ['bar', 'foo'])