Repository: mars-project/mars

Issue:
[BUG] infer_index_value is wrong
**Describe the bug**

We have implemented `infer_index_value` as follows, noticing the `out_index = operator(left_index, right_index)` part, it doesn't make any sence to perform `operator` on `index_value`, since operators like `+-*/` are _element-wise_ on `Index` of pandas.

A trivial example to trigger the problem:

```python
df1 = pd.DataFrame(np.arange(10).reshape(5, 2), index=range(5))
df2 = pd.DataFrame(np.arange(10).reshape(5, 2), index=np.arange(5))

x = add(from_pandas(df1), from_pandas(df2))
x.tiles()
```

https://github.com/mars-project/mars/blob/e6594f0b9232b71ac3e6860ed6ca56a72a16f308/mars/dataframe/arithmetic/utils.py#L33-L52

**To Reproduce**
To help us reproducing this bug, please provide information below:
1. Your Python version
2. The version of Mars you use: master
3. Versions of crucial packages, such as numpy, scipy and protobuf
4. Full stack of the error.
5. Minimized code to reproduce the error.

**Expected behavior**

Infer the correct value for `index_value`.

**Additional context**
Add any other context about the problem here.



Discussion:

Resolving patch:
diff --git a/mars/dataframe/arithmetic/core.py b/mars/dataframe/arithmetic/core.py
--- a/mars/dataframe/arithmetic/core.py
+++ b/mars/dataframe/arithmetic/core.py
@@ -702,7 +702,7 @@ def _calc_properties(cls, x1, x2):
             index.value.should_be_monotonic = True
             index_shape = x1.shape[0]
         elif x1.index_value is not None and x2.index_value is not None:
-            index = infer_index_value(x1.index_value, x2.index_value, cls._operator)
+            index = infer_index_value(x1.index_value, x2.index_value)
             index.value.should_be_monotonic = True
             if index.key == x1.index_value.key == x2.index_value.key and \
                     (not np.isnan(x1.shape[0]) or not np.isnan(x2.shape[0])):
diff --git a/mars/dataframe/arithmetic/utils.py b/mars/dataframe/arithmetic/utils.py
--- a/mars/dataframe/arithmetic/utils.py
+++ b/mars/dataframe/arithmetic/utils.py
@@ -12,12 +12,10 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import operator
+import pandas as pd
+from pandas.core.dtypes.cast import find_common_type
 
-try:
-    import pandas as pd
-except ImportError:  # pragma: no cover
-    pass
+import operator
 
 from ..utils import build_empty_df, parse_index
 from ..core import IndexValue
@@ -30,13 +28,12 @@ def infer_dtypes(left_dtypes, right_dtypes, operator):
     return operator(left, right).dtypes
 
 
-def infer_index_value(left_index_value, right_index_value, operator):
+def infer_index_value(left_index_value, right_index_value):
     if isinstance(left_index_value.value, IndexValue.RangeIndex) and \
             isinstance(right_index_value.value, IndexValue.RangeIndex):
         if left_index_value.value.slice == right_index_value.value.slice:
             return left_index_value
-        key = tokenize(left_index_value.key, right_index_value.key,
-                       operator.__name__)
+        key = tokenize(left_index_value.key, right_index_value.key)
         return parse_index(pd.Int64Index([]), key=key)
 
     # when left index and right index is identical, and both of them are elements unique,
@@ -47,8 +44,8 @@ def infer_index_value(left_index_value, right_index_value, operator):
 
     left_index = left_index_value.to_pandas()
     right_index = right_index_value.to_pandas()
-    out_index = operator(left_index, right_index)
-    key = tokenize(left_index_value.key, right_index_value.key, operator.__name__)
+    out_index = pd.Index([], dtype=find_common_type([left_index.dtype, right_index.dtype]))
+    key = tokenize(left_index_value.key, right_index_value.key)
     return parse_index(out_index, key=key)
 
 


Test patch:
diff --git a/mars/dataframe/arithmetic/tests/test_utils.py b/mars/dataframe/arithmetic/tests/test_utils.py
--- a/mars/dataframe/arithmetic/tests/test_utils.py
+++ b/mars/dataframe/arithmetic/tests/test_utils.py
@@ -41,7 +41,7 @@ def testInferIndexValue(self):
 
         ival1 = parse_index(index1)
         ival2 = parse_index(index2)
-        oival = infer_index_value(ival1, ival2, operator.add)
+        oival = infer_index_value(ival1, ival2)
 
         self.assertEqual(oival.key, ival1.key)
         self.assertEqual(oival.key, ival2.key)
@@ -52,7 +52,7 @@ def testInferIndexValue(self):
 
         ival1 = parse_index(index1)
         ival2 = parse_index(index2)
-        oival = infer_index_value(ival1, ival2, operator.add)
+        oival = infer_index_value(ival1, ival2)
 
         self.assertIsInstance(oival.value, IndexValue.Int64Index)
         self.assertNotEqual(oival.key, ival1.key)
@@ -64,7 +64,7 @@ def testInferIndexValue(self):
 
         ival1 = parse_index(index1)
         ival2 = parse_index(index2)
-        oival = infer_index_value(ival1, ival2, operator.add)
+        oival = infer_index_value(ival1, ival2)
 
         self.assertIsInstance(oival.value, IndexValue.Int64Index)
         self.assertEqual(oival.key, ival1.key)
@@ -76,7 +76,7 @@ def testInferIndexValue(self):
 
         ival1 = parse_index(index1)
         ival2 = parse_index(index2)
-        oival = infer_index_value(ival1, ival2, operator.add)
+        oival = infer_index_value(ival1, ival2)
 
         self.assertIsInstance(oival.value, IndexValue.Int64Index)
         self.assertNotEqual(oival.key, ival1.key)
@@ -88,7 +88,7 @@ def testInferIndexValue(self):
 
         ival1 = parse_index(index1)
         ival2 = parse_index(index2)
-        oival = infer_index_value(ival1, ival2, operator.add)
+        oival = infer_index_value(ival1, ival2)
 
         self.assertIsInstance(oival.value, IndexValue.Int64Index)
         self.assertNotEqual(oival.key, ival1.key)
@@ -100,8 +100,31 @@ def testInferIndexValue(self):
 
         ival1 = parse_index(index1)
         ival2 = parse_index(index2)
-        oival = infer_index_value(ival1, ival2, operator.add)
+        oival = infer_index_value(ival1, ival2)
 
         self.assertIsInstance(oival.value, IndexValue.Float64Index)
         self.assertNotEqual(oival.key, ival1.key)
         self.assertNotEqual(oival.key, ival2.key)
+
+        # range index and other index
+        index1 = pd.RangeIndex(1, 4)
+        index2 = pd.Float64Index([2, 3, 4])
+
+        ival1 = parse_index(index1)
+        ival2 = parse_index(index2)
+        oival = infer_index_value(ival1, ival2)
+
+        self.assertIsInstance(oival.value, IndexValue.Float64Index)
+        self.assertNotEqual(oival.key, ival1.key)
+        self.assertNotEqual(oival.key, ival2.key)
+
+        index1 = pd.DatetimeIndex([])
+        index2 = pd.RangeIndex(2)
+
+        ival1 = parse_index(index1)
+        ival2 = parse_index(index2)
+        oival = infer_index_value(ival1, ival2)
+
+        self.assertIsInstance(oival.value, IndexValue.Index)
+        self.assertNotEqual(oival.key, ival1.key)
+        self.assertNotEqual(oival.key, ival2.key)