Repository: modin-project/modin

Issue:
REFACTOR: Move the pandas query compiler's groupby_agg.is_reduce_fn definition
### Describe the problem

The pandas query compiler's `groupby_agg` method defines [is_reduce_fn](https://github.com/modin-project/modin/blob/cb54844de9e501a2a54c8619a49711477c551b8f/modin/core/storage_formats/pandas/query_compiler.py#L2635), which checks "whether all functions which is defined by `fn` can be implemented via TreeReduce approach." `is_reduce_fn` doesn't use any variables defined within `groupby_agg`, and `groupby_agg` is already very long, so `is_reduce_fn` should be moved outside of `groupby_agg`, possibly to `modin/core/dataframe/algebra/groupby.py`: https://github.com/modin-project/modin/blob/cb54844de9e501a2a54c8619a49711477c551b8f/modin/core/dataframe/algebra/groupby.py#L24





Discussion:

Resolving patch:
diff --git a/modin/core/dataframe/algebra/__init__.py b/modin/core/dataframe/algebra/__init__.py
--- a/modin/core/dataframe/algebra/__init__.py
+++ b/modin/core/dataframe/algebra/__init__.py
@@ -19,10 +19,7 @@
 from .reduce import Reduce
 from .fold import Fold
 from .binary import Binary
-from .groupby import (
-    GroupByReduce,
-    groupby_reduce_functions,
-)
+from .groupby import GroupByReduce, groupby_reduce_functions, is_reduce_function
 
 __all__ = [
     "Operator",
@@ -33,4 +30,5 @@
     "Binary",
     "GroupByReduce",
     "groupby_reduce_functions",
+    "is_reduce_function",
 ]
diff --git a/modin/core/dataframe/algebra/groupby.py b/modin/core/dataframe/algebra/groupby.py
--- a/modin/core/dataframe/algebra/groupby.py
+++ b/modin/core/dataframe/algebra/groupby.py
@@ -13,6 +13,7 @@
 
 """Module houses builder class for GroupByReduce operator."""
 
+from collections.abc import Container
 import pandas
 
 from .tree_reduce import TreeReduce
@@ -484,3 +485,60 @@ def wrapper(df):
     "size": ("size", "sum"),
     "sum": ("sum", "sum"),
 }
+
+
+def _is_reduce_function_with_depth(fn, depth: int = 0):
+    """
+    Check whether all functions defined by `fn` are groupby reductions.
+
+    If true, all functions defined by `fn` can be implemented with TreeReduce.
+    This is a recursive helper function for is_reduce_function.
+
+    Parameters
+    ----------
+    fn : Any
+        Function to test.
+    depth : int, default: 0
+        How many nested containers we are within for this check.
+            - if it's 0, then we're outside of any container, and `fn` could be
+              either function name or container of function names/renamers.
+            - if it's 1, then we're inside container of function
+              names/renamers.`fn` must be either function name or renamer.
+              renamer is some container which length == 2, where the first
+              element is the new column name and the second is the function
+              name.
+
+    Returns
+    -------
+    bool
+        Whether all functions defined by `fn` are reductions.
+    """
+    if not isinstance(fn, str) and isinstance(fn, Container):
+        assert depth == 0 or (
+            depth > 0 and len(fn) == 2
+        ), f"Got the renamer with incorrect length, expected 2 got {len(fn)}."
+        return (
+            all(_is_reduce_function_with_depth(f, depth + 1) for f in fn)
+            if depth == 0
+            else _is_reduce_function_with_depth(fn[1], depth + 1)
+        )
+    return isinstance(fn, str) and fn in groupby_reduce_functions
+
+
+def is_reduce_function(fn):
+    """
+    Check whether all functions defined by `fn` are groupby reductions.
+
+    If true, all functions defined by `fn` can be implemented with TreeReduce.
+
+    Parameters
+    ----------
+    fn : Any
+        Function to test.
+
+    Returns
+    -------
+    bool
+        Whether all functions defined by `fn` are reductions.
+    """
+    return _is_reduce_function_with_depth(fn, depth=0)
diff --git a/modin/core/storage_formats/pandas/query_compiler.py b/modin/core/storage_formats/pandas/query_compiler.py
--- a/modin/core/storage_formats/pandas/query_compiler.py
+++ b/modin/core/storage_formats/pandas/query_compiler.py
@@ -30,7 +30,7 @@
     is_datetime_or_timedelta_dtype,
 )
 from pandas.core.base import DataError
-from collections.abc import Iterable, Container
+from collections.abc import Iterable
 from typing import List, Hashable
 import warnings
 
@@ -51,6 +51,7 @@
     Binary,
     GroupByReduce,
     groupby_reduce_functions,
+    is_reduce_function,
 )
 from modin.core.dataframe.algebra.default2pandas.groupby import GroupBy, GroupByDefault
 
@@ -2632,27 +2633,8 @@ def groupby_agg(
         how="axis_wise",
         drop=False,
     ):
-        def is_reduce_fn(fn, deep_level=0):
-            """Check whether all functions which is defined by `fn` can be implemented via TreeReduce approach."""
-            if not isinstance(fn, str) and isinstance(fn, Container):
-                # `deep_level` parameter specifies the number of nested containers that was met:
-                # - if it's 0, then we're outside of container, `fn` could be either function name
-                #   or container of function names/renamers.
-                # - if it's 1, then we're inside container of function names/renamers. `fn` must be
-                #   either function name or renamer (renamer is some container which length == 2,
-                #   the first element is the new column name and the second is the function name).
-                assert deep_level == 0 or (
-                    deep_level > 0 and len(fn) == 2
-                ), f"Got the renamer with incorrect length, expected 2 got {len(fn)}."
-                return (
-                    all(is_reduce_fn(f, deep_level + 1) for f in fn)
-                    if deep_level == 0
-                    else is_reduce_fn(fn[1], deep_level + 1)
-                )
-            return isinstance(fn, str) and fn in groupby_reduce_functions
-
         if isinstance(agg_func, dict) and all(
-            is_reduce_fn(x) for x in agg_func.values()
+            is_reduce_function(x) for x in agg_func.values()
         ):
             return self._groupby_dict_reduce(
                 by, agg_func, axis, groupby_kwargs, agg_args, agg_kwargs, drop


Test patch: