Repository: modin-project/modin

Issue:
New API: `combine` for Series and DataFrame
It should be relatively easy to add implementation for `combine` for both Series and DataFrame. It can reuse a lot of the logic in the `binary_op` utitlies.


Discussion:

Resolving patch:
diff --git a/modin/pandas/base.py b/modin/pandas/base.py
--- a/modin/pandas/base.py
+++ b/modin/pandas/base.py
@@ -190,16 +190,21 @@ def _validate_other(
                 )
         return result
 
-    def _binary_op(self, op, other, axis=None, **kwargs):
-        axis = self._get_axis_number(axis) if axis is not None else 1
+    def _binary_op(self, op, other, **kwargs):
+        # _axis indicates the operator will use the default axis
+        if kwargs.pop("_axis", None) is None:
+            if kwargs.get("axis", None) is not None:
+                kwargs["axis"] = axis = self._get_axis_number(kwargs.get("axis", None))
+            else:
+                kwargs["axis"] = axis = 1
+        else:
+            axis = 0
         if kwargs.get("level", None) is not None:
             return self._default_to_pandas(
-                getattr(getattr(pandas, self.__name__), op), other, axis=axis, **kwargs
+                getattr(getattr(pandas, self.__name__), op), other, **kwargs
             )
         other = self._validate_other(other, axis, numeric_or_object_only=True)
-        new_query_compiler = self._query_compiler.binary_op(
-            op, other=other, axis=axis, **kwargs
-        )
+        new_query_compiler = self._query_compiler.binary_op(op, other=other, **kwargs)
         return self._create_or_update_from_compiler(new_query_compiler)
 
     def _default_to_pandas(self, op, *args, **kwargs):
@@ -705,8 +710,8 @@ def clip_upper(self, threshold, axis=None, inplace=False):
         return self.clip(upper=threshold, axis=axis, inplace=inplace)
 
     def combine(self, other, func, fill_value=None, **kwargs):
-        return self._default_to_pandas(
-            "combine", other, func, fill_value=fill_value, **kwargs
+        return self._binary_op(
+            "combine", other, _axis=0, func=func, fill_value=fill_value, **kwargs
         )
 
     def combine_first(self, other):
diff --git a/modin/pandas/series.py b/modin/pandas/series.py
--- a/modin/pandas/series.py
+++ b/modin/pandas/series.py
@@ -465,7 +465,9 @@ def between(self, left, right, inclusive=True):
         )
 
     def combine(self, other, func, fill_value=None):
-        return super(Series, self).combine(other, func, fill_value=fill_value)
+        return super(Series, self).combine(
+            other, lambda s1, s2: s1.combine(s2, func, fill_value=fill_value)
+        )
 
     def compound(self, axis=None, skipna=None, level=None):
         return self._default_to_pandas(


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
@@ -2220,13 +2220,6 @@ def test_boxplot(self, data):
 
         assert modin_df.boxplot() == to_pandas(modin_df).boxplot()
 
-    def test_combine(self):
-        df1 = pd.DataFrame({"A": [0, 0], "B": [4, 4]})
-        df2 = pd.DataFrame({"A": [1, 1], "B": [3, 3]})
-
-        with pytest.warns(UserWarning):
-            df1.combine(df2, lambda s1, s2: s1 if s1.sum() < s2.sum() else s2)
-
     def test_combine_first(self):
         df1 = pd.DataFrame({"A": [None, 0], "B": [None, 4]})
         df2 = pd.DataFrame({"A": [1, 1], "B": [3, 3]})
@@ -5312,6 +5305,18 @@ def test_constructor(self, data):
 
 
 class TestDataFrameJoinSort:
+    @pytest.mark.parametrize("data", test_data_values, ids=test_data_keys)
+    def test_combine(self, data):
+        pandas_df = pandas.DataFrame(data)
+        modin_df = pd.DataFrame(data)
+
+        modin_df.combine(
+            modin_df + 1, lambda s1, s2: s1 if s1.count() < s2.count() else s2
+        )
+        pandas_df.combine(
+            pandas_df + 1, lambda s1, s2: s1 if s1.count() < s2.count() else s2
+        )
+
     def test_join(self):
         frame_data = {
             "col1": [0, 1, 2, 3],
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
@@ -944,8 +944,7 @@ def test_clip_upper(request, data):
 def test_combine(data):
     modin_series, _ = create_test_series(data)  # noqa: F841
     modin_series2 = modin_series % (max(modin_series) // 2)
-    with pytest.warns(UserWarning):
-        modin_series.combine(modin_series2, lambda s1, s2: s1 if s1 < s2 else s2)
+    modin_series.combine(modin_series2, lambda s1, s2: s1 if s1 < s2 else s2)
 
 
 @pytest.mark.parametrize("data", test_data_values, ids=test_data_keys)