Repository: modin-project/modin

Issue:
`GroupBy.__getitem__` does not include 'by' columns into resulted object which leads to `KeyError`
### System information
- **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**: Any
- **Modin version** (`modin.__version__`): 
- **Python version**: 3.7.5
- **Code we can use to reproduce**:
```python
import modin.pandas as pd

df = pd.DataFrame(
    {
        "a": [1, 1, 2, 3],
        "b": [3, 4, 5, 6],
        "c": [7, 8, 9, 10],
        "d": [11, 12, 13, 14],
    }
)

grp = df.groupby("a")
grp_view_columns = grp[["b", "c"]]
grp_view_columns.agg(["mean"]) # KeyError: 'a' (failed to default to pandas)

grp = df.groupby(by=["a", [1, 1, 2, 2]])
grp_view_columns = grp[["b", "c"]]
repr(grp_view_columns.mean()) # KeyError: 'a' (modin aggregation is failed)

```

<details><summary>Tracebacks</summary>

First exception:
```
UserWarning: `DataFrame.groupby_on_multiple_columns` defaulting to pandas implementation.
To request implementation, send an email to feature_requests@modin.org.
FutureWarning: The `squeeze` parameter is deprecated and will be removed in a future version.
Traceback (most recent call last):
  File "t3.py", line 14, in <module>
    grp_view_columns.agg(["mean"]) # KeyError: 'a' (failed to default to pandas)
  File "modin/pandas/groupby.py", line 438, in aggregate
    **kwargs,
  File "modin/pandas/groupby.py", line 985, in _default_to_pandas
    return self._df._default_to_pandas(groupby_on_multiple_columns, *args, **kwargs)
  File "modin/pandas/base.py", line 459, in _default_to_pandas
    result = op(pandas_obj, *args, **kwargs)
  File "modin/pandas/groupby.py", line 979, in groupby_on_multiple_columns
    by=by, axis=self._axis, squeeze=self._squeeze, **self._kwargs
  File "miniconda3/envs/modin_tests/lib/python3.7/site-packages/pandas/core/frame.py", line 7636, in groupby
    dropna=dropna,
  File "miniconda3/envs/modin_tests/lib/python3.7/site-packages/pandas/core/groupby/groupby.py", line 896, in __init__
    dropna=self.dropna,
  File "miniconda3/envs/modin_tests/lib/python3.7/site-packages/pandas/core/groupby/grouper.py", line 860, in get_grouper
    raise KeyError(gpr)
KeyError: 'a'
```

Second exception:
```
ray::deploy_ray_func() (pid=1790987, ip=10.125.130.30)
  File "python/ray/_raylet.pyx", line 490, in ray._raylet.execute_task
  File "python/ray/_raylet.pyx", line 497, in ray._raylet.execute_task
  File "python/ray/_raylet.pyx", line 501, in ray._raylet.execute_task
  File "/localdisk/dchigare/miniconda3/envs/modin_tests/lib/python3.7/site-packages/ray/util/tracing/tracing_helper.py", line 330, in _function_with_tracing
    return function(*args, **kwargs)
  File "/localdisk/dchigare/repos/modin_bp/modin/engines/ray/pandas_on_ray/frame/axis_partition.py", line 207, in deploy_ray_func
    result = func(*args)
  File "/localdisk/dchigare/repos/modin_bp/modin/engines/base/frame/axis_partition.py", line 303, in deploy_axis_func
    result = func(dataframe, **kwargs)
  File "/localdisk/dchigare/repos/modin_bp/modin/engines/base/frame/data.py", line 1153, in _map_reduce_func
    series_result = func(df, *args, **kwargs)
  File "/localdisk/dchigare/repos/modin_bp/modin/backends/pandas/query_compiler.py", line 2845, in <lambda>
    df, by, drop, partition_idx
  File "/localdisk/dchigare/repos/modin_bp/modin/backends/pandas/query_compiler.py", line 2838, in groupby_agg_builder
    return compute_groupby(df.copy(), drop, partition_idx)
  File "/localdisk/dchigare/repos/modin_bp/modin/backends/pandas/query_compiler.py", line 2742, in compute_groupby
    grouped_df = df.groupby(by=by, axis=axis, **groupby_kwargs)
  File "/localdisk/dchigare/miniconda3/envs/modin_tests/lib/python3.7/site-packages/pandas/core/frame.py", line 7636, in groupby
    dropna=dropna,
  File "/localdisk/dchigare/miniconda3/envs/modin_tests/lib/python3.7/site-packages/pandas/core/groupby/groupby.py", line 896, in __init__
    dropna=self.dropna,
  File "/localdisk/dchigare/miniconda3/envs/modin_tests/lib/python3.7/site-packages/pandas/core/groupby/grouper.py", line 860, in get_grouper
    raise KeyError(gpr)
KeyError: 'a'
```

</details>

<!--
You can obtain the Modin version with

python -c "import modin; print(modin.__version__)"
-->

### Describe the problem
The problem that causes these `KeyError`s is that the slice of groupby object (produced by `__getitem__`) contains the source `_df` without "by" columns, only with the cols, specified by the `key` argument:
https://github.com/modin-project/modin/blob/e561a516217abbf630e89f794c838fb878c30a5f/modin/pandas/groupby.py#L353-L360

`GroupBy.__getitem__` does not include 'by' columns into resulted object which leads to `KeyError`
### System information
- **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**: Any
- **Modin version** (`modin.__version__`): 
- **Python version**: 3.7.5
- **Code we can use to reproduce**:
```python
import modin.pandas as pd

df = pd.DataFrame(
    {
        "a": [1, 1, 2, 3],
        "b": [3, 4, 5, 6],
        "c": [7, 8, 9, 10],
        "d": [11, 12, 13, 14],
    }
)

grp = df.groupby("a")
grp_view_columns = grp[["b", "c"]]
grp_view_columns.agg(["mean"]) # KeyError: 'a' (failed to default to pandas)

grp = df.groupby(by=["a", [1, 1, 2, 2]])
grp_view_columns = grp[["b", "c"]]
repr(grp_view_columns.mean()) # KeyError: 'a' (modin aggregation is failed)

```

<details><summary>Tracebacks</summary>

First exception:
```
UserWarning: `DataFrame.groupby_on_multiple_columns` defaulting to pandas implementation.
To request implementation, send an email to feature_requests@modin.org.
FutureWarning: The `squeeze` parameter is deprecated and will be removed in a future version.
Traceback (most recent call last):
  File "t3.py", line 14, in <module>
    grp_view_columns.agg(["mean"]) # KeyError: 'a' (failed to default to pandas)
  File "modin/pandas/groupby.py", line 438, in aggregate
    **kwargs,
  File "modin/pandas/groupby.py", line 985, in _default_to_pandas
    return self._df._default_to_pandas(groupby_on_multiple_columns, *args, **kwargs)
  File "modin/pandas/base.py", line 459, in _default_to_pandas
    result = op(pandas_obj, *args, **kwargs)
  File "modin/pandas/groupby.py", line 979, in groupby_on_multiple_columns
    by=by, axis=self._axis, squeeze=self._squeeze, **self._kwargs
  File "miniconda3/envs/modin_tests/lib/python3.7/site-packages/pandas/core/frame.py", line 7636, in groupby
    dropna=dropna,
  File "miniconda3/envs/modin_tests/lib/python3.7/site-packages/pandas/core/groupby/groupby.py", line 896, in __init__
    dropna=self.dropna,
  File "miniconda3/envs/modin_tests/lib/python3.7/site-packages/pandas/core/groupby/grouper.py", line 860, in get_grouper
    raise KeyError(gpr)
KeyError: 'a'
```

Second exception:
```
ray::deploy_ray_func() (pid=1790987, ip=10.125.130.30)
  File "python/ray/_raylet.pyx", line 490, in ray._raylet.execute_task
  File "python/ray/_raylet.pyx", line 497, in ray._raylet.execute_task
  File "python/ray/_raylet.pyx", line 501, in ray._raylet.execute_task
  File "/localdisk/dchigare/miniconda3/envs/modin_tests/lib/python3.7/site-packages/ray/util/tracing/tracing_helper.py", line 330, in _function_with_tracing
    return function(*args, **kwargs)
  File "/localdisk/dchigare/repos/modin_bp/modin/engines/ray/pandas_on_ray/frame/axis_partition.py", line 207, in deploy_ray_func
    result = func(*args)
  File "/localdisk/dchigare/repos/modin_bp/modin/engines/base/frame/axis_partition.py", line 303, in deploy_axis_func
    result = func(dataframe, **kwargs)
  File "/localdisk/dchigare/repos/modin_bp/modin/engines/base/frame/data.py", line 1153, in _map_reduce_func
    series_result = func(df, *args, **kwargs)
  File "/localdisk/dchigare/repos/modin_bp/modin/backends/pandas/query_compiler.py", line 2845, in <lambda>
    df, by, drop, partition_idx
  File "/localdisk/dchigare/repos/modin_bp/modin/backends/pandas/query_compiler.py", line 2838, in groupby_agg_builder
    return compute_groupby(df.copy(), drop, partition_idx)
  File "/localdisk/dchigare/repos/modin_bp/modin/backends/pandas/query_compiler.py", line 2742, in compute_groupby
    grouped_df = df.groupby(by=by, axis=axis, **groupby_kwargs)
  File "/localdisk/dchigare/miniconda3/envs/modin_tests/lib/python3.7/site-packages/pandas/core/frame.py", line 7636, in groupby
    dropna=dropna,
  File "/localdisk/dchigare/miniconda3/envs/modin_tests/lib/python3.7/site-packages/pandas/core/groupby/groupby.py", line 896, in __init__
    dropna=self.dropna,
  File "/localdisk/dchigare/miniconda3/envs/modin_tests/lib/python3.7/site-packages/pandas/core/groupby/grouper.py", line 860, in get_grouper
    raise KeyError(gpr)
KeyError: 'a'
```

</details>

<!--
You can obtain the Modin version with

python -c "import modin; print(modin.__version__)"
-->

### Describe the problem
The problem that causes these `KeyError`s is that the slice of groupby object (produced by `__getitem__`) contains the source `_df` without "by" columns, only with the cols, specified by the `key` argument:
https://github.com/modin-project/modin/blob/e561a516217abbf630e89f794c838fb878c30a5f/modin/pandas/groupby.py#L353-L360



Discussion:
One more example that probably relates to the issue.

```python
import modin.pandas as pd

df = pd.DataFrame(
    {
        "a": [1, 1, 2, 3],
        "b": [3, 4, 5, 6],
        "c": [7, 8, 9, 10],
        "d": [11, 12, 13, 14],
    }
)

grp = df.groupby(by=["a", [1, 1, 2, 2]])
grp_view_columns = grp[["b", "c"]]

grp_view_columns.count()
AttributeError: 'list' object has no attribute 'columns'
```
```python
import modin.pandas as pd

df = pd.DataFrame(
    {
        "a": [1, 1, 2, 3],
        "b": [3, 4, 5, 6],
        "c": [7, 8, 9, 10],
        "d": [11, 12, 13, 14],
    }
)

grp = df.groupby(by=["a", [1, 1, 2, 2]])
grp_view_columns = grp["b"]
NotImplementedError: Column lookups on GroupBy with arbitrary Series in by is not yet supported.
```
The last one should be resolved as part of a separate issue.
#3285 
I got an error that could be possibly related to the issue. 

    OS Platform and Distribution: Linux Ubuntu 16.04.7
    Modin version (modin.__version__): 0.10.2
    Python version: 3.8.9
```python
cor = modin_data.groupby(['Column_1'])[['Column_2', 'Column_3']].rolling(26, min_periods=4).corr()
```

Same code works with pandas, but not modin.


<details><summary>Traceback</summary>
<p>

```python
KeyError                                  Traceback (most recent call last)
~/model/process.py in <module>
----> 1 cor = modin_data.groupby(['Column_1'])[['Column_2', 'Column_3']].rolling(26, min_periods=4).corr()

~/CLV/.env/clv_env/lib/python3.8/site-packages/modin/pandas/groupby.py in rolling(self, *args, **kwargs)
    673 
    674     def rolling(self, *args, **kwargs):
--> 675         return self._default_to_pandas(lambda df: df.rolling(*args, **kwargs))
    676 
    677     def hist(self):

~/CLV/.env/clv_env/lib/python3.8/site-packages/modin/pandas/groupby.py in _default_to_pandas(self, f, *args, **kwargs)
    971             )
    972 
--> 973         return self._df._default_to_pandas(groupby_on_multiple_columns, *args, **kwargs)
    974 
    975 

~/CLV/.env/clv_env/lib/python3.8/site-packages/modin/pandas/base.py in _default_to_pandas(self, op, *args, **kwargs)
    457         pandas_obj = self._to_pandas()
    458         if callable(op):
--> 459             result = op(pandas_obj, *args, **kwargs)
    460         elif isinstance(op, str):
    461             # The inner `getattr` is ensuring that we are treating this object (whether

~/CLV/.env/clv_env/lib/python3.8/site-packages/modin/pandas/groupby.py in groupby_on_multiple_columns(df, *args, **kwargs)
    964         def groupby_on_multiple_columns(df, *args, **kwargs):
    965             return f(
--> 966                 df.groupby(
    967                     by=by, axis=self._axis, squeeze=self._squeeze, **self._kwargs
    968                 ),

~/CLV/.env/clv_env/lib/python3.8/site-packages/pandas/core/frame.py in groupby(self, by, axis, level, as_index, sort, group_keys, squeeze, observed, dropna)
   7624         # error: Argument "squeeze" to "DataFrameGroupBy" has incompatible type
   7625         # "Union[bool, NoDefault]"; expected "bool"
-> 7626         return DataFrameGroupBy(
   7627             obj=self,
   7628             keys=by,

~/CLV/.env/clv_env/lib/python3.8/site-packages/pandas/core/groupby/groupby.py in __init__(self, obj, keys, axis, level, grouper, exclusions, selection, as_index, sort, group_keys, squeeze, observed, mutated, dropna)
    886             from pandas.core.groupby.grouper import get_grouper
    887 
--> 888             grouper, exclusions, obj = get_grouper(
    889                 obj,
    890                 keys,

~/CLV/.env/clv_env/lib/python3.8/site-packages/pandas/core/groupby/grouper.py in get_grouper(obj, key, axis, level, sort, observed, mutated, validate, dropna)
    858                 in_axis, level, gpr = False, gpr, None
    859             else:
--> 860                 raise KeyError(gpr)
    861         elif isinstance(gpr, Grouper) and gpr.key is not None:
    862             # Add key to exclusions

KeyError: 'Column_1'
```

</p>
</details>
@straiffix, you're right, your problem is directly caused by this issue. This bug is now being fixed in #3298 PR and the fix is more likely to be a part of the next release. 

I've tried your reproducer on the #3298 branch and it works fine (for except defaulting to pandas on `groupby.rolling`)

As a workaround for now you may use:
```python
corr = modin_data[["Column_1", "Column_2", "Column_3"]].groupby("Column_1").rolling(26, min_periods=4).corr()
```
One more example that probably relates to the issue.

```python
import modin.pandas as pd

df = pd.DataFrame(
    {
        "a": [1, 1, 2, 3],
        "b": [3, 4, 5, 6],
        "c": [7, 8, 9, 10],
        "d": [11, 12, 13, 14],
    }
)

grp = df.groupby(by=["a", [1, 1, 2, 2]])
grp_view_columns = grp[["b", "c"]]

grp_view_columns.count()
AttributeError: 'list' object has no attribute 'columns'
```
```python
import modin.pandas as pd

df = pd.DataFrame(
    {
        "a": [1, 1, 2, 3],
        "b": [3, 4, 5, 6],
        "c": [7, 8, 9, 10],
        "d": [11, 12, 13, 14],
    }
)

grp = df.groupby(by=["a", [1, 1, 2, 2]])
grp_view_columns = grp["b"]
NotImplementedError: Column lookups on GroupBy with arbitrary Series in by is not yet supported.
```
The last one should be resolved as part of a separate issue.
#3285 
I got an error that could be possibly related to the issue. 

    OS Platform and Distribution: Linux Ubuntu 16.04.7
    Modin version (modin.__version__): 0.10.2
    Python version: 3.8.9
```python
cor = modin_data.groupby(['Column_1'])[['Column_2', 'Column_3']].rolling(26, min_periods=4).corr()
```

Same code works with pandas, but not modin.


<details><summary>Traceback</summary>
<p>

```python
KeyError                                  Traceback (most recent call last)
~/model/process.py in <module>
----> 1 cor = modin_data.groupby(['Column_1'])[['Column_2', 'Column_3']].rolling(26, min_periods=4).corr()

~/CLV/.env/clv_env/lib/python3.8/site-packages/modin/pandas/groupby.py in rolling(self, *args, **kwargs)
    673 
    674     def rolling(self, *args, **kwargs):
--> 675         return self._default_to_pandas(lambda df: df.rolling(*args, **kwargs))
    676 
    677     def hist(self):

~/CLV/.env/clv_env/lib/python3.8/site-packages/modin/pandas/groupby.py in _default_to_pandas(self, f, *args, **kwargs)
    971             )
    972 
--> 973         return self._df._default_to_pandas(groupby_on_multiple_columns, *args, **kwargs)
    974 
    975 

~/CLV/.env/clv_env/lib/python3.8/site-packages/modin/pandas/base.py in _default_to_pandas(self, op, *args, **kwargs)
    457         pandas_obj = self._to_pandas()
    458         if callable(op):
--> 459             result = op(pandas_obj, *args, **kwargs)
    460         elif isinstance(op, str):
    461             # The inner `getattr` is ensuring that we are treating this object (whether

~/CLV/.env/clv_env/lib/python3.8/site-packages/modin/pandas/groupby.py in groupby_on_multiple_columns(df, *args, **kwargs)
    964         def groupby_on_multiple_columns(df, *args, **kwargs):
    965             return f(
--> 966                 df.groupby(
    967                     by=by, axis=self._axis, squeeze=self._squeeze, **self._kwargs
    968                 ),

~/CLV/.env/clv_env/lib/python3.8/site-packages/pandas/core/frame.py in groupby(self, by, axis, level, as_index, sort, group_keys, squeeze, observed, dropna)
   7624         # error: Argument "squeeze" to "DataFrameGroupBy" has incompatible type
   7625         # "Union[bool, NoDefault]"; expected "bool"
-> 7626         return DataFrameGroupBy(
   7627             obj=self,
   7628             keys=by,

~/CLV/.env/clv_env/lib/python3.8/site-packages/pandas/core/groupby/groupby.py in __init__(self, obj, keys, axis, level, grouper, exclusions, selection, as_index, sort, group_keys, squeeze, observed, mutated, dropna)
    886             from pandas.core.groupby.grouper import get_grouper
    887 
--> 888             grouper, exclusions, obj = get_grouper(
    889                 obj,
    890                 keys,

~/CLV/.env/clv_env/lib/python3.8/site-packages/pandas/core/groupby/grouper.py in get_grouper(obj, key, axis, level, sort, observed, mutated, validate, dropna)
    858                 in_axis, level, gpr = False, gpr, None
    859             else:
--> 860                 raise KeyError(gpr)
    861         elif isinstance(gpr, Grouper) and gpr.key is not None:
    862             # Add key to exclusions

KeyError: 'Column_1'
```

</p>
</details>
@straiffix, you're right, your problem is directly caused by this issue. This bug is now being fixed in #3298 PR and the fix is more likely to be a part of the next release. 

I've tried your reproducer on the #3298 branch and it works fine (for except defaulting to pandas on `groupby.rolling`)

As a workaround for now you may use:
```python
corr = modin_data[["Column_1", "Column_2", "Column_3"]].groupby("Column_1").rolling(26, min_periods=4).corr()
```

Resolving patch:
diff --git a/modin/backends/base/query_compiler.py b/modin/backends/base/query_compiler.py
--- a/modin/backends/base/query_compiler.py
+++ b/modin/backends/base/query_compiler.py
@@ -2360,7 +2360,8 @@ def groupby_agg(
         """
         if isinstance(by, type(self)) and len(by.columns) == 1:
             by = by.columns[0] if drop else by.to_pandas().squeeze()
-        elif isinstance(by, type(self)):
+        # converting QC 'by' to a list of column labels only if this 'by' comes from the self (if drop is True)
+        elif drop and isinstance(by, type(self)):
             by = list(by.columns)
 
         return GroupByDefault.register(pandas.core.groupby.DataFrameGroupBy.aggregate)(
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
@@ -16,6 +16,7 @@
 import pandas
 
 from .mapreducefunction import MapReduceFunction
+from .default_methods.groupby_default import GroupBy
 from modin.utils import try_cast_to_pandas, hashable
 
 
@@ -272,6 +273,10 @@ def caller(
             or isinstance(by, pandas.Grouper)
         ):
             by = try_cast_to_pandas(by, squeeze=True)
+            # Since 'by' may be a 2D query compiler holding columns to group by,
+            # to_pandas will also produce a pandas DataFrame containing them.
+            # So splitting 2D 'by' into a list of 1D Series using 'GroupBy.validate_by':
+            by = GroupBy.validate_by(by)
             if default_to_pandas_func is None:
                 default_to_pandas_func = (
                     (lambda grp: grp.agg(map_func))
diff --git a/modin/pandas/groupby.py b/modin/pandas/groupby.py
--- a/modin/pandas/groupby.py
+++ b/modin/pandas/groupby.py
@@ -18,6 +18,7 @@
 import pandas.core.groupby
 from pandas.core.dtypes.common import is_list_like
 from pandas.core.aggregation import reconstruct_func
+from pandas._libs.lib import no_default
 import pandas.core.common as com
 from types import BuiltinFunctionType
 from collections.abc import Iterable
@@ -31,6 +32,7 @@
     wrap_into_list,
 )
 from modin.backends.base.query_compiler import BaseQueryCompiler
+from modin.data_management.functions.default_methods.groupby_default import GroupBy
 from modin.config import IsExperimental
 from .series import Series
 from .utils import is_label
@@ -294,6 +296,36 @@ def first(self, **kwargs):
     def backfill(self, limit=None):
         return self.bfill(limit)
 
+    _internal_by_cache = no_default
+
+    # TODO: since python 3.9:
+    # @cached_property
+    @property
+    def _internal_by(self):
+        """
+        Get only those components of 'by' that are column labels of the source frame.
+
+        Returns
+        -------
+        tuple of labels
+        """
+        if self._internal_by_cache is not no_default:
+            return self._internal_by_cache
+
+        internal_by = tuple()
+        if self._drop:
+            if is_list_like(self._by):
+                internal_by = tuple(by for by in self._by if isinstance(by, str))
+            else:
+                ErrorMessage.catch_bugs_and_request_email(
+                    failure_condition=not isinstance(self._by, BaseQueryCompiler),
+                    extra_log=f"When 'drop' is True, 'by' must be either list-like or a QueryCompiler, met: {type(self._by)}.",
+                )
+                internal_by = tuple(self._by.columns)
+
+        self._internal_by_cache = internal_by
+        return internal_by
+
     def __getitem__(self, key):
         """
         Implement indexing operation on a DataFrameGroupBy object.
@@ -313,36 +345,47 @@ def __getitem__(self, key):
         NotImplementedError
             Column lookups on GroupBy with arbitrary Series in by is not yet supported.
         """
-        kwargs = {**self._kwargs.copy(), "squeeze": self._squeeze}
-        # Most of time indexing DataFrameGroupBy results in another DataFrameGroupBy object unless circumstances are
-        # special in which case SeriesGroupBy has to be returned. Such circumstances are when key equals to a single
-        # column name and is not a list of column names or list of one column name.
-        make_dataframe = True
-        if self._drop and self._as_index:
-            if not isinstance(key, list):
-                key = [key]
-                kwargs["squeeze"] = True
+        # These parameters are common for building the resulted Series or DataFrame groupby object
+        kwargs = {
+            **self._kwargs.copy(),
+            "by": self._by,
+            "axis": self._axis,
+            "idx_name": self._idx_name,
+            "squeeze": self._squeeze,
+        }
+        # The rules of type deduction for the resulted object is the following:
+        #   1. If `key` is a list-like or `as_index is False`, then the resulted object is a DataFrameGroupBy
+        #   2. Otherwise, the resulted object is SeriesGroupBy
+        #   3. Result type does not depend on the `by` origin
+        # Examples:
+        #   - drop: any, as_index: any, __getitem__(key: list_like) -> DataFrameGroupBy
+        #   - drop: any, as_index: False, __getitem__(key: any) -> DataFrameGroupBy
+        #   - drop: any, as_index: True, __getitem__(key: label) -> SeriesGroupBy
+        if is_list_like(key):
+            make_dataframe = True
+        else:
+            if self._as_index:
                 make_dataframe = False
-        # When `as_index` is False, pandas will always convert to a `DataFrame`, we
-        # convert to a list here so that the result will be a `DataFrame`.
-        elif not self._as_index and not isinstance(key, list):
-            # Sometimes `__getitem__` doesn't only get the item, it also gets the `by`
-            # column. This logic is here to ensure that we also get the `by` data so
-            # that it is there for `as_index=False`.
-            if (
-                isinstance(self._by, type(self._query_compiler))
-                and all(c in self._columns for c in self._by.columns)
-                and self._drop
-            ):
-                key = list(self._by.columns) + [key]
             else:
+                make_dataframe = True
                 key = [key]
-        if isinstance(key, list) and (make_dataframe or not self._as_index):
+        if make_dataframe:
+            internal_by = frozenset(self._internal_by)
+            if len(internal_by.intersection(key)) != 0:
+                ErrorMessage.missmatch_with_pandas(
+                    operation="GroupBy.__getitem__",
+                    message=(
+                        "intersection of the selection and 'by' columns is not yet supported, "
+                        "to achieve the desired result rewrite the original code from:\n"
+                        "df.groupby('by_column')['by_column']\n"
+                        "to the:\n"
+                        "df.groupby(df['by_column'].copy())['by_column']"
+                    ),
+                )
+            cols_to_grab = internal_by.union(key)
+            key = [col for col in self._df.columns if col in cols_to_grab]
             return DataFrameGroupBy(
                 self._df[key],
-                self._by,
-                self._axis,
-                idx_name=self._idx_name,
                 drop=self._drop,
                 **kwargs,
             )
@@ -357,9 +400,6 @@ def __getitem__(self, key):
             )
         return SeriesGroupBy(
             self._df[key],
-            self._by,
-            self._axis,
-            idx_name=self._idx_name,
             drop=False,
             **kwargs,
         )
@@ -954,12 +994,17 @@ def _default_to_pandas(self, f, *args, **kwargs):
             and len(self._by.columns) == 1
         ):
             by = self._by.columns[0] if self._drop else self._by.to_pandas().squeeze()
-        elif isinstance(self._by, type(self._query_compiler)):
+        # converting QC 'by' to a list of column labels only if this 'by' comes from the self (if drop is True)
+        elif self._drop and isinstance(self._by, type(self._query_compiler)):
             by = list(self._by.columns)
         else:
             by = self._by
 
         by = try_cast_to_pandas(by, squeeze=True)
+        # Since 'by' may be a 2D query compiler holding columns to group by,
+        # to_pandas will also produce a pandas DataFrame containing them.
+        # So splitting 2D 'by' into a list of 1D Series using 'GroupBy.validate_by':
+        by = GroupBy.validate_by(by)
 
         def groupby_on_multiple_columns(df, *args, **kwargs):
             return f(


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
@@ -15,7 +15,7 @@
 import pandas
 import numpy as np
 import modin.pandas as pd
-from modin.utils import try_cast_to_pandas, get_current_backend
+from modin.utils import try_cast_to_pandas, get_current_backend, hashable
 from modin.pandas.utils import from_pandas, is_scalar
 from .utils import (
     df_equals,
@@ -439,7 +439,41 @@ def maybe_get_columns(df, by):
     ):
         # Not yet supported for non-original-column-from-dataframe Series in by:
         eval___getattr__(modin_groupby, pandas_groupby, "col3")
+        eval___getitem__(modin_groupby, pandas_groupby, "col3")
     eval_groups(modin_groupby, pandas_groupby)
+    # Intersection of the selection and 'by' columns is not yet supported
+    non_by_cols = (
+        # Potential selection starts only from the second column, because the first may
+        # be categorical in this test, which is not yet supported
+        [col for col in pandas_df.columns[1:] if col not in modin_groupby._internal_by]
+        if isinstance(by, list)
+        else ["col3", "col4"]
+    )
+    eval___getitem__(modin_groupby, pandas_groupby, non_by_cols)
+    # When GroupBy.__getitem__ meets an intersection of the selection and 'by' columns
+    # it throws a warning with the suggested workaround. The following code tests
+    # that this workaround works as expected.
+    if len(modin_groupby._internal_by) != 0:
+        if not isinstance(by, list):
+            by = [by]
+        by_from_workaround = [
+            modin_df[getattr(col, "name", col)].copy()
+            if (hashable(col) and col in modin_groupby._internal_by)
+            or isinstance(col, GetColumn)
+            else col
+            for col in by
+        ]
+        # GroupBy result with 'as_index=False' depends on the 'by' origin, since we forcibly changed
+        # the origin of 'by' for modin by doing a copy, set 'as_index=True' to compare results.
+        modin_groupby = modin_df.groupby(
+            maybe_get_columns(modin_df, by_from_workaround), as_index=True
+        )
+        pandas_groupby = pandas_df.groupby(pandas_by, as_index=True)
+        eval___getitem__(
+            modin_groupby,
+            pandas_groupby,
+            list(modin_groupby._internal_by) + non_by_cols[:1],
+        )
 
 
 def test_single_group_row_groupby():
@@ -1111,6 +1145,56 @@ def eval___getattr__(modin_groupby, pandas_groupby, item):
     )
 
 
+def eval___getitem__(md_grp, pd_grp, item):
+    eval_general(
+        md_grp,
+        pd_grp,
+        lambda grp: grp[item].mean(),
+        comparator=build_types_asserter(df_equals),
+    )
+    eval_general(
+        md_grp,
+        pd_grp,
+        lambda grp: grp[item].count(),
+        comparator=build_types_asserter(df_equals),
+    )
+
+    def build_list_agg(fns):
+        def test(grp):
+            res = grp[item].agg(fns)
+            if res.ndim == 2:
+                # Modin's frame has an extra level in the result. Alligning columns to compare.
+                # https://github.com/modin-project/modin/issues/3490
+                res = res.set_axis(fns, axis=1)
+            return res
+
+        return test
+
+    # issue-#3252
+    eval_general(
+        md_grp,
+        pd_grp,
+        build_list_agg(["mean"]),
+        comparator=build_types_asserter(df_equals),
+    )
+    eval_general(
+        md_grp,
+        pd_grp,
+        build_list_agg(["mean", "count"]),
+        comparator=build_types_asserter(df_equals),
+    )
+    # Explicit default-to-pandas test
+    eval_general(
+        md_grp,
+        pd_grp,
+        # Defaulting to pandas only for Modin groupby objects
+        lambda grp: grp[item].sum()
+        if not isinstance(grp, pd.groupby.DataFrameGroupBy)
+        else grp[item]._default_to_pandas(lambda df: df.sum()),
+        comparator=build_types_asserter(df_equals),
+    )
+
+
 def eval_groups(modin_groupby, pandas_groupby):
     for k, v in modin_groupby.groups.items():
         assert v.equals(pandas_groupby.groups[k])
@@ -1442,6 +1526,37 @@ def get_columns(df):
     df_equals(ref, exp)
 
 
+@pytest.mark.parametrize(
+    # When True, use (df[name] + 1), otherwise just use name
+    "columns",
+    [
+        [(True, "a"), (True, "b"), (True, "c")],
+        [(True, "a"), (True, "b")],
+        [(False, "a"), (False, "b"), (True, "c")],
+        [(False, "a"), (True, "c")],
+        [(False, "a"), (True, "c"), (False, [1, 1, 2])],
+        [(False, "a"), (False, "b"), (False, "c")],
+        [(False, "a"), (False, "b"), (False, "c"), (False, [1, 1, 2])],
+    ],
+)
+def test_internal_by_detection(columns):
+    def get_columns(df):
+        return [(df[name] + 1) if lookup else name for (lookup, name) in columns]
+
+    data = {"a": [1, 1, 2], "b": [11, 11, 22], "c": [111, 111, 222]}
+
+    md_df = pd.DataFrame(data)
+    by = get_columns(md_df)
+    md_grp = md_df.groupby(by)
+
+    ref = frozenset(
+        col for is_lookup, col in columns if not is_lookup and hashable(col)
+    )
+    exp = frozenset(md_grp._internal_by)
+
+    assert ref == exp
+
+
 @pytest.mark.parametrize(
     # When True, use (df[name] + 1), otherwise just use name
     "columns",
@@ -1566,6 +1681,8 @@ def test_multi_column_groupby_different_partitions(
         by, as_index=as_index
     )
     eval_general(md_grp, pd_grp, func_to_apply)
+    eval___getitem__(md_grp, pd_grp, md_df.columns[1])
+    eval___getitem__(md_grp, pd_grp, [md_df.columns[1], md_df.columns[2]])
 
 
 @pytest.mark.parametrize(