Repository: modin-project/modin

Issue:
GroupBy size broken in case of Series
### System information
- **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**: Any
- **Modin version** (`modin.__version__`): 8152a4d0ed2c8669b43e2632e364befe05153766
- **Python version**: 3.7.5
- **Code we can use to reproduce**:
```python
import modin.pandas as pd

ser = pd.Series([1, 2, 3, 4])
grp = ser.groupby(["a", "a", "b", "b"])
print(grp.size())  # Error

```

Traceback:
```
Traceback (most recent call last):
  File "../rofl.py", line 5, in <module>
    print(grp.size())
  File "/localdisk/dchigare/repos/modin_bp/modin/pandas/groupby.py", line 511, in size
    if self._kwargs.get("as_index")
  File "/localdisk/dchigare/repos/modin_bp/modin/pandas/series.py", line 201, in __getattr__
    raise e
  File "/localdisk/dchigare/repos/modin_bp/modin/pandas/series.py", line 197, in __getattr__
    return object.__getattribute__(self, key)
AttributeError: 'Series' object has no attribute 'columns'
```

<!--
You can obtain the Modin version with

python -c "import modin; print(modin.__version__)"
-->

### Describe the problem
If our grouping object is Series, then we're getting this exception in `groupby.size()` size method



Discussion:

Resolving patch:
diff --git a/modin/backends/pandas/query_compiler.py b/modin/backends/pandas/query_compiler.py
--- a/modin/backends/pandas/query_compiler.py
+++ b/modin/backends/pandas/query_compiler.py
@@ -2460,11 +2460,41 @@ def _callable_func(self, func, axis, *args, **kwargs):
     groupby_max = GroupbyReduceFunction.register("max")
     groupby_min = GroupbyReduceFunction.register("min")
     groupby_prod = GroupbyReduceFunction.register("prod")
-    groupby_size = GroupbyReduceFunction.register("size", method="size")
     groupby_sum = GroupbyReduceFunction.register("sum")
 
+    def groupby_size(
+        self, by, axis, groupby_args, map_args, reduce_args, numeric_only, drop
+    ):
+        result = self._groupby_dict_reduce(
+            by=by,
+            axis=axis,
+            agg_func={self.columns[0]: [("__size_col__", "size")]},
+            agg_args=[],
+            agg_kwargs={},
+            groupby_kwargs=groupby_args,
+            drop=drop,
+            method="size",
+            default_to_pandas_func=lambda grp: grp.size(),
+        )
+        if groupby_args.get("as_index", True):
+            result.columns = ["__reduced__"]
+        elif isinstance(result.columns, pandas.MultiIndex):
+            # Dropping one extra-level which was added because of renaming aggregation
+            result.columns = (
+                result.columns[:-1].droplevel(-1).append(pandas.Index(["size"]))
+            )
+        return result
+
     def _groupby_dict_reduce(
-        self, by, axis, agg_func, agg_args, agg_kwargs, groupby_kwargs, drop=False
+        self,
+        by,
+        axis,
+        agg_func,
+        agg_args,
+        agg_kwargs,
+        groupby_kwargs,
+        drop=False,
+        **kwargs,
     ):
         map_dict = {}
         reduce_dict = {}
@@ -2497,7 +2527,7 @@ def _groupby_dict_reduce(
                 )
                 reduce_dict[reduced_col_name] = groupby_reduce_functions[func][1]
             map_dict[col] = map_fns
-        return GroupbyReduceFunction.register(map_dict, reduce_dict)(
+        return GroupbyReduceFunction.register(map_dict, reduce_dict, **kwargs)(
             query_compiler=self,
             by=by,
             axis=axis,
diff --git a/modin/data_management/functions/groupby_function.py b/modin/data_management/functions/groupby_function.py
--- a/modin/data_management/functions/groupby_function.py
+++ b/modin/data_management/functions/groupby_function.py
@@ -153,10 +153,11 @@ def caller(
             or isinstance(by, pandas.Grouper)
         ):
             by = try_cast_to_pandas(by, squeeze=True)
-            default_func = (
+            default_func = kwargs.get(
+                "default_to_pandas_func",
                 (lambda grp: grp.agg(map_func))
                 if isinstance(map_func, dict)
-                else map_func
+                else map_func,
             )
             return query_compiler.default_to_pandas(
                 lambda df: default_func(
diff --git a/modin/pandas/groupby.py b/modin/pandas/groupby.py
--- a/modin/pandas/groupby.py
+++ b/modin/pandas/groupby.py
@@ -480,65 +480,9 @@ def all(self, **kwargs):
         )
 
     def size(self):
-        if self._axis == 0:
-            # Series objects in 'by' mean we couldn't handle the case
-            # and transform 'by' to a query compiler.
-            # In this case we are just defaulting to pandas.
-            if is_list_like(self._by) and any(
-                isinstance(o, type(self._df._query_compiler)) for o in self._by
-            ):
-                work_object = DataFrameGroupBy(
-                    self._df,
-                    self._by,
-                    self._axis,
-                    drop=False,
-                    idx_name=None,
-                    squeeze=self._squeeze,
-                    **self._kwargs,
-                )
-                result = work_object._wrap_aggregation(
-                    type(work_object._query_compiler).groupby_size,
-                    lambda df: df.size(),
-                    numeric_only=False,
-                )
-                result = result.__constructor__(
-                    query_compiler=result._query_compiler
-                ).squeeze(axis=1)
-                if isinstance(result, Series):
-                    # Pandas does not name size() output for Series
-                    result.name = None
-                return result
-            work_object = SeriesGroupBy(
-                self._df[self._df.columns[0]]
-                if self._kwargs.get("as_index")
-                else self._df[self._df.columns[0]].to_frame(),
-                self._by,
-                self._axis,
-                drop=False,
-                idx_name=None,
-                squeeze=self._squeeze,
-                **self._kwargs,
-            )
-            result = work_object._wrap_aggregation(
-                type(work_object._query_compiler).groupby_size,
-                lambda df: df.size(),
-                numeric_only=False,
-            )
-            result = result.__constructor__(query_compiler=result._query_compiler)
-            if not self._kwargs.get("as_index") and not isinstance(result, Series):
-                result = result.rename(columns={0: "size"})
-                result = (
-                    result.rename(columns={"__reduced__": "index"})
-                    if "__reduced__" in result.columns
-                    else result
-                )
-            else:
-                # Pandas does not name size() output for Series
-                result.name = None
-            return result.fillna(0)
-        else:
+        if self._axis == 1:
             return DataFrameGroupBy(
-                self._df.T,
+                self._df.T.iloc[:, [0]],
                 self._by,
                 0,
                 drop=self._drop,
@@ -546,6 +490,34 @@ def size(self):
                 squeeze=self._squeeze,
                 **self._kwargs,
             ).size()
+        work_object = type(self)(
+            self._df,
+            self._by,
+            self._axis,
+            drop=False,
+            idx_name=None,
+            squeeze=self._squeeze,
+            **self._kwargs,
+        )
+        result = work_object._wrap_aggregation(
+            type(work_object._query_compiler).groupby_size,
+            lambda df: df.size(),
+            numeric_only=False,
+        )
+        if not isinstance(result, Series):
+            result = result.squeeze(axis=1)
+        if not self._kwargs.get("as_index") and not isinstance(result, Series):
+            result = result.rename(columns={0: "size"})
+            result = (
+                result.rename(columns={"__reduced__": "index"})
+                if "__reduced__" in result.columns
+                else result
+            )
+        elif isinstance(self._df, Series):
+            result.name = self._df.name
+        else:
+            result.name = None
+        return result.fillna(0)
 
     def sum(self, **kwargs):
         return self._wrap_aggregation(


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
@@ -914,6 +914,7 @@ def test_series_groupby(by, as_index_series_or_dataframe):
         eval_max(modin_groupby, pandas_groupby)
         eval_len(modin_groupby, pandas_groupby)
         eval_sum(modin_groupby, pandas_groupby)
+        eval_size(modin_groupby, pandas_groupby)
         eval_ngroup(modin_groupby, pandas_groupby)
         eval_nunique(modin_groupby, pandas_groupby)
         eval_median(modin_groupby, pandas_groupby)