Repository: mars-project/mars

Issue:
[BUG] Execute groupby failed
<!--
Thank you for your contribution!

Please review https://github.com/mars-project/mars/blob/master/CONTRIBUTING.rst before opening an issue.
-->

**Describe the bug**

`df.groupby().execute()` may fail.

**To Reproduce**
To help us reproducing this bug, please provide information below:
1. Your Python version
2. The version of Mars you use
3. Versions of crucial packages, such as numpy, scipy and protobuf
4. Full stack of the error.
5. Minimized code to reproduce the error.

```
In [1]: import pandas as pd; import numpy as np                                 

In [2]: df = pd.DataFrame(np.random.rand(4, 3), index=np.arange(5, 1, -1))      

In [4]: import mars.dataframe as md                                             

In [5]: mdf = md.DataFrame(df)                                                  

In [6]: mdf.groupby(0).execute()                                                
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-6-491b51043e08> in <module>
----> 1 mdf.groupby(0).execute()

~/Workspace/mars/mars/core.py in execute(self, session, **kw)
    426         if session is None:
    427             session = Session.default_or_local()
--> 428         return session.run(self, **kw)
    429 
    430     def fetch(self, session=None, **kw):

~/Workspace/mars/mars/session.py in run(self, *tileables, **kw)
    181         tileables = tuple(mt.tensor(t) if not isinstance(t, (Entity, Base)) else t
    182                           for t in tileables)
--> 183         result = self._sess.run(*tileables, **kw)
    184 
    185         for t in tileables:

~/Workspace/mars/mars/session.py in run(self, *tileables, **kw)
     88             # set number of running cores
     89             self.context.set_ncores(kw['n_parallel'])
---> 90             res = self._executor.execute_tileables(tileables, **kw)
     91             return res
     92 

~/Workspace/mars/mars/utils.py in _wrapped(*args, **kwargs)
    380                 _kernel_mode.eager = False
    381             _kernel_mode.eager_count = enter_eager_count + 1
--> 382             return func(*args, **kwargs)
    383         finally:
    384             _kernel_mode.eager_count -= 1

~/Workspace/mars/mars/utils.py in inner(*args, **kwargs)
    468     def inner(*args, **kwargs):
    469         with build_mode():
--> 470             return func(*args, **kwargs)
    471     return inner
    472 

~/Workspace/mars/mars/executor.py in execute_tileables(self, tileables, fetch, n_parallel, n_thread, print_progress, mock, compose)
    828                 # update shape of tileable and its chunks whatever it's successful or not
    829                 self._update_tileable_and_chunk_shape(
--> 830                     tileable_graph, chunk_result, chunk_graph_builder.interrupted_ops)
    831                 if chunk_graph_builder.done:
    832                     if len(intermediate_result_keys) > 0:

~/Workspace/mars/mars/executor.py in _update_tileable_and_chunk_shape(self, tileable_graph, chunk_result, failed_ops)
    726                     continue
    727                 for c in tiled_n.chunks:
--> 728                     c.data._shape = chunk_result[c.key].shape
    729                 new_nsplits = self.get_tileable_nsplits(n, chunk_result=chunk_result)
    730                 for node in (n, tiled_n):

~/Workspace/mars/mars/lib/groupby_wrapper.py in __getattr__(self, item)
     74         if item in getattr(self.obj, 'columns', ()):
     75             return self.__getitem__(item)
---> 76         return getattr(self.groupby_obj, item)
     77 
     78     def __iter__(self):

~/miniconda3/lib/python3.7/site-packages/pandas/core/groupby/groupby.py in __getattr__(self, attr)
    578 
    579         raise AttributeError(
--> 580             f"'{type(self).__name__}' object has no attribute '{attr}'"
    581         )
    582 

AttributeError: 'DataFrameGroupBy' object has no attribute 'shape'
```




Discussion:

Resolving patch:
diff --git a/mars/lib/groupby_wrapper.py b/mars/lib/groupby_wrapper.py
--- a/mars/lib/groupby_wrapper.py
+++ b/mars/lib/groupby_wrapper.py
@@ -54,13 +54,13 @@ def fill_value(v, key):
         else:
             self.groupby_obj = groupby_obj
 
-        self.is_frame = isinstance(self.groupby_obj, DataFrameGroupBy)
-
         if grouper_cache:
             self.groupby_obj.grouper._cache = grouper_cache
         if selection:
             self.groupby_obj = self.groupby_obj[selection]
 
