Repository: modin-project/modin

Issue:
Version of __setitem__ that doesn't fall back to Pandas, where key is DataFrame or 2-D array
This covers one of items in #1063.

Turns out this doesn't actually fall back to Pandas, this just fails with exception, so really this is a bug.


Discussion:

Resolving patch:
diff --git a/modin/pandas/dataframe.py b/modin/pandas/dataframe.py
--- a/modin/pandas/dataframe.py
+++ b/modin/pandas/dataframe.py
@@ -33,7 +33,13 @@
 import warnings
 
 from modin.error_message import ErrorMessage
-from .utils import from_pandas, from_non_pandas, to_pandas, _inherit_docstrings
+from .utils import (
+    from_pandas,
+    from_non_pandas,
+    to_pandas,
+    _inherit_docstrings,
+    hashable,
+)
 from .iterator import PartitionIterator
 from .series import Series
 from .base import BasePandasDataset
@@ -2515,7 +2521,7 @@ def __setattr__(self, key, value):
         object.__setattr__(self, key, value)
 
     def __setitem__(self, key, value):
-        if key not in self.columns:
+        if hashable(key) and key not in self.columns:
             # Handle new column case first
             if isinstance(value, Series):
                 if len(self.columns) == 0:
@@ -2554,6 +2560,13 @@ def __setitem__(self, key, value):
 
         if not isinstance(key, str):
 
+            if isinstance(key, DataFrame) or isinstance(key, np.ndarray):
+                if isinstance(key, np.ndarray):
+                    if key.shape != self.shape:
+                        raise ValueError("Array must be same shape as DataFrame")
+                    key = DataFrame(key, columns=self.columns)
+                return self.mask(key, value, inplace=True)
+
             def setitem_without_string_columns(df):
                 # Arrow makes memory-mapped objects immutable, so copy will allow them
                 # to be mutable again.
diff --git a/modin/pandas/utils.py b/modin/pandas/utils.py
--- a/modin/pandas/utils.py
+++ b/modin/pandas/utils.py
@@ -127,3 +127,12 @@ def wrapper(*args, **kwargs):
 
     wrapper.__name__ = func.__name__
     return wrapper
+
+
+def hashable(obj):
+    """Return whether the object is hashable."""
+    try:
+        hash(obj)
+    except TypeError:
+        return False
+    return True


Test patch:
diff --git a/modin/pandas/test/test_dataframe.py b/modin/pandas/test/test_dataframe.py
--- a/modin/pandas/test/test_dataframe.py
+++ b/modin/pandas/test/test_dataframe.py
@@ -5369,6 +5369,33 @@ def test___setitem__(self, data):
 
         df_equals(modin_df, pandas_df)
 
+    def test___setitem__mask(self):
+        # DataFrame mask:
+        data = test_data["int_data"]
+        modin_df = pd.DataFrame(data)
+        pandas_df = pandas.DataFrame(data)
+
+        mean = int((RAND_HIGH + RAND_LOW) / 2)
+        pandas_df[pandas_df > mean] = -50
+        modin_df[modin_df > mean] = -50
+
+        df_equals(modin_df, pandas_df)
+
+        # Array mask:
+        pandas_df = pandas.DataFrame(data)
+        modin_df = pd.DataFrame(data)
+        array = (pandas_df > mean).to_numpy()
+
+        modin_df[array] = -50
+        pandas_df[array] = -50
+
+        df_equals(modin_df, pandas_df)
+
+        # Array mask of wrong size:
+        with pytest.raises(ValueError):
+            array = np.array([[1, 2], [3, 4]])
+            modin_df[array] = 20
+
     @pytest.mark.parametrize(
         "data",
         [