Repository: modin-project/modin
Pull request: PERF-#5573: Don't trigger axes computation in `columnarize` function
Signed-off-by: Anatoly Myachev <anatoly.myachev@intel.com>

<!--
Thank you for your contribution!
Please review the contributing docs: https://modin.readthedocs.io/en/latest/development/contributing.html
if you have questions about contributing.
-->

## What do these changes do?

<!-- Please give a short brief about these changes. -->

- [x] first commit message and PR title follow format outlined [here](https://modin.readthedocs.io/en/latest/development/contributing.html#commit-message-formatting)
  > **_NOTE:_**  If you edit the PR title to match this format, you need to add another commit (even if it's empty) or amend your last commit for the CI job that checks the PR title to pick up the new PR title.
- [x] passes `flake8 modin/ asv_bench/benchmarks scripts/doc_checker.py`
- [x] passes `black --check modin/ asv_bench/benchmarks scripts/doc_checker.py`
- [x] signed commit with `git commit -s` <!-- you can amend your commit with a signature via `git commit -amend -s` -->
- [x] Resolves #5573 <!-- issue must be created for each patch -->
- [x] tests added and passing
- [x] module layout described at `docs/development/architecture.rst` is up-to-date <!-- if you have added, renamed or removed files or directories please update the documentation accordingly -->


Problem statement:
PERF: don't trigger axes computation in `columnarize` function
In some cases, we know in advance that the Query Compiler will contain a column and it does not need to be transposed (what `columnarize` function does, for which it materializes axes).

We can avoid materialization the way `HDK` does. With the help of an additional flag in the query compiler.

Hints from issue discussion:

Commit message: add _shape_hint doc in base qc

Signed-off-by: Anatoly Myachev <anatoly.myachev@intel.com>

Patch to review:
diff --git a/modin/core/storage_formats/base/query_compiler.py b/modin/core/storage_formats/base/query_compiler.py
index 0bbd7bd7108..0c62e1678ed 100644
--- a/modin/core/storage_formats/base/query_compiler.py
+++ b/modin/core/storage_formats/base/query_compiler.py
@@ -106,6 +106,8 @@ class BaseQueryCompiler(ClassLogger, abc.ABC):
         Whether underlying execution engine is designed to be executed in a lazy mode only.
         If True, such QueryCompiler will be handled differently at the front-end in order
         to reduce execution triggering as much as possible.
+    _shape_hint : {"row", "column", None}, default: None
+        Shape hint for frames known to be a column or a row, otherwise None.
 
     Notes
     -----
@@ -152,6 +154,7 @@ def default_to_pandas(self, pandas_op, *args, **kwargs):
     # treated differently.
 
     lazy_execution = False
+    _shape_hint = None
 
     # Metadata modification abstract methods
     def add_prefix(self, prefix, axis=1):
@@ -1068,6 +1071,9 @@ def columnarize(self):
         BaseQueryCompiler
             Transposed new QueryCompiler or self.
         """
+        if self._shape_hint == "column":
+            return self
+
         if len(self.columns) != 1 or (
             len(self.index) == 1 and self.index[0] == MODIN_UNNAMED_SERIES_LABEL
         ):
diff --git a/modin/core/storage_formats/pandas/query_compiler.py b/modin/core/storage_formats/pandas/query_compiler.py
index 75dbc30a6f0..562163e4aa8 100644
--- a/modin/core/storage_formats/pandas/query_compiler.py
+++ b/modin/core/storage_formats/pandas/query_compiler.py
@@ -242,10 +242,13 @@ class PandasQueryCompiler(BaseQueryCompiler):
     ----------
     modin_frame : PandasDataframe
         Modin Frame to query with the compiled queries.
+    shape_hint : {"row", "column", None}, default: None
+        Shape hint for frames known to be a column or a row, otherwise None.
     """
 
-    def __init__(self, modin_frame):
+    def __init__(self, modin_frame, shape_hint=None):
         self._modin_frame = modin_frame
+        self._shape_hint = shape_hint
 
     @property
     def lazy_execution(self):
@@ -2227,7 +2230,7 @@ def getitem_array(self, key):
             return self.getitem_column_array(key)
 
     def getitem_column_array(self, key, numeric=False):
-        # Convert to list for type checking
+        shape_hint = "column" if len(key) == 1 else None
         if numeric:
             new_modin_frame = self._modin_frame.take_2d_labels_or_positional(
                 col_positions=key
@@ -2236,7 +2239,7 @@ def getitem_column_array(self, key, numeric=False):
             new_modin_frame = self._modin_frame.take_2d_labels_or_positional(
                 col_labels=key
             )
-        return self.__constructor__(new_modin_frame)
+        return self.__constructor__(new_modin_frame, shape_hint)
 
     def getitem_row_array(self, key):
         return self.__constructor__(
diff --git a/modin/experimental/core/storage_formats/pyarrow/query_compiler.py b/modin/experimental/core/storage_formats/pyarrow/query_compiler.py
index 74f4585c55d..ae888cedbac 100644
--- a/modin/experimental/core/storage_formats/pyarrow/query_compiler.py
+++ b/modin/experimental/core/storage_formats/pyarrow/query_compiler.py
@@ -55,6 +55,8 @@ class PyarrowQueryCompiler(PandasQueryCompiler):
     ----------
     modin_frame : PyarrowOnRayDataframe
         Modin Frame to query with the compiled queries.
+    shape_hint : {"row", "column", None}, default: None
+        Shape hint for frames known to be a column or a row, otherwise None.
     """
 
     def query(self, expr, **kwargs):


Code review comments:
diff_hunk: @@ -2236,7 +2239,7 @@ def getitem_column_array(self, key, numeric=False):
             new_modin_frame = self._modin_frame.take_2d_labels_or_positional(
                 col_labels=key
             )
-        return self.__constructor__(new_modin_frame)
+        return self.__constructor__(new_modin_frame, shape_hint)
path: modin/core/storage_formats/pandas/query_compiler.py
text: @user1:
```suggestion
        return self.__constructor__(new_modin_frame, shape_hint=shape_hint)
```
If a parameter is one of the kwargs, I prefer to pass it as a keyword arg.

@author:
Do you mean if the parameter is optional?

@user1:
yep