diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 4220061b2385b..e83bc9c1448eb 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -2580,7 +2580,6 @@ def __nonzero__(self): # -------------------------------------------------------------------- # Set Operation Methods - @final def _get_reconciled_name_object(self, other): """ If the result of a set operation will be self, diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py index cb0ca335f751f..385b7e487d4c5 100644 --- a/pandas/core/indexes/multi.py +++ b/pandas/core/indexes/multi.py @@ -3593,6 +3593,34 @@ def _union(self, other, sort): def _is_comparable_dtype(self, dtype: DtypeObj) -> bool: return is_object_dtype(dtype) + def _get_reconciled_name_object(self, other): + """ + If the result of a set operation will be self, + return self, unless the names change, in which + case make a shallow copy of self. + """ + names = self._maybe_match_names(other) + if self.names != names: + return self.rename(names) + return self + + def _maybe_match_names(self, other): + """ + Try to find common names to attach to the result of an operation between + a and b. Return a consensus list of names if they match at least partly + or None if they have completely different names. + """ + if len(self.names) != len(other.names): + return None + names = [] + for a_name, b_name in zip(self.names, other.names): + if a_name == b_name: + names.append(a_name) + else: + # TODO: what if they both have np.nan for their names? + names.append(None) + return names + def intersection(self, other, sort=False): """ Form the intersection of two MultiIndex objects. @@ -3616,12 +3644,12 @@ def intersection(self, other, sort=False): """ self._validate_sort_keyword(sort) self._assert_can_do_setop(other) - other, result_names = self._convert_can_do_setop(other) + other, _ = self._convert_can_do_setop(other) if self.equals(other): if self.has_duplicates: - return self.unique().rename(result_names) - return self.rename(result_names) + return self.unique()._get_reconciled_name_object(other) + return self._get_reconciled_name_object(other) return self._intersection(other, sort=sort) diff --git a/pandas/core/ops/common.py b/pandas/core/ops/common.py index a6bcab44e5519..58ad3237c8288 100644 --- a/pandas/core/ops/common.py +++ b/pandas/core/ops/common.py @@ -93,7 +93,7 @@ def _maybe_match_name(a, b): """ Try to find a name to attach to the result of an operation between a and b. If only one of these has a `name` attribute, return that - name. Otherwise return a consensus name if they match of None if + name. Otherwise return a consensus name if they match or None if they have different names. Parameters diff --git a/pandas/tests/indexes/multi/test_setops.py b/pandas/tests/indexes/multi/test_setops.py index f4f602c780187..f9fc425e46696 100644 --- a/pandas/tests/indexes/multi/test_setops.py +++ b/pandas/tests/indexes/multi/test_setops.py @@ -421,6 +421,29 @@ def test_intersect_with_duplicates(tuples, exp_tuples): tm.assert_index_equal(result, expected) +@pytest.mark.parametrize( + "data, names, expected", + [ + ((1,), None, None), + ((1,), ["a"], None), + ((1,), ["b"], None), + ((1, 2), ["c", "d"], [None, None]), + ((1, 2), ["b", "a"], [None, None]), + ((1, 2, 3), ["a", "b", "c"], None), + ((1, 2), ["a", "c"], ["a", None]), + ((1, 2), ["c", "b"], [None, "b"]), + ((1, 2), ["a", "b"], ["a", "b"]), + ((1, 2), [None, "b"], [None, "b"]), + ], +) +def test_maybe_match_names(data, names, expected): + # GH#38323 + mi = pd.MultiIndex.from_tuples([], names=["a", "b"]) + mi2 = pd.MultiIndex.from_tuples([data], names=names) + result = mi._maybe_match_names(mi2) + assert result == expected + + def test_intersection_equal_different_names(): # GH#30302 mi1 = MultiIndex.from_arrays([[1, 2], [3, 4]], names=["c", "b"]) @@ -429,3 +452,11 @@ def test_intersection_equal_different_names(): result = mi1.intersection(mi2) expected = MultiIndex.from_arrays([[1, 2], [3, 4]], names=[None, "b"]) tm.assert_index_equal(result, expected) + + +def test_intersection_different_names(): + # GH#38323 + mi = MultiIndex.from_arrays([[1], [3]], names=["c", "b"]) + mi2 = MultiIndex.from_arrays([[1], [3]]) + result = mi.intersection(mi2) + tm.assert_index_equal(result, mi2)