+        self.is_frame = isinstance(self.groupby_obj, DataFrameGroupBy)
+
     def __getitem__(self, item):
         return GroupByWrapper(
             self.obj, keys=self.keys, axis=self.axis, level=self.level,
@@ -82,6 +82,13 @@ def __iter__(self):
     def empty(self):
         return self.obj.empty
 
+    @property
+    def shape(self):
+        shape = list(self.groupby_obj.obj.shape)
+        if self.is_frame and self.selection:
+            shape[1] = len(self.selection)
+        return tuple(shape)
+
     def to_tuple(self, truncate=False, pickle_function=False):
         if self.selection and truncate:
             if isinstance(self.selection, Iterable) and not isinstance(self.selection, str):


Test patch:
diff --git a/mars/lib/tests/test_lib.py b/mars/lib/tests/test_lib.py
--- a/mars/lib/tests/test_lib.py
+++ b/mars/lib/tests/test_lib.py
@@ -33,45 +33,71 @@ def testGroupByWrapper(self):
 
         grouped = GroupByWrapper.from_tuple(wrapped_groupby(df, level=0).to_tuple())
         assert_groupby_equal(grouped, df.groupby(level=0))
+        self.assertEqual(grouped.shape, (8, 4))
+        self.assertTrue(grouped.is_frame)
 
         grouped = GroupByWrapper.from_tuple(wrapped_groupby(df, level=0).C.to_tuple())
         assert_groupby_equal(grouped, df.groupby(level=0).C)
+        self.assertEqual(grouped.shape, (8,))
+        self.assertFalse(grouped.is_frame)
 
         grouped = GroupByWrapper.from_tuple(wrapped_groupby(df, 'B').to_tuple())
         assert_groupby_equal(grouped, df.groupby('B'))
+        self.assertEqual(grouped.shape, (8, 4))
+        self.assertTrue(grouped.is_frame)
 
         grouped = GroupByWrapper.from_tuple(wrapped_groupby(df, 'B').C.to_tuple(truncate=True))
         assert_groupby_equal(grouped, df.groupby('B').C, with_selection=True)
+        self.assertEqual(grouped.shape, (8,))
+        self.assertFalse(grouped.is_frame)
 
         grouped = GroupByWrapper.from_tuple(wrapped_groupby(df, 'B')[['C', 'D']].to_tuple(truncate=True))
         assert_groupby_equal(grouped, df.groupby('B')[['C', 'D']], with_selection=True)
+        self.assertEqual(grouped.shape, (8, 2))
+        self.assertTrue(grouped.is_frame)
 
         grouped = GroupByWrapper.from_tuple(wrapped_groupby(df, ['B', 'C']).to_tuple(truncate=True))
         assert_groupby_equal(grouped, df.groupby(['B', 'C']))
+        self.assertEqual(grouped.shape, (8, 4))
+        self.assertTrue(grouped.is_frame)
 
         grouped = GroupByWrapper.from_tuple(
             wrapped_groupby(df, ['B', 'C']).C.to_tuple(truncate=True))
         assert_groupby_equal(grouped, df.groupby(['B', 'C']).C, with_selection=True)
+        self.assertEqual(grouped.shape, (8,))
+        self.assertFalse(grouped.is_frame)
 
         grouped = GroupByWrapper.from_tuple(
             wrapped_groupby(df, ['B', 'C'])[['A', 'D']].to_tuple(truncate=True))
         assert_groupby_equal(grouped, df.groupby(['B', 'C'])[['A', 'D']], with_selection=True)
+        self.assertEqual(grouped.shape, (8, 2))
+        self.assertTrue(grouped.is_frame)
 
         grouped = GroupByWrapper.from_tuple(
             wrapped_groupby(df, ['B', 'C'])[['C', 'D']].to_tuple(truncate=True))
         assert_groupby_equal(grouped, df.groupby(['B', 'C'])[['C', 'D']], with_selection=True)
+        self.assertEqual(grouped.shape, (8, 2))
+        self.assertTrue(grouped.is_frame)
 
         grouped = GroupByWrapper.from_tuple(
             wrapped_groupby(df, lambda x: x[-1] % 2).to_tuple(pickle_function=True))
         assert_groupby_equal(grouped, df.groupby(lambda x: x[-1] % 2), with_selection=True)
+        self.assertEqual(grouped.shape, (8, 4))
+        self.assertTrue(grouped.is_frame)
 
         grouped = GroupByWrapper.from_tuple(
             wrapped_groupby(df, lambda x: x[-1] % 2).C.to_tuple(pickle_function=True))
         assert_groupby_equal(grouped, df.groupby(lambda x: x[-1] % 2).C, with_selection=True)
+        self.assertEqual(grouped.shape, (8,))
+        self.assertFalse(grouped.is_frame)
 
         grouped = GroupByWrapper.from_tuple(
             wrapped_groupby(df, lambda x: x[-1] % 2)[['C', 'D']].to_tuple(pickle_function=True))
         assert_groupby_equal(grouped, df.groupby(lambda x: x[-1] % 2)[['C', 'D']], with_selection=True)
+        self.assertEqual(grouped.shape, (8, 2))
+        self.assertTrue(grouped.is_frame)
 
         grouped = GroupByWrapper.from_tuple(wrapped_groupby(df.B, lambda x: x[-1] % 2).to_tuple())
         assert_groupby_equal(grouped, df.B.groupby(lambda x: x[-1] % 2), with_selection=True)
+        self.assertEqual(grouped.shape, (8,))
+        self.assertFalse(grouped.is_frame)