Repository: modin-project/modin

Issue:
[BUG] groupby + aggregate return pandas series as a result
### System information
- **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**: Ubuntu 19.04
- **Modin installed from (source or binary)**: source
- **Modin version**: master
- **Python version**: 3.8.3
- **Exact command to reproduce**: python test.py

<!--
You can obtain the Modin version with

python -c "import modin; print(modin.__version__)"
-->

### Describe the problem
Pandas Series is returned by operation instead of Modin Series.
<!-- Describe the problem clearly here. -->

### Source code / logs
```
import modin.pandas as pd

data = {
        "a": [1, 1, 2, 2, 2],
        "b": [11, 21, 12, 22, 32],
        "c": [101, 201, 102, 202, 302],
    }
df = pd.DataFrame(data)
df = df.groupby(["a"]).c.sum()
print(type(df))
```
```
# python test.py
<class 'pandas.core.series.Series'>
```


Discussion:
There is a bug in how we're accessing attributes via `__getattr__`, look at the related code:
```python
if __name__ == "__main__":
    import modin.pandas as pd

    data = {
            "a": [1, 1, 2, 2, 2],
            "b": [11, 21, 12, 22, 32],
            "c": [101, 201, 102, 202, 302],
        }
    df = pd.DataFrame(data)

    attr_res = df.groupby(["a"]).c
    item_res = df.groupby(["a"])["c"]

    print(type(attr_res)) # pandas.SeriesGroupBy
    print(type(item_res)) # modin.SeriesGroupBy

```

The thing is, that for some reasons we're doing `__getitem__` default to pandas in `__getattr__`, despite we already have implementation for `DataFrameGroupBy.__getitem__` https://github.com/modin-project/modin/blob/896f5b6e456fc7565fc2dea59af479b4b1739e1c/modin/pandas/groupby.py#L109-L114
What is happening here is:
1. `df.__getitem__(key)` returns `pandas.SeriesGroupBy`
2. `_default_to_pandas` expects only DataFrames and Series as the result of the default function
3. `_default_to_pandas` can't create any distributed object from an unexpected object and just returns it
4. we're getting pandas object as the result of modin function

so the fix will be is just replacing default to pandas call to `self.__getitem__(key)`

@devin-petersohn this taks is assigned to you, but if you don't mind, I can prepare the PR with this quick fix
@dchigarev I have assigned to you, thanks for looking at it!

Resolving patch:
diff --git a/modin/pandas/groupby.py b/modin/pandas/groupby.py
--- a/modin/pandas/groupby.py
+++ b/modin/pandas/groupby.py
@@ -110,7 +110,7 @@ def __getattr__(self, key):
             return object.__getattribute__(self, key)
         except AttributeError as e:
             if key in self._columns:
-                return self._default_to_pandas(lambda df: df.__getitem__(key))
+                return self.__getitem__(key)
             raise e
 
     @property


Test patch:
diff --git a/modin/pandas/test/test_groupby.py b/modin/pandas/test/test_groupby.py
--- a/modin/pandas/test/test_groupby.py
+++ b/modin/pandas/test/test_groupby.py
@@ -16,7 +16,7 @@
 import numpy as np
 import modin.pandas as pd
 from modin.utils import try_cast_to_pandas, get_current_backend
-from modin.pandas.utils import from_pandas
+from modin.pandas.utils import from_pandas, is_scalar
 from .utils import (
     df_equals,
     check_df_columns_have_nans,
@@ -49,6 +49,17 @@ def eval_aggregation(md_df, pd_df, operation=None, by=None, *args, **kwargs):
     )
 
 
+def build_types_asserter(comparator):
+    def wrapper(obj1, obj2, *args, **kwargs):
+        error_str = f"obj1 and obj2 has incorrect types: {type(obj1)} and {type(obj2)}"
+        assert not (is_scalar(obj1) ^ is_scalar(obj2)), error_str
+        assert obj1.__module__.split(".")[0] == "modin", error_str
+        assert obj2.__module__.split(".")[0] == "pandas", error_str
+        comparator(obj1, obj2, *args, **kwargs)
+
+    return wrapper
+
+
 @pytest.mark.parametrize("as_index", [True, False])
 def test_mixed_dtypes_groupby(as_index):
     frame_data = np.random.randint(97, 198, size=(2 ** 6, 2 ** 4))
@@ -1038,16 +1049,18 @@ def eval_quantile(modin_groupby, pandas_groupby):
 
 
 def eval___getattr__(modin_groupby, pandas_groupby, item):
-    try:
-        pandas_groupby = pandas_groupby[item]
-        pandas_result = pandas_groupby.count()
-    except Exception as e:
-        with pytest.raises(type(e)):
-            modin_groupby[item].count()
-    else:
-        modin_groupby = modin_groupby[item]
-        modin_result = modin_groupby.count()
-        df_equals(modin_result, pandas_result)
+    eval_general(
+        modin_groupby,
+        pandas_groupby,
+        lambda grp: grp[item].count(),
+        comparator=build_types_asserter(df_equals),
+    )
+    eval_general(
+        modin_groupby,
+        pandas_groupby,
+        lambda grp: getattr(grp, item).count(),
+        comparator=build_types_asserter(df_equals),
+    )
 
 
 def eval_groups(modin_groupby, pandas_groupby):