Repository: modin-project/modin

Issue:
Do not convert a Series to numpy in case setting a value via `loc` when the row locator is a Series or booleans
```python
import modin.pandas as pd

df = pd.DataFrame({"a": [1,2,3], "b": [4,5,6], "c": [7,8,9]})
df.loc[df["a"] == 1, "b"] = 555
```

`df["a"] == 1` is converting to a numpy array, which can be too expensive when the length of a Series is large.


Discussion:

Resolving patch:
diff --git a/modin/core/storage_formats/base/query_compiler.py b/modin/core/storage_formats/base/query_compiler.py
--- a/modin/core/storage_formats/base/query_compiler.py
+++ b/modin/core/storage_formats/base/query_compiler.py
@@ -2384,6 +2384,40 @@ def inserter(df, loc, column, value):
 
     # END Abstract insert
 
+    # __setitem__ methods
+    def setitem_bool(self, row_loc, col_loc, item):
+        """
+        Set an item to the given location based on `row_loc` and `col_loc`.
+
+        Parameters
+        ----------
+        row_loc : BaseQueryCompiler
+            Query Compiler holding a Series of booleans.
+        col_loc : label
+            Column label in `self`.
+        item : scalar
+            An item to be set.
+
+        Returns
+        -------
+        BaseQueryCompiler
+            New QueryCompiler with the inserted item.
+
+        Notes
+        -----
+        Currently, this method is only used to set a scalar to the given location.
+        """
+
+        def _set_item(df, row_loc, col_loc, item):
+            df.loc[row_loc.squeeze(axis=1), col_loc] = item
+            return df
+
+        return DataFrameDefault.register(_set_item)(
+            self, row_loc=row_loc, col_loc=col_loc, item=item
+        )
+
+    # END __setitem__ methods
+
     # Abstract drop
     def drop(self, index=None, columns=None, errors: str = "raise"):
         """
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
@@ -2251,6 +2251,25 @@ def applyier(df, internal_indices, other=[], internal_other_indices=[]):
         labels="drop",
     )
 
+    # __setitem__ methods
+    def setitem_bool(self, row_loc, col_loc, item):
+        def _set_item(df, row_loc):
+            df = df.copy()
+            df.loc[row_loc.squeeze(axis=1), col_loc] = item
+            return df
+
+        new_modin_frame = self._modin_frame.broadcast_apply_full_axis(
+            axis=1,
+            func=_set_item,
+            other=row_loc._modin_frame,
+            new_index=self._modin_frame._index_cache,
+            new_columns=self._modin_frame._columns_cache,
+            keep_partitioning=False,
+        )
+        return self.__constructor__(new_modin_frame)
+
+    # END __setitem__ methods
+
     def __validate_bool_indexer(self, indexer):
         if len(indexer) != len(self.index):
             raise ValueError(
diff --git a/modin/pandas/indexing.py b/modin/pandas/indexing.py
--- a/modin/pandas/indexing.py
+++ b/modin/pandas/indexing.py
@@ -813,6 +813,17 @@ def _set_item_existing_loc(self, row_loc, col_loc, item):
         item : modin.pandas.DataFrame, modin.pandas.Series or scalar
             Value that should be assigned to located dataset.
         """
+        if (
+            isinstance(row_loc, Series)
+            and is_boolean_array(row_loc)
+            and is_scalar(item)
+        ):
+            new_qc = self.df._query_compiler.setitem_bool(
+                row_loc._query_compiler, col_loc, item
+            )
+            self.df._update_inplace(new_qc)
+            return
+
         row_lookup, col_lookup = self.qc.get_positions_from_labels(row_loc, col_loc)
         self._setitem_positional(
             row_lookup,


Test patch: