From c3018823c9150ef40fa1b5a0cdc5cd3430273c46 Mon Sep 17 00:00:00 2001 From: Chilin Chiou Date: Sun, 23 Feb 2025 22:00:17 +0800 Subject: [PATCH 1/6] BUG: fix incorrect difference result of periodindex with index --- pandas/core/indexes/period.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pandas/core/indexes/period.py b/pandas/core/indexes/period.py index 0a7a0319bed3a..93448b2e4a5d4 100644 --- a/pandas/core/indexes/period.py +++ b/pandas/core/indexes/period.py @@ -534,6 +534,22 @@ def shift(self, periods: int = 1, freq=None) -> Self: ) return self + periods + def _convert_can_do_setop(self, other): + try: + if isinstance(other, Index): + # Handle empty cases + if other.empty: + return super()._convert_can_do_setop(other) + + # Convert non-PeriodIndex to PeriodIndex + if not isinstance(other, PeriodIndex): + other = PeriodIndex(other, freq=self.freq) + + except (TypeError, ValueError): + return super()._convert_can_do_setop(other) + + return super()._convert_can_do_setop(other) + def period_range( start=None, From 9f8512bfeba0ec833dd2cb3cb08a75b348b60637 Mon Sep 17 00:00:00 2001 From: Chilin Chiou Date: Sun, 23 Feb 2025 22:04:56 +0800 Subject: [PATCH 2/6] TST: add testcase for difference using periodindex with index --- pandas/tests/indexes/period/test_setops.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pandas/tests/indexes/period/test_setops.py b/pandas/tests/indexes/period/test_setops.py index 2fa7e8cd0d2df..2097ef118769f 100644 --- a/pandas/tests/indexes/period/test_setops.py +++ b/pandas/tests/indexes/period/test_setops.py @@ -361,3 +361,16 @@ def test_union_duplicates(self): freq="D", ) tm.assert_index_equal(result, expected) + + def test_difference_periodindex_with_index(self): + # GH#58971 + index1 = period_range("2022-01", periods=5, freq="M") + index2 = pd.Index(["2022-02", "2022-03"]) + + result1 = index1.difference(index2) + expected1 = PeriodIndex(["2022-01", "2022-04", "2022-05"], freq="M") + tm.assert_index_equal(result1, expected1) + + result2 = index2.difference(index1) + expected2 = pd.Index([]) + tm.assert_index_equal(result2, expected2) From bdf98c43730119dbc65b8e7e46080b4cb5556cc7 Mon Sep 17 00:00:00 2001 From: Chilin Chiou Date: Sun, 23 Feb 2025 22:13:10 +0800 Subject: [PATCH 3/6] DOC: Add changelog entry --- doc/source/whatsnew/v3.0.0.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index edd205860b4e4..88b3d2314e52d 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -691,6 +691,7 @@ Indexing - Bug in :meth:`DataFrame.__getitem__` returning modified columns when called with ``slice`` in Python 3.12 (:issue:`57500`) - Bug in :meth:`DataFrame.from_records` throwing a ``ValueError`` when passed an empty list in ``index`` (:issue:`58594`) - Bug in :meth:`MultiIndex.insert` when a new value inserted to a datetime-like level gets cast to ``NaT`` and fails indexing (:issue:`60388`) +- Bug in :meth:`PeriodIndex.difference` producing incorrect results when operating between :class:`PeriodIndex` and :class:`Index` objects (:issue:`58971`) - Bug in printing :attr:`Index.names` and :attr:`MultiIndex.levels` would not escape single quotes (:issue:`60190`) Missing From f1a73632fee3297d3cee54e10a33b4817cbcd79f Mon Sep 17 00:00:00 2001 From: Chilin Chiou Date: Fri, 28 Feb 2025 15:38:34 +0800 Subject: [PATCH 4/6] Refactor PeriodIndex.convert_can_do_setop to improve exception handling --- pandas/core/indexes/period.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/pandas/core/indexes/period.py b/pandas/core/indexes/period.py index 93448b2e4a5d4..486c02ec8ddd5 100644 --- a/pandas/core/indexes/period.py +++ b/pandas/core/indexes/period.py @@ -535,18 +535,14 @@ def shift(self, periods: int = 1, freq=None) -> Self: return self + periods def _convert_can_do_setop(self, other): - try: - if isinstance(other, Index): - # Handle empty cases - if other.empty: - return super()._convert_can_do_setop(other) - - # Convert non-PeriodIndex to PeriodIndex - if not isinstance(other, PeriodIndex): - other = PeriodIndex(other, freq=self.freq) + if not isinstance(other, Index) or isinstance(other, PeriodIndex): + return super()._convert_can_do_setop(other) + # Convert non-PeriodIndex to PeriodIndex + try: + other = PeriodIndex(other, freq=self.freq) except (TypeError, ValueError): - return super()._convert_can_do_setop(other) + pass return super()._convert_can_do_setop(other) From d49cbc093ee474bdb6f0beafb9d673e18aa1c54f Mon Sep 17 00:00:00 2001 From: Chilin Chiou Date: Fri, 28 Feb 2025 17:10:07 +0800 Subject: [PATCH 5/6] Handle empty case --- pandas/core/indexes/period.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pandas/core/indexes/period.py b/pandas/core/indexes/period.py index 486c02ec8ddd5..22300619104a4 100644 --- a/pandas/core/indexes/period.py +++ b/pandas/core/indexes/period.py @@ -535,7 +535,11 @@ def shift(self, periods: int = 1, freq=None) -> Self: return self + periods def _convert_can_do_setop(self, other): - if not isinstance(other, Index) or isinstance(other, PeriodIndex): + if ( + not isinstance(other, Index) + or other.empty + or isinstance(other, PeriodIndex) + ): return super()._convert_can_do_setop(other) # Convert non-PeriodIndex to PeriodIndex From 7322f67da7a043070cb7a5184f54558001b0cd77 Mon Sep 17 00:00:00 2001 From: Chilin Chiou Date: Fri, 28 Feb 2025 21:01:23 +0800 Subject: [PATCH 6/6] Add dtype of expected result in testcase --- pandas/tests/indexes/period/test_setops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/indexes/period/test_setops.py b/pandas/tests/indexes/period/test_setops.py index 2097ef118769f..bba3a60f1c636 100644 --- a/pandas/tests/indexes/period/test_setops.py +++ b/pandas/tests/indexes/period/test_setops.py @@ -372,5 +372,5 @@ def test_difference_periodindex_with_index(self): tm.assert_index_equal(result1, expected1) result2 = index2.difference(index1) - expected2 = pd.Index([]) + expected2 = pd.Index([], dtype=index2.dtype) tm.assert_index_equal(result2, expected2)