diff --git a/doc/source/whatsnew/v1.1.0.rst b/doc/source/whatsnew/v1.1.0.rst index 4e7bd5a2032a7..e03d2d4376e8a 100644 --- a/doc/source/whatsnew/v1.1.0.rst +++ b/doc/source/whatsnew/v1.1.0.rst @@ -324,7 +324,7 @@ MultiIndex # Common elements are now guaranteed to be ordered by the left side left.intersection(right, sort=False) -- +- Bug in :meth:`Index.join` for MultiIndex when joining level between datetimelike and string(:issue:`26558`) I/O ^^^ diff --git a/pandas/core/dtypes/generic.py b/pandas/core/dtypes/generic.py index 435d80b2c4dfb..caa7a9f127aa3 100644 --- a/pandas/core/dtypes/generic.py +++ b/pandas/core/dtypes/generic.py @@ -28,6 +28,9 @@ def _check(cls, inst) -> bool: ABCTimedeltaIndex = create_pandas_abc_type( "ABCTimedeltaIndex", "_typ", ("timedeltaindex",) ) +ABCDatetimeTimedeltaMixin = create_pandas_abc_type( + "ABCDatetimeTimedeltaMixin", "_typ", ("datetimeindex", "timedeltaindex",) +) ABCPeriodIndex = create_pandas_abc_type("ABCPeriodIndex", "_typ", ("periodindex",)) ABCCategoricalIndex = create_pandas_abc_type( "ABCCategoricalIndex", "_typ", ("categoricalindex",) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 31966489403f4..4432a09b074f8 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -54,6 +54,7 @@ ABCCategorical, ABCDataFrame, ABCDatetimeIndex, + ABCDatetimeTimedeltaMixin, ABCIntervalIndex, ABCMultiIndex, ABCPandasArray, @@ -3408,7 +3409,16 @@ def join(self, other, how="left", level=None, return_indexers=False, sort=False) # have the same levels/names so a simple join if self.names == other.names: - pass + # For datetimelike levels, we maybe convert the corresponding level + for i, (left, right) in enumerate(zip(self.levels, other.levels)): + if isinstance(left, ABCDatetimeTimedeltaMixin): + left, right = left._prepare_for_join(right) + self = self.set_levels(left, i) + other = other.set_levels(right, i) + elif isinstance(right, ABCDatetimeTimedeltaMixin): + right, left = right._prepare_for_join(left) + self = self.set_levels(left, i) + other = other.set_levels(right, i) else: return self._join_multi(other, how=how, return_indexers=return_indexers) diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index 054a64bf3f990..288c0650104b5 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -830,13 +830,7 @@ def join( """ See Index.join """ - if self._is_convertible_to_index_for_join(other): - try: - other = type(self)(other) - except (TypeError, ValueError): - pass - - this, other = self._maybe_utc_convert(other) + this, other = self._prepare_for_join(other) return Index.join( this, other, @@ -846,6 +840,16 @@ def join( sort=sort, ) + def _prepare_for_join(self, other): + if self._is_convertible_to_index_for_join(other): + try: + other = type(self)(other) + except (TypeError, ValueError): + pass + + this, other = self._maybe_utc_convert(other) + return this, other + def _maybe_utc_convert(self, other): this = self if not hasattr(self, "tz"): diff --git a/pandas/tests/indexes/multi/test_join.py b/pandas/tests/indexes/multi/test_join.py index 062fb92c44552..b0e684cb8b04a 100644 --- a/pandas/tests/indexes/multi/test_join.py +++ b/pandas/tests/indexes/multi/test_join.py @@ -103,3 +103,21 @@ def test_join_multi_wrong_order(): tm.assert_index_equal(midx1, join_idx) assert lidx is None tm.assert_numpy_array_equal(ridx, exp_ridx) + + +@pytest.mark.parametrize( + ("data", "func"), [("2020-03-15", pd.to_datetime), ("1d", pd.to_timedelta)] +) +def test_join_mixed_index_datetime_string(data, func, join_type): + # GH 26558 + # Joining multiindex with mixed datetime and string level + mi1 = pd.MultiIndex.from_tuples([(data,)]) + mi2 = pd.MultiIndex.from_tuples([(func(data),)]) + + _, indexer_left, indexer_right = mi1.join(mi2, how=join_type, return_indexers=True) + assert indexer_left is None + assert indexer_right is None + + _, indexer_left, indexer_right = mi2.join(mi1, how=join_type, return_indexers=True) + assert indexer_left is None + assert indexer_right is None