Repository: modin-project/modin

Issue:
[REFACTOR] Avoid API level index access in getitem for bool indexer
Currently using the subscription operator for bool Series causes index access in API level. This operation is used in Census and we need it to be lazy.



Discussion:

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
@@ -833,6 +833,23 @@ def quantile_for_list_of_values(self, **kwargs):
     # END Abstract map across rows/columns
 
     # Abstract __getitem__ methods
+    @abc.abstractmethod
+    def getitem_array(self, key):
+        """
+        Get column or row data specified by key.
+
+        Parameters
+        ----------
+        key : BaseQueryCompiler, numpy.ndarray, pandas.Index or list
+            Target numeric indices or labels by which to retrieve data.
+
+        Returns
+        -------
+        BaseQueryCompiler
+            A new Query Compiler.
+        """
+        pass
+
     @abc.abstractmethod
     def getitem_column_array(self, key):
         """Get column data for target labels.
diff --git a/modin/backends/pandas/query_compiler.py b/modin/backends/pandas/query_compiler.py
--- a/modin/backends/pandas/query_compiler.py
+++ b/modin/backends/pandas/query_compiler.py
@@ -13,6 +13,8 @@
 
 import numpy as np
 import pandas
+from pandas.core.common import is_bool_indexer
+from pandas.core.indexing import check_bool_indexer
 from pandas.core.dtypes.common import (
     is_list_like,
     is_numeric_dtype,
@@ -20,6 +22,7 @@
     is_scalar,
 )
 from pandas.core.base import DataError
+import warnings
 
 from modin.backends.base.query_compiler import BaseQueryCompiler
 from modin.error_message import ErrorMessage
@@ -1918,6 +1921,56 @@ def applyier(df, internal_indices, other=[], internal_other_indices=[]):
     # END Map across rows/columns
 
     # __getitem__ methods
+    def getitem_array(self, key):
+        """
+        Get column or row data specified by key.
+
+        Parameters
+        ----------
+        key : PandasQueryCompiler, numpy.ndarray, pandas.Index or list
+            Target numeric indices or labels by which to retrieve data.
+
+        Returns
+        -------
+        PandasQueryCompiler
+            A new Query Compiler.
+        """
+        # TODO: dont convert to pandas for array indexing
+        if isinstance(key, type(self)):
+            key = key.to_pandas().squeeze(axis=1)
+        if is_bool_indexer(key):
+            if isinstance(key, pandas.Series) and not key.index.equals(self.index):
+                warnings.warn(
+                    "Boolean Series key will be reindexed to match DataFrame index.",
+                    PendingDeprecationWarning,
+                    stacklevel=3,
+                )
+            elif len(key) != len(self.index):
+                raise ValueError(
+                    "Item wrong length {} instead of {}.".format(
+                        len(key), len(self.index)
+                    )
+                )
+            key = check_bool_indexer(self.index, key)
+            # We convert to a RangeIndex because getitem_row_array is expecting a list
+            # of indices, and RangeIndex will give us the exact indices of each boolean
+            # requested.
+            key = pandas.RangeIndex(len(self.index))[key]
+            if len(key):
+                return self.getitem_row_array(key)
+            else:
+                return self.from_pandas(
+                    pandas.DataFrame(columns=self.columns), type(self._modin_frame)
+                )
+        else:
+            if any(k not in self.columns for k in key):
+                raise KeyError(
+                    "{} not index".format(
+                        str([k for k in key if k not in self.columns]).replace(",", "")
+                    )
+                )
+            return self.getitem_column_array(key)
+
     def getitem_column_array(self, key, numeric=False):
         """Get column data for target labels.
 
diff --git a/modin/pandas/dataframe.py b/modin/pandas/dataframe.py
--- a/modin/pandas/dataframe.py
+++ b/modin/pandas/dataframe.py
@@ -12,7 +12,7 @@
 # governing permissions and limitations under the License.
 
 import pandas
-from pandas.core.common import apply_if_callable, is_bool_indexer
+from pandas.core.common import apply_if_callable
 from pandas.core.dtypes.common import (
     infer_dtype_from_object,
     is_dict_like,
@@ -20,7 +20,6 @@
     is_numeric_dtype,
 )
 from pandas.core.indexes.api import ensure_index_from_sequences
-from pandas.core.indexing import check_bool_indexer
 from pandas.util._validators import validate_bool_kwarg
 from pandas.io.formats.printing import pprint_thing
 
@@ -2927,8 +2926,12 @@ def _getitem(self, key):
                 return self._getitem_column(key)
         except (KeyError, ValueError, TypeError):
             pass
-        if isinstance(key, (Series, np.ndarray, pandas.Index, list)):
-            return self._getitem_array(key)
+        if isinstance(key, Series):
+            return DataFrame(
+                query_compiler=self._query_compiler.getitem_array(key._query_compiler)
+            )
+        elif isinstance(key, (np.ndarray, pandas.Index, list)):
+            return DataFrame(query_compiler=self._query_compiler.getitem_array(key))
         elif isinstance(key, DataFrame):
             return self.where(key)
         elif is_mi_columns:
@@ -2948,45 +2951,6 @@ def _getitem_column(self, key):
             s._parent_axis = 1
         return s
 
-    def _getitem_array(self, key):
-        # TODO: dont convert to pandas for array indexing
-        if isinstance(key, Series):
-            key = key._to_pandas()
-        if is_bool_indexer(key):
-            if isinstance(key, pandas.Series) and not key.index.equals(self.index):
-                warnings.warn(
-                    "Boolean Series key will be reindexed to match DataFrame index.",
-                    PendingDeprecationWarning,
-                    stacklevel=3,
-                )
-            elif len(key) != len(self.index):
-                raise ValueError(
-                    "Item wrong length {} instead of {}.".format(
-                        len(key), len(self.index)
-                    )
-                )
-            key = check_bool_indexer(self.index, key)
-            # We convert to a RangeIndex because getitem_row_array is expecting a list
-            # of indices, and RangeIndex will give us the exact indices of each boolean
-            # requested.
-            key = pandas.RangeIndex(len(self.index))[key]
-            if len(key):
-                return DataFrame(
-                    query_compiler=self._query_compiler.getitem_row_array(key)
-                )
-            else:
-                return DataFrame(columns=self.columns)
-        else:
-            if any(k not in self.columns for k in key):
-                raise KeyError(
-                    "{} not index".format(
-                        str([k for k in key if k not in self.columns]).replace(",", "")
-                    )
-                )
-            return DataFrame(
-                query_compiler=self._query_compiler.getitem_column_array(key)
-            )
-
     def __getattr__(self, key):
         """After regular attribute access, looks up the name in the columns
 


Test patch: