Repository: modin-project/modin

Issue:
modin datetime Series fails when touches pandas timedeltas
### System information
- **OS Platform and Distribution**: Ubuntu 18.04.5 LTS
- **Modin version**: 0.14.0
- **Python version**: 3.8.5
- **Code to reproduce**:
    `modin.pandas.date_range('2000-12-12','2000-12-13').to_series().pipe(modin.pandas.Series) + modin.pandas.to_timedelta([1,2], unit='h')`

i ran into this problem when read excel file and parsed a column with `modin.pandas.to_datetime` (and `pandas.to_datetime` aswell!), the dataframe coulmn became a `modin.pandas.Series`. This series errors out everything i try to do with it.


Edit: i tried to create a DataFrame directly to check whether it is the read_excel that is the culprit. Spoiler: it isnt
```
df = modin.pandas.DataFrame(modin.pandas.date_range('2000-12-12','2000-12-13'))
df[0] + modin.pandas.to_timedelta([1,2], unit='h')
```
`TypeError: Cannot do operation non-numeric dtypes`


Discussion:
@iliya-malecki thank you for reporting this bug. In can reproduce it at Modin commit 5825856a4ee19ff0dd5d1beaf6090bf601d0b411. Here's a copy-pastable script to reproduce the bug:

```python
import modin.pandas as pd
import numpy as np
import pandas

# Make a modin series of dates
s = pd.Series(np.datetime64("2000-12-12"))
# Make a pandas.core.indexes.timedeltas.TimedeltaIndex
deltas = pd.to_timedelta([1], unit="h")
# Adding the Modin series to the deltas gives TypeError
s + deltas
# But adding the pandas series to the deltas gives 
# 0   2000-12-12 01:00:00
# dtype: datetime64[ns]
print(pandas.Series(np.datetime64("2000-12-12")) + deltas)
```

The Modin team will try to fix this bug.

Resolving patch:
diff --git a/modin/pandas/base.py b/modin/pandas/base.py
--- a/modin/pandas/base.py
+++ b/modin/pandas/base.py
@@ -222,10 +222,7 @@ def _validate_other(
         self,
         other,
         axis,
-        numeric_only=False,
-        numeric_or_time_only=False,
-        numeric_or_object_only=False,
-        comparison_dtypes_only=False,
+        dtype_check=False,
         compare_index=False,
     ):
         """
@@ -239,14 +236,8 @@ def _validate_other(
             Specifies axis along which to do validation. When `1` or `None`
             is specified, validation is done along `index`, if `0` is specified
             validation is done along `columns` of `other` frame.
-        numeric_only : bool, default: False
-            Validates that both frames have only numeric dtypes.
-        numeric_or_time_only : bool, default: False
-            Validates that both frames have either numeric or time dtypes.
-        numeric_or_object_only : bool, default: False
-            Validates that both frames have either numeric or object dtypes.
-        comparison_dtypes_only : bool, default: False
-            Validates that both frames have either numeric or time or equal dtypes.
+        dtype_check : bool, default: False
+            Validates that both frames have compatible dtypes.
         compare_index : bool, default: False
             Compare Index if True.
 
@@ -300,22 +291,10 @@ def _validate_other(
             if not self.index.equals(other.index):
                 raise TypeError("Cannot perform operation with non-equal index")
         # Do dtype checking.
-        if numeric_only:
-            if not all(
-                is_numeric_dtype(self_dtype) and is_numeric_dtype(other_dtype)
-                for self_dtype, other_dtype in zip(self._get_dtypes(), other_dtypes)
-            ):
-                raise TypeError("Cannot do operation on non-numeric dtypes")
-        elif numeric_or_object_only:
+        if dtype_check:
             if not all(
                 (is_numeric_dtype(self_dtype) and is_numeric_dtype(other_dtype))
                 or (is_object_dtype(self_dtype) and is_object_dtype(other_dtype))
-                for self_dtype, other_dtype in zip(self._get_dtypes(), other_dtypes)
-            ):
-                raise TypeError("Cannot do operation non-numeric dtypes")
-        elif comparison_dtypes_only:
-            if not all(
-                (is_numeric_dtype(self_dtype) and is_numeric_dtype(other_dtype))
                 or (
                     is_datetime_or_timedelta_dtype(self_dtype)
                     and is_datetime_or_timedelta_dtype(other_dtype)
@@ -323,21 +302,7 @@ def _validate_other(
                 or is_dtype_equal(self_dtype, other_dtype)
                 for self_dtype, other_dtype in zip(self._get_dtypes(), other_dtypes)
             ):
-                raise TypeError(
-                    "Cannot do operation non-numeric objects with numeric objects"
-                )
-        elif numeric_or_time_only:
-            if not all(
-                (is_numeric_dtype(self_dtype) and is_numeric_dtype(other_dtype))
-                or (
-                    is_datetime_or_timedelta_dtype(self_dtype)
-                    and is_datetime_or_timedelta_dtype(other_dtype)
-                )
-                for self_dtype, other_dtype in zip(self._get_dtypes(), other_dtypes)
-            ):
-                raise TypeError(
-                    "Cannot do operation non-numeric objects with numeric objects"
-                )
+                raise TypeError("Cannot do operation with improper dtypes")
         return result
 
     def _validate_function(self, func, on_invalid=None):
@@ -416,7 +381,7 @@ def _binary_op(self, op, other, **kwargs):
             return self._default_to_pandas(
                 getattr(self._pandas_class, op), other, **kwargs
             )
-        other = self._validate_other(other, axis, numeric_or_object_only=True)
+        other = self._validate_other(other, axis, dtype_check=True)
         exclude_list = [
             "__add__",
             "__radd__",


Test patch:
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
@@ -1141,6 +1141,14 @@ def test_between_time():
     )
 
 
+def test_add_series_to_timedeltaindex():
+    # Make a pandas.core.indexes.timedeltas.TimedeltaIndex
+    deltas = pd.to_timedelta([1], unit="h")
+    test_series = create_test_series(np.datetime64("2000-12-12"))
+    eval_general(*test_series, lambda s: s + deltas)
+    eval_general(*test_series, lambda s: s - deltas)
+
+
 @pytest.mark.parametrize("data", test_data_values, ids=test_data_keys)
 def test_bfill(data):
     modin_series, pandas_series = create_test_series(data)