Repository: modin-project/modin

Issue:
pandas.Series is accepted in "where" instead of Modin Series type
### System information
- **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**:

Ubuntu 20.04.2 LTS

- **Modin version** (`modin.__version__`):

0.9.0+11.g28cfbc3d.dirty

- **Python version**:

Python 3.8.6

- **Code we can use to reproduce**:

<!--
You can obtain the Modin version with

python -c "import modin; print(modin.__version__)"
-->

### Describe the problem
Code on this line https://github.com/modin-project/modin/blob/master/modin/pandas/dataframe.py#L1963 checks for `pandas.Series` type while it should check for Modin Series type instead.


<!-- Describe the problem clearly here. -->

### Source code / logs
<!-- Include any logs or source code that would be helpful to diagnose the problem. If including tracebacks, please include the full traceback. Large logs and files should be attached. Try to provide a reproducible test case that is the bare minimum necessary to generate the problem. -->



Discussion:
Also this code https://github.com/modin-project/modin/blob/master/modin/backends/pandas/query_compiler.py#L391-L400 seems to be checking for `pandas.Series` type as well because it is in `else` to `isinstance(other, type(self))` which means `other` is of non-Modin type.
@gshimansky looks like the code line you referred to now points at something else instead. Is this still an issue?
I think this is still relevant: 

https://github.com/modin-project/modin/blob/8f3f389949e8c29b72269939318f49671676291b/modin/pandas/dataframe.py#L2279-L2280


Resolving patch:
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
@@ -442,6 +442,10 @@ def where(self, cond, other, **kwargs):
         assert isinstance(
             cond, type(self)
         ), "Must have the same QueryCompiler subclass to perform this operation"
+        # it's doesn't work if `other` is Series._query_compiler because
+        # `n_ary_op` performs columns copartition both for `cond` and `other`.
+        if isinstance(other, type(self)) and other._shape_hint is not None:
+            other = other.to_pandas()
         if isinstance(other, type(self)):
             # Make sure to set join_type=None so the `where` result always has
             # the same row and column labels as `self`.
diff --git a/modin/pandas/dataframe.py b/modin/pandas/dataframe.py
--- a/modin/pandas/dataframe.py
+++ b/modin/pandas/dataframe.py
@@ -2509,7 +2509,7 @@ def where(
         Replace values where the condition is False.
         """
         inplace = validate_bool_kwarg(inplace, "inplace")
-        if isinstance(other, pandas.Series) and axis is None:
+        if isinstance(other, Series) and axis is None:
             raise ValueError("Must specify axis=0 or 1")
         if level is not None:
             if isinstance(other, DataFrame):
@@ -2572,8 +2572,13 @@ def where(
                     "No axis named NoDefault.no_default for object type DataFrame"
                 )
             axis = self._get_axis_number(axis)
-            if isinstance(other, pandas.Series):
-                other = other.reindex(self.index if axis == 0 else self.columns)
+            if isinstance(other, Series):
+                other = other.reindex(
+                    self.index if axis == 0 else self.columns
+                )._query_compiler
+                if other._shape_hint is None:
+                    # To make the query compiler recognizable as a Series at lower levels
+                    other._shape_hint = "column"
             elif is_list_like(other):
                 index = self.index if axis == 0 else self.columns
                 other = pandas.Series(other, index=index)


Test patch:
diff --git a/modin/pandas/test/dataframe/test_join_sort.py b/modin/pandas/test/dataframe/test_join_sort.py
--- a/modin/pandas/test/dataframe/test_join_sort.py
+++ b/modin/pandas/test/dataframe/test_join_sort.py
@@ -726,6 +726,13 @@ def test_where():
     modin_result = modin_df.where(modin_cond_df, -modin_df)
     assert all((to_pandas(modin_result) == pandas_result).all())
 
+    # test case when other is Series
+    other_data = random_state.randn(len(pandas_df))
+    modin_other, pandas_other = pd.Series(other_data), pandas.Series(other_data)
+    pandas_result = pandas_df.where(pandas_cond_df, pandas_other, axis=0)
+    modin_result = modin_df.where(modin_cond_df, modin_other, axis=0)
+    df_equals(modin_result, pandas_result)
+
     # Test that we choose the right values to replace when `other` == `True`
     # everywhere.
     other_data = np.full(shape=pandas_df.shape, fill_value=True)
diff --git a/modin/pandas/test/test_series.py b/modin/pandas/test/test_series.py
--- a/modin/pandas/test/test_series.py
+++ b/modin/pandas/test/test_series.py
@@ -3719,9 +3719,10 @@ def test_where():
     modin_result = modin_series.where(modin_cond_series, -modin_series)
     assert all((to_pandas(modin_result) == pandas_result))
 
-    other = pandas.Series(random_state.randn(100))
-    pandas_result = pandas_series.where(pandas_cond_series, other, axis=0)
-    modin_result = modin_series.where(modin_cond_series, other, axis=0)
+    other_data = random_state.randn(100)
+    modin_other, pandas_other = pd.Series(other_data), pandas.Series(other_data)
+    pandas_result = pandas_series.where(pandas_cond_series, pandas_other, axis=0)
+    modin_result = modin_series.where(modin_cond_series, modin_other, axis=0)
     assert all(to_pandas(modin_result) == pandas_result)
 
     pandas_result = pandas_series.where(pandas_series < 2, True)