Repository: modin-project/modin

Issue:
GroupBy.size is broken for OmniSci backend
### System information
- **Modin version** (`modin.__version__`): 7c3218f
- **Python version**: 3.7.5
- **Code we can use to reproduce**:
```python
import os
os.environ["MODIN_EXPERIMENTAL"] = True
os.environ["MODIN_BACKEND"] = "omnisci"

import modin.pandas as pd

df = pd.DataFrame({"col1": ["a", "b", "b"], "col2": [1, 2, 3]})
grp = df.groupby("col1")
print(grp.size()) # Error

```

<details><summary>Traceback</summary>

```
Traceback (most recent call last):
  File "rofl.py", line 5, in <module>
    print(df.groupby("col1").size()) # Error
  File "/localdisk/dchigare/repos/modin_bp/modin/pandas/base.py", line 2807, in __str__
    return repr(self)
  File "/localdisk/dchigare/repos/modin_bp/modin/pandas/series.py", line 254, in __repr__
    temp_df = self._build_repr_df(num_rows, num_cols)
  File "/localdisk/dchigare/repos/modin_bp/modin/pandas/base.py", line 152, in _build_repr_df
    if len(self.index) == 0 or (
  File "/localdisk/dchigare/repos/modin_bp/modin/pandas/base.py", line 2836, in __getattribute__
    return object.__getattribute__(self, item)
  File "/localdisk/dchigare/repos/modin_bp/modin/pandas/base.py", line 510, in _get_index
    return self._query_compiler.index
  File "/localdisk/dchigare/repos/modin_bp/modin/experimental/backends/omnisci/query_compiler.py", line 334, in _get_index
    return self._modin_frame.index
  File "/localdisk/dchigare/repos/modin_bp/modin/experimental/engines/omnisci_on_ray/frame/data.py", line 1137, in _get_index
    self._execute()
  File "/localdisk/dchigare/repos/modin_bp/modin/experimental/engines/omnisci_on_ray/frame/data.py", line 1010, in _execute
    self._op, self._index_cols, self._dtypes, self._table_cols
  File "/localdisk/dchigare/repos/modin_bp/modin/experimental/engines/omnisci_on_ray/frame/partition_manager.py", line 176, in run_exec_plan
    curs = omniSession.executeRA(cmd_prefix + calcite_json)
  File "/localdisk/dchigare/repos/modin_bp/modin/experimental/engines/omnisci_on_ray/frame/omnisci_worker.py", line 64, in executeRA
    r = cls._server.executeRA(query)
  File "dbe.pyx", line 196, in dbe.PyDbEngine.executeRA
RuntimeError: expressions in ELSE clause must be of the same or compatible types as those in the THEN clauses.

```

</details>

<!--
You can obtain the Modin version with

python -c "import modin; print(modin.__version__)"
-->

### Describe the problem
The caused exceptions just hides the original problem, we see this error because of `fillna` call for the result of groupby [(this line)](https://github.com/modin-project/modin/blob/7c3218fd9a28b9612228d0dcd694693842ac971e/modin/pandas/groupby.py#L536). If we'll remove `fillna` and run the code above again we'll see the cause of that exception:
```
modin result:
col1
a    a
b    b
Name: __reduced__, dtype: category
dtype: category
Categories (2, object): ['a', 'b']

pandas result:
col1
a    1
b    2
dtype: int64
```
Instead of returning the group sizes it just returns us the name of groups. GroupBy size implementation at omnisci backend is similar to `df.groupby(by).agg({df.columns[0]: "size"})`. Futher experiments revealed this behaviour:
```python
df.groupby("col1").agg({"col2": "size"}) # works fine
df.groupby("col1").agg({"col1": "size"}) # don't works fine
```
So it seems, that attempt to aggregate the same column that we're grouping on becomes an issue here, and since we're dropping all the other columns instead of by-cols at `groupby.size` at the API level, it crashes all the time.



Discussion:

Resolving patch:
diff --git a/modin/experimental/backends/omnisci/query_compiler.py b/modin/experimental/backends/omnisci/query_compiler.py
--- a/modin/experimental/backends/omnisci/query_compiler.py
+++ b/modin/experimental/backends/omnisci/query_compiler.py
@@ -213,6 +213,14 @@ def groupby_size(
         -------
         BaseQueryCompiler
         """
+        # Grouping on empty frame or on index level.
+        if len(self.columns) == 0:
+            return super().groupby_size(by, axis, groupby_args, map_args, **kwargs)
+
+        groupby_args = groupby_args.copy()
+        as_index = groupby_args.get("as_index", True)
+        # Setting 'as_index' to True to avoid 'by' and 'agg' columns naming conflict
+        groupby_args["as_index"] = True
         new_frame = self._modin_frame.groupby_agg(
             by,
             axis,
@@ -220,12 +228,12 @@ def groupby_size(
             groupby_args,
             **kwargs,
         )
-        if groupby_args["as_index"]:
+        if as_index:
             shape_hint = "column"
             new_frame = new_frame._set_columns(["__reduced__"])
         else:
             shape_hint = None
-            new_frame = new_frame._set_columns(list(new_frame.columns)[:-1] + ["size"])
+            new_frame = new_frame._set_columns(["size"]).reset_index(drop=False)
         return self.__constructor__(new_frame, shape_hint=shape_hint)
 
     def groupby_sum(self, by, axis, groupby_args, map_args, **kwargs):
diff --git a/modin/experimental/engines/omnisci_on_ray/frame/data.py b/modin/experimental/engines/omnisci_on_ray/frame/data.py
--- a/modin/experimental/engines/omnisci_on_ray/frame/data.py
+++ b/modin/experimental/engines/omnisci_on_ray/frame/data.py
@@ -438,18 +438,47 @@ def groupby_agg(self, by, axis, agg, groupby_args, **kwargs):
         if groupby_args["level"] is not None:
             raise NotImplementedError("levels are not supported for groupby")
 
-        groupby_cols = by_frame.columns.tolist()
-        agg_cols = [col for col in self.columns if col not in by_frame.columns]
+        drop = kwargs.get("drop", True)
+        as_index = groupby_args.get("as_index", True)
+        groupby_cols = by_frame.columns
+        if isinstance(agg, dict):
+            agg_cols = agg.keys()
+        elif not drop:
+            # If 'by' data came from a different frame then 'self-aggregation'
+            # columns are more prioritized.
+            agg_cols = self.columns
+        else:
+            agg_cols = [col for col in self.columns if col not in groupby_cols]
+
+        # Mimic pandas behaviour: pandas does not allow for aggregation to be empty
+        # in case of multi-column 'by'.
+        if not as_index and len(agg_cols) == 0 and len(groupby_cols) > 1:
+            agg_cols = self.columns
 
         # Create new base where all required columns are computed. We don't allow
         # complex expressions to be a group key or an aggeregate operand.
         assert isinstance(by_frame._op, TransformNode), "unexpected by_frame"
-        exprs = OrderedDict(((col, by_frame.ref(col)) for col in groupby_cols))
+
+        col_to_delete_template = "__delete_me_{name}"
+
+        def generate_by_name(by):
+            """Generate unuqie name for `by` column in the resulted frame."""
+            if as_index:
+                return f"__index__0_{by}"
+            elif by in agg_cols:
+                # Aggregation columns are more prioritized than the 'by' cols,
+                # so in case of naming conflicts, we drop 'by' cols.
+                return col_to_delete_template.format(name=by)
+            else:
+                return by
+
+        exprs = OrderedDict(
+            ((generate_by_name(col), by_frame.ref(col)) for col in groupby_cols)
+        )
+        groupby_cols = list(exprs.keys())
         exprs.update(((col, self.ref(col)) for col in agg_cols))
         exprs = translate_exprs_to_base(exprs, base)
-        base_cols = Index.__new__(
-            Index, data=list(exprs.keys()), dtype=self.columns.dtype
-        )
+        base_cols = Index.__new__(Index, data=exprs.keys(), dtype=self.columns.dtype)
         base = self.__constructor__(
             columns=base_cols,
             dtypes=self._dtypes_for_exprs(exprs),
@@ -466,12 +495,12 @@ def groupby_agg(self, by, axis, agg, groupby_args, **kwargs):
         # if groupby_args["dropna"]:
         #     base = base.dropna(subset=groupby_cols, how="any")
 
-        if groupby_args["as_index"]:
+        if as_index:
             index_cols = groupby_cols.copy()
         else:
             new_columns = groupby_cols.copy()
 
-        new_dtypes = by_frame._dtypes[groupby_cols].tolist()
+        new_dtypes = base._dtypes[groupby_cols].tolist()
 
         agg_exprs = OrderedDict()
         if isinstance(agg, str):
@@ -500,6 +529,15 @@ def groupby_agg(self, by, axis, agg, groupby_args, **kwargs):
             force_execution_mode=self._force_execution_mode,
         )
 
+        if not as_index:
+            col_to_delete = col_to_delete_template.format(name=".*")
+            filtered_columns = [
+                col
+                for col in new_frame.columns
+                if not (isinstance(col, str) and re.match(col_to_delete, col))
+            ]
+            if len(filtered_columns) != len(new_frame.columns):
+                new_frame = new_frame.mask(col_indices=filtered_columns)
         return new_frame
 
     def agg(self, agg, **kwargs):
@@ -2210,7 +2248,7 @@ def _index_name(self, col):
         match = re.search("__index__\\d+_(.*)", col)
         if match:
             name = match.group(1)
-            if name == "__None__":
+            if name in ("__None__", "__reduced__"):
                 return None
             return name
 


Test patch:
diff --git a/modin/experimental/engines/omnisci_on_ray/test/test_dataframe.py b/modin/experimental/engines/omnisci_on_ray/test/test_dataframe.py
--- a/modin/experimental/engines/omnisci_on_ray/test/test_dataframe.py
+++ b/modin/experimental/engines/omnisci_on_ray/test/test_dataframe.py
@@ -810,6 +810,39 @@ def applier(df, **kwargs):
             force_lazy=True,
         )
 
+    @pytest.mark.parametrize("method", ["sum", "size"])
+    def test_groupby_series(self, method):
+        def groupby(df, **kwargs):
+            ser = df[df.columns[0]]
+            return getattr(ser.groupby(ser), method)()
+
+        run_and_compare(groupby, data=self.data)
+
+    def test_groupby_size(self):
+        def groupby(df, **kwargs):
+            return df.groupby("a").size()
+
+        run_and_compare(groupby, data=self.data)
+
+    @pytest.mark.parametrize("by", [["a"], ["a", "b", "c"]])
+    @pytest.mark.parametrize("agg", ["sum", "size"])
+    @pytest.mark.parametrize("as_index", [True, False])
+    def test_groupby_agg_by_col(self, by, agg, as_index):
+        def simple_agg(df, **kwargs):
+            return df.groupby(by, as_index=as_index).agg(agg)
+
+        run_and_compare(simple_agg, data=self.data)
+
+        def dict_agg(df, **kwargs):
+            return df.groupby(by, as_index=as_index).agg({by[0]: agg})
+
+        run_and_compare(dict_agg, data=self.data)
+
+        def dict_agg_all_cols(df, **kwargs):
+            return df.groupby(by, as_index=as_index).agg({col: agg for col in by})
+
+        run_and_compare(dict_agg_all_cols, data=self.data)
+
     taxi_data = {
         "a": [1, 1, 2, 2],
         "b": [11, 21, 12, 11],
@@ -825,11 +858,7 @@ def applier(df, **kwargs):
     #       res = df.groupby("cab_type").size() - this should be tested later as well
     def test_taxi_q1(self):
         def taxi_q1(df, **kwargs):
-            # TODO: For now we can't do such groupby by first column since modin use that
-            #      column as aggregation one by default. We don't support such cases at
-            #      at the moment, this will be handled later
-            # ref = df.groupby("a").size()
-            return df.groupby("b").size()
+            return df.groupby("a").size()
 
         run_and_compare(taxi_q1, data=self.taxi_data)