Repository: modin-project/modin

Issue:
clip throws ValueError
### System information
- **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**: CentOS 7.8
- **Modin version** (`modin.__version__`): 0.14.1
- **Python version**: 3.8.12
- **Code we can use to reproduce**:

import ray
import modin.pandas as pd
import numpy as np

ray.init()
data = np.random.rand(3,3)
df = pd.DataFrame({
  'A' : list( data[0] ),
  'B' : list( data[1] ),
  'C' : list( data[2] ),
})
median = df.median()
df = df.clip( median-0.2, median+0.2, axis=1 )
print( df )


### Describe the problem

When I run the above code, it fails with a ValueError on the truth value of a Series.  This is in modin/pandas/base.py:3748, called from pandas/core/series.py:488.

So it could have something to do with the pandas that I am using, which is version 1.4.2.

The ray version I am using is 1.12.1

### 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. -->

![modin_error](https://user-images.githubusercontent.com/106099183/169860955-024be086-af8e-4f8c-be3c-a1526d985185.png)




Discussion:
If I import pandas and then coerce the Series I'm clipping on to an actual pandas Series, then it seems to work:

median = df.median()
median = pandas.Series( list( median ), index=list( median.index ) )
df = df.clip( median-0.2, median+0.2, axis=1 )
print( df )

So I think it could be something to do with the modin Series not passing for a pandas Series in pandas code.
@mcinnj thank you for reporting this bug! I can reproduce the error at Modin commit 0f70e828ed127229f7160075d2ad06a6b16b1002. Here's a simpler script to reproduce the error:

```python
import modin.pandas as pd
df = pd.DataFrame([[1, 2]])
df.clip(lower=pd.Series([3, 4]), axis=1)
```

Replacing `modin.pandas` with `pandas` gives the clipped frame:

```
   0  1
0  3  4
```

But in Modin I get errror:

<details>
<summary>Show stack trace </summary>

```
  File "/Users/maheshvashishtha/software_sources/modin/modin/core/execution/ray/implementations/pandas_on_ray/partitioning/virtual_partition.py", line 497, in deploy_ray_func
    result = func(*args)
  File "/Users/maheshvashishtha/software_sources/modin/modin/core/dataframe/pandas/partitioning/axis_partition.py", line 160, in deploy_axis_func
    result = func(dataframe, **kwargs)
  File "/Users/maheshvashishtha/software_sources/modin/modin/core/storage_formats/pandas/query_compiler.py", line 1608, in <lambda>
    new_modin_frame = self._modin_frame.fold(axis, lambda df: df.clip(**kwargs))
  File "/usr/local/lib/python3.9/site-packages/pandas/util/_decorators.py", line 311, in wrapper
    return func(*args, **kwargs)
  File "/usr/local/lib/python3.9/site-packages/pandas/core/frame.py", line 10917, in clip
    return super().clip(lower, upper, axis, inplace, *args, **kwargs)
  File "/usr/local/lib/python3.9/site-packages/pandas/core/generic.py", line 7569, in clip
    result = result._clip_with_one_bound(
  File "/usr/local/lib/python3.9/site-packages/pandas/core/generic.py", line 7407, in _clip_with_one_bound
    threshold = align_method_FRAME(self, threshold, axis, flex=None)[1]
  File "/usr/local/lib/python3.9/site-packages/pandas/core/ops/__init__.py", line 282, in align_method_FRAME
    right = to_series(right)
  File "/usr/local/lib/python3.9/site-packages/pandas/core/ops/__init__.py", line 242, in to_series
    right = left._constructor_sliced(right, index=left.columns)
  File "/usr/local/lib/python3.9/site-packages/pandas/core/series.py", line 417, in __init__
    data, index = self._init_dict(data, index, dtype)
  File "/usr/local/lib/python3.9/site-packages/pandas/core/series.py", line 488, in _init_dict
    if data:
  File "/Users/maheshvashishtha/software_sources/modin/modin/pandas/base.py", line 3748, in __nonzero__
    raise ValueError(
ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().
```
</details>

If I can make a quick fix, I'll assign the issue to myself. Otherwise, I'll leave it unassigned.

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
@@ -765,6 +765,10 @@ def clip(self, lower, upper, **kwargs):  # noqa: PR02
         BaseQueryCompiler
             QueryCompiler with values limited by the specified thresholds.
         """
+        if isinstance(lower, BaseQueryCompiler):
+            lower = lower.to_pandas().squeeze(1)
+        if isinstance(upper, BaseQueryCompiler):
+            upper = upper.to_pandas().squeeze(1)
         return DataFrameDefault.register(pandas.DataFrame.clip)(
             self, lower=lower, upper=upper, **kwargs
         )
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
@@ -1601,6 +1601,10 @@ def describe_builder(df, internal_indices=[]):
     diff = Fold.register(pandas.DataFrame.diff)
 
     def clip(self, lower, upper, **kwargs):
+        if isinstance(lower, BaseQueryCompiler):
+            lower = lower.to_pandas().squeeze(1)
+        if isinstance(upper, BaseQueryCompiler):
+            upper = upper.to_pandas().squeeze(1)
         kwargs["upper"] = upper
         kwargs["lower"] = lower
         axis = kwargs.get("axis", 0)
diff --git a/modin/pandas/base.py b/modin/pandas/base.py
--- a/modin/pandas/base.py
+++ b/modin/pandas/base.py
@@ -1055,11 +1055,6 @@ def clip(
         if axis is not None:
             axis = self._get_axis_number(axis)
         self._validate_dtypes(numeric_only=True)
-        if is_list_like(lower) or is_list_like(upper):
-            if axis is None:
-                raise ValueError("Must specify axis = 0 or 1")
-            self._validate_other(lower, axis)
-            self._validate_other(upper, axis)
         inplace = validate_bool_kwarg(inplace, "inplace")
         axis = numpy_compat.function.validate_clip_with_axis(axis, args, kwargs)
         # any np.nan bounds are treated as None
@@ -1067,6 +1062,11 @@ def clip(
             lower = None
         if upper is not None and np.any(np.isnan(upper)):
             upper = None
+        if is_list_like(lower) or is_list_like(upper):
+            if axis is None:
+                raise ValueError("Must specify axis = 0 or 1")
+            lower = self._validate_other(lower, axis)
+            upper = self._validate_other(upper, axis)
         # FIXME: Judging by pandas docs `*args` and `**kwargs` serves only compatibility
         # purpose and does not affect the result, we shouldn't pass them to the query compiler.
         new_query_compiler = self._query_compiler.clip(


Test patch:
diff --git a/modin/pandas/test/dataframe/test_map_metadata.py b/modin/pandas/test/dataframe/test_map_metadata.py
--- a/modin/pandas/test/dataframe/test_map_metadata.py
+++ b/modin/pandas/test/dataframe/test_map_metadata.py
@@ -564,7 +564,8 @@ def test_astype_category_large():
 
 @pytest.mark.parametrize("data", test_data_values, ids=test_data_keys)
 @pytest.mark.parametrize("axis", axis_values, ids=axis_keys)
-def test_clip(request, data, axis):
+@pytest.mark.parametrize("bound_type", ["list", "series"], ids=["list", "series"])
+def test_clip(request, data, axis, bound_type):
     modin_df = pd.DataFrame(data)
     pandas_df = pandas.DataFrame(data)
 
@@ -576,8 +577,6 @@ def test_clip(request, data, axis):
         )
         # set bounds
         lower, upper = np.sort(random_state.random_integers(RAND_LOW, RAND_HIGH, 2))
-        lower_list = random_state.random_integers(RAND_LOW, RAND_HIGH, ind_len)
-        upper_list = random_state.random_integers(RAND_LOW, RAND_HIGH, ind_len)
 
         # test only upper scalar bound
         modin_result = modin_df.clip(None, upper, axis=axis)
@@ -589,14 +588,26 @@ def test_clip(request, data, axis):
         pandas_result = pandas_df.clip(lower, upper, axis=axis)
         df_equals(modin_result, pandas_result)
 
+        lower = random_state.random_integers(RAND_LOW, RAND_HIGH, ind_len)
+        upper = random_state.random_integers(RAND_LOW, RAND_HIGH, ind_len)
+
+        if bound_type == "series":
+            modin_lower = pd.Series(lower)
+            pandas_lower = pandas.Series(lower)
+            modin_upper = pd.Series(upper)
+            pandas_upper = pandas.Series(upper)
+        else:
+            modin_lower = pandas_lower = lower
+            modin_upper = pandas_upper = upper
+
         # test lower and upper list bound on each column
-        modin_result = modin_df.clip(lower_list, upper_list, axis=axis)
-        pandas_result = pandas_df.clip(lower_list, upper_list, axis=axis)
+        modin_result = modin_df.clip(modin_lower, modin_upper, axis=axis)
+        pandas_result = pandas_df.clip(pandas_lower, pandas_upper, axis=axis)
         df_equals(modin_result, pandas_result)
 
         # test only upper list bound on each column
-        modin_result = modin_df.clip(np.nan, upper_list, axis=axis)
-        pandas_result = pandas_df.clip(np.nan, upper_list, axis=axis)
+        modin_result = modin_df.clip(np.nan, modin_upper, axis=axis)
+        pandas_result = pandas_df.clip(np.nan, pandas_upper, axis=axis)
         df_equals(modin_result, pandas_result)
 
         with pytest.raises(ValueError):
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
@@ -1164,8 +1164,11 @@ def test_bool(data):
 
 
 @pytest.mark.parametrize("data", test_data_values, ids=test_data_keys)
-def test_clip(request, data):
-    modin_series, pandas_series = create_test_series(data)
+@pytest.mark.parametrize("bound_type", ["list", "series"], ids=["list", "series"])
+def test_clip_scalar(request, data, bound_type):
+    modin_series, pandas_series = create_test_series(
+        data,
+    )
 
     if name_contains(request.node.name, numeric_dfs):
         # set bounds
@@ -1182,6 +1185,37 @@ def test_clip(request, data):
         df_equals(modin_result, pandas_result)
 
 
+@pytest.mark.parametrize("data", test_data_values, ids=test_data_keys)
+@pytest.mark.parametrize("bound_type", ["list", "series"], ids=["list", "series"])
+def test_clip_sequence(request, data, bound_type):
+    modin_series, pandas_series = create_test_series(
+        data,
+    )
+
+    if name_contains(request.node.name, numeric_dfs):
+        lower = random_state.random_integers(RAND_LOW, RAND_HIGH, len(pandas_series))
+        upper = random_state.random_integers(RAND_LOW, RAND_HIGH, len(pandas_series))
+
+        if bound_type == "series":
+            modin_lower = pd.Series(lower)
+            pandas_lower = pandas.Series(lower)
+            modin_upper = pd.Series(upper)
+            pandas_upper = pandas.Series(upper)
+        else:
+            modin_lower = pandas_lower = lower
+            modin_upper = pandas_upper = upper
+
+        # test lower and upper list bound
+        modin_result = modin_series.clip(modin_lower, modin_upper, axis=0)
+        pandas_result = pandas_series.clip(pandas_lower, pandas_upper)
+        df_equals(modin_result, pandas_result)
+
+        # test only upper list bound
+        modin_result = modin_series.clip(np.nan, modin_upper, axis=0)
+        pandas_result = pandas_series.clip(np.nan, pandas_upper)
+        df_equals(modin_result, pandas_result)
+
+
 @pytest.mark.parametrize("data", test_data_values, ids=test_data_keys)
 def test_combine(data):
     modin_series, _ = create_test_series(data)  # noqa: F841