Repository: modin-project/modin

Issue:
HDK: sum|mean|median groupby aggregations fail for boolean columns
### Modin version checks

- [X] I have checked that this issue has not already been reported.

- [X] I have confirmed this bug exists on the latest released version of Modin.

- [X] I have confirmed this bug exists on the main branch of Modin. (In order to do this you can follow [this guide](https://modin.readthedocs.io/en/stable/getting_started/installation.html#installing-from-the-github-master-branch).)


### Reproducible Example

```python
df = pd.DataFrame({"A": [1, 2, 1], "B": [True, True, False]})
print(df.groupby("A").agg("sum"))
print(df.groupby("A").agg("mean"))
print(df.groupby("A").agg("median"))
```


### Issue Description

The code above fails with `AVG|SUM' is only valid on integer and floating point` error and the `median` aggregation is not valid. 

### Expected Behavior

```
   B
A   
1  1
2  1
     B
A     
1  0.5
2  1.0
     B
A     
1  0.5
2  1.0
```

### Error Logs

<details>

```python-traceback

Replace this line with the error backtrace (if applicable).

```

</details>


### Installed Versions

<details>

Replace this line with the output of pd.show_versions()

</details>



Discussion:

Resolving patch:
diff --git a/modin/experimental/core/execution/native/implementations/hdk_on_native/calcite_builder.py b/modin/experimental/core/execution/native/implementations/hdk_on_native/calcite_builder.py
--- a/modin/experimental/core/execution/native/implementations/hdk_on_native/calcite_builder.py
+++ b/modin/experimental/core/execution/native/implementations/hdk_on_native/calcite_builder.py
@@ -45,7 +45,7 @@
 )
 
 from collections import abc
-from pandas.core.dtypes.common import get_dtype
+from pandas.core.dtypes.common import get_dtype, is_bool_dtype
 
 
 class CalciteBuilder:
@@ -581,6 +581,13 @@ def __exit__(self, type, value, traceback):
         bool: "BOOLEAN",
     }
 
+    # The following aggregates require boolean columns to be cast.
+    _bool_cast_aggregates = {
+        "sum": get_dtype(int),
+        "mean": get_dtype(float),
+        "median": get_dtype(float),
+    }
+
     def __init__(self):
         self._input_ctx_stack = []
 
@@ -888,10 +895,29 @@ def _process_groupby(self, op):
         for col in frame._table_cols:
             if col not in op.by:
                 proj_cols.append(col)
-        proj_exprs = [self._ref(frame, col) for col in proj_cols]
+
+        # Cast boolean columns, if required
+        agg_exprs = op.agg_exprs
+        cast_agg = self._bool_cast_aggregates
+        if any(v.agg in cast_agg for v in agg_exprs.values()) and (
+            bool_cols := {
+                c: cast_agg[agg_exprs[c].agg]
+                for c, t in frame.dtypes.items()
+                if is_bool_dtype(t) and agg_exprs[c].agg in cast_agg
+            }
+        ):
+            trans = self._input_ctx()._maybe_copy_and_translate_expr
+            proj_exprs = [
+                trans(frame.ref(c).cast(bool_cols[c]))
+                if c in bool_cols
+                else self._ref(frame, c)
+                for c in proj_cols
+            ]
+        else:
+            proj_exprs = [self._ref(frame, col) for col in proj_cols]
         # Add expressions required for compound aggregates
         compound_aggs = {}
-        for agg, expr in op.agg_exprs.items():
+        for agg, expr in agg_exprs.items():
             if expr.agg in self._compound_aggregates:
                 compound_aggs[agg] = self._compound_aggregates[expr.agg](
                     self, expr.operands[0]
@@ -907,7 +933,7 @@ def _process_groupby(self, op):
         group = [self._ref_idx(frame, col) for col in op.by]
         fields = op.by.copy()
         aggs = []
-        for agg, expr in op.agg_exprs.items():
+        for agg, expr in agg_exprs.items():
             if agg in compound_aggs:
                 extra_aggs = compound_aggs[agg].gen_agg_exprs()
                 fields.extend(extra_aggs.keys())
@@ -922,8 +948,8 @@ def _process_groupby(self, op):
             self._input_ctx().replace_input_node(frame, node, fields)
             proj_cols = op.by.copy()
             proj_exprs = [self._ref(frame, col) for col in proj_cols]
-            proj_cols.extend(op.agg_exprs.keys())
-            for agg in op.agg_exprs:
+            proj_cols.extend(agg_exprs.keys())
+            for agg in agg_exprs:
                 if agg in compound_aggs:
                     proj_exprs.append(compound_aggs[agg].gen_reduce_expr())
                 else:
diff --git a/modin/experimental/core/storage_formats/hdk/query_compiler.py b/modin/experimental/core/storage_formats/hdk/query_compiler.py
--- a/modin/experimental/core/storage_formats/hdk/query_compiler.py
+++ b/modin/experimental/core/storage_formats/hdk/query_compiler.py
@@ -857,9 +857,10 @@ def dtypes(self):
         return self._modin_frame.dtypes
 
 
-_SUPPORTED_NUM_TYPE_CODES = set(np.typecodes["AllInteger"] + np.typecodes["Float"]) - {
-    np.dtype(np.float16).char
-}
+# "?" is the boolean type code.
+_SUPPORTED_NUM_TYPE_CODES = set(
+    np.typecodes["AllInteger"] + np.typecodes["Float"] + "?"
+) - {np.dtype(np.float16).char}
 
 
 def _check_int_or_float(op, dtypes):  # noqa: GL08


Test patch:
diff --git a/modin/experimental/core/execution/native/implementations/hdk_on_native/test/test_dataframe.py b/modin/experimental/core/execution/native/implementations/hdk_on_native/test/test_dataframe.py
--- a/modin/experimental/core/execution/native/implementations/hdk_on_native/test/test_dataframe.py
+++ b/modin/experimental/core/execution/native/implementations/hdk_on_native/test/test_dataframe.py
@@ -841,6 +841,7 @@ class TestGroupby:
         "a": [1, 1, 2, 2, 2, 1],
         "b": [11, 21, 12, 22, 32, 11],
         "c": [101, 201, 202, 202, 302, 302],
+        "d": [True, True, False, True, False, True],
     }
     cols_value = ["a", ["a", "b"]]
 
@@ -908,7 +909,7 @@ def test_groupby_lazy_multiindex(self):
         index = generate_multiindex(len(self.data["a"]))
 
         def groupby(df, *args, **kwargs):
-            df = df + 1
+            df = df[["a", "b", "c"]] + 1
             return df.groupby("a").agg({"b": "size"})
 
         run_and_compare(groupby, data=self.data, constructor_kwargs={"index": index})
@@ -1285,6 +1286,7 @@ class TestAgg:
         "b": [10, 20, None, 20, 10, None],
         "c": [None, 200, None, 400, 500, 600],
         "d": [11, 22, 33, 22, 33, 22],
+        "e": [True, True, False, True, False, True],
     }
     int_data = pandas.DataFrame(data).fillna(0).astype("int").to_dict()