diff --git a/doc/source/whatsnew/v1.3.0.rst b/doc/source/whatsnew/v1.3.0.rst index 57dd1d05a274e..af96269019ca4 100644 --- a/doc/source/whatsnew/v1.3.0.rst +++ b/doc/source/whatsnew/v1.3.0.rst @@ -232,7 +232,7 @@ MultiIndex ^^^^^^^^^^ - Bug in :meth:`DataFrame.drop` raising ``TypeError`` when :class:`MultiIndex` is non-unique and no level is provided (:issue:`36293`) -- +- Bug in :meth:`MultiIndex.equals` incorrectly returning ``True`` when :class:`MultiIndex` containing ``NaN`` even when they are differntly ordered (:issue:`38439`) I/O ^^^ diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py index 1edd98e980a2d..78e7a8516178a 100644 --- a/pandas/core/indexes/multi.py +++ b/pandas/core/indexes/multi.py @@ -3454,13 +3454,17 @@ def equals(self, other: object) -> bool: for i in range(self.nlevels): self_codes = self.codes[i] - self_codes = self_codes[self_codes != -1] + other_codes = other.codes[i] + self_mask = self_codes == -1 + other_mask = other_codes == -1 + if not np.array_equal(self_mask, other_mask): + return False + self_codes = self_codes[~self_mask] self_values = algos.take_nd( np.asarray(self.levels[i]._values), self_codes, allow_fill=False ) - other_codes = other.codes[i] - other_codes = other_codes[other_codes != -1] + other_codes = other_codes[~other_mask] other_values = algos.take_nd( np.asarray(other.levels[i]._values), other_codes, allow_fill=False ) diff --git a/pandas/tests/indexes/multi/test_equivalence.py b/pandas/tests/indexes/multi/test_equivalence.py index c31c2416ff722..bb34760e28d96 100644 --- a/pandas/tests/indexes/multi/test_equivalence.py +++ b/pandas/tests/indexes/multi/test_equivalence.py @@ -209,6 +209,16 @@ def test_equals_missing_values(): assert not result +def test_equals_missing_values_differently_sorted(): + # GH#38439 + mi1 = pd.MultiIndex.from_tuples([(81.0, np.nan), (np.nan, np.nan)]) + mi2 = pd.MultiIndex.from_tuples([(np.nan, np.nan), (81.0, np.nan)]) + assert not mi1.equals(mi2) + + mi2 = pd.MultiIndex.from_tuples([(81.0, np.nan), (np.nan, np.nan)]) + assert mi1.equals(mi2) + + def test_is_(): mi = MultiIndex.from_tuples(zip(range(10), range(10))) assert mi.is_(mi)