Repository: modin-project/modin

Issue:
Cannot pass `pd.Grouper` object to `groupby`
<!--
General questions should be asked on the mailing list modin-dev@googlegroups.com.

Before submitting an issue, please fill out the following form.
-->

### System information
- **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**: WSL
- **Modin installed from (source or binary)**: source
- **Python version**: 3.6
- **Exact command to reproduce**:

```python
df.groupby(pd.Grouper(key="Date", freq="W-WED")).count()
```
<!--
You can obtain the Modin version with

python -c "import modin; print(modin.__version__)"
-->

### Describe the problem
<!-- Describe the problem clearly here. -->
`Grouper` objects are not correctly supported in Modin.
### Source code / logs
 The following error is thrown:

```
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-26-f257b274d700> in <module>
----> 1 m_df.groupby(pd.Grouper(key="starred_at", freq="W-WED")).count()

/mnt/c/Users/devin/software_sources/modin/modin/pandas/dataframe.py in groupby(self, by, axis, level, as_index, sort, group_keys, squeeze, observed, **kwargs)
    407             idx_name,
    408             observed=observed,
--> 409             **kwargs
    410         )
    411

/mnt/c/Users/devin/software_sources/modin/modin/pandas/groupby.py in __init__(self, df, by, axis, level, as_index, sort, group_keys, squeeze, idx_name, **kwargs)
     42         self._by = by
     43         # This tells us whether or not there are multiple columns/rows in the groupby
---> 44         self._is_multi_by = all(obj in self._df for obj in self._by) and axis == 0
     45         self._level = level
     46         self._kwargs = {

TypeError: 'TimeGrouper' object is not iterable
```
<!-- Include any logs or source code that would be helpful to diagnose the problem. If including tracebacks, please include the full traceback. Large logs and files should be attached. Try to provide a reproducible test case that is the bare minimum necessary to generate the problem. -->



Discussion:

Resolving patch:
diff --git a/modin/pandas/groupby.py b/modin/pandas/groupby.py
--- a/modin/pandas/groupby.py
+++ b/modin/pandas/groupby.py
@@ -41,7 +41,11 @@ def __init__(
         self._columns = self._query_compiler.columns
         self._by = by
         # This tells us whether or not there are multiple columns/rows in the groupby
-        self._is_multi_by = all(obj in self._df for obj in self._by) and axis == 0
+        self._is_multi_by = (
+            is_list_like(self._by)
+            and all(obj in self._df for obj in self._by)
+            and axis == 0
+        )
         self._level = level
         self._kwargs = {
             "sort": sort,
@@ -80,17 +84,19 @@ def __getattr__(self, key):
     @property
     def _index_grouped(self):
         if self._index_grouped_cache is None:
-            if self._is_multi_by:
+            if self._is_multi_by or isinstance(self._by, pandas.Grouper):
                 # Because we are doing a collect (to_pandas) here and then groupby, we
                 # end up using pandas implementation. Add the warning so the user is
                 # aware.
                 ErrorMessage.catch_bugs_and_request_email(self._axis == 1)
-                ErrorMessage.default_to_pandas("Groupby with multiple columns")
+                ErrorMessage.default_to_pandas(
+                    "Groupby with multiple columns or Grouper object"
+                )
                 self._index_grouped_cache = {
                     k: v.index
-                    for k, v in self._df._query_compiler.getitem_column_array(self._by)
-                    .to_pandas()
-                    .groupby(by=self._by)
+                    for k, v in self._df._query_compiler.to_pandas().groupby(
+                        by=self._by
+                    )
                 }
             else:
                 if self._axis == 0:
@@ -323,6 +329,13 @@ def boxplot(
         )
 
     def ngroup(self, ascending=True):
+        if self._is_multi_by or isinstance(self._by, pandas.Grouper):
+            ErrorMessage.default_to_pandas(
+                "Gropuby with multiple columns or Grouper object"
+            )
+            return self._df._default_to_pandas(
+                lambda df: df.groupby(by=self._by).ngroup()
+            )
         index = self._index if not self._axis else self._columns
         return (
             pandas.Series(index=index)
@@ -410,7 +423,7 @@ def _apply_agg_function(self, f, drop=True, **kwargs):
         assert callable(f), "'{0}' object is not callable".format(type(f))
         from .dataframe import DataFrame
 
-        if self._is_multi_by:
+        if self._is_multi_by or isinstance(self._by, pandas.Grouper):
             return self._default_to_pandas(f, **kwargs)
         # For aggregations, pandas behavior does this for the result.
         # For other operations it does not, so we wait until there is an aggregation to


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
@@ -75,11 +75,16 @@ def test_mixed_dtypes_groupby():
 
     n = 1
 
-    by_values = ["col1", lambda x: x % 2, pandas_df.col1]
+    by_values = [
+        ("col1", "col1"),
+        (lambda x: x % 2, lambda x: x % 2),
+        (ray_df.col1, pandas_df.col1),
+        (pd.Grouper("col1", sort=True), pandas.Grouper("col1", sort=True)),
+    ]
 
     for by in by_values:
-        ray_groupby = ray_df.groupby(by=by)
-        pandas_groupby = pandas_df.groupby(by=by)
+        ray_groupby = ray_df.groupby(by=by[0])
+        pandas_groupby = pandas_df.groupby(by=by[1])
 
         ray_groupby_equals_pandas(ray_groupby, pandas_groupby)
         test_ngroups(ray_groupby, pandas_groupby)