From 7cf8288d9e670dd4c804028ea254233f38d47ec3 Mon Sep 17 00:00:00 2001 From: Brock Date: Wed, 20 Jan 2021 14:21:52 -0800 Subject: [PATCH] ENH: PeriodIndex setops with incompatible freq cast instead of raise --- doc/source/whatsnew/v1.3.0.rst | 1 + pandas/core/indexes/base.py | 3 ++- pandas/core/indexes/period.py | 29 ---------------------- pandas/tests/indexes/period/test_join.py | 2 +- pandas/tests/indexes/period/test_setops.py | 25 ++++++++----------- pandas/tests/series/test_arithmetic.py | 2 +- 6 files changed, 16 insertions(+), 46 deletions(-) diff --git a/doc/source/whatsnew/v1.3.0.rst b/doc/source/whatsnew/v1.3.0.rst index ce75eda458577..5351fe2bdfffa 100644 --- a/doc/source/whatsnew/v1.3.0.rst +++ b/doc/source/whatsnew/v1.3.0.rst @@ -271,6 +271,7 @@ Interval - Bug in :meth:`IntervalIndex.intersection` and :meth:`IntervalIndex.symmetric_difference` always returning object-dtype when operating with :class:`CategoricalIndex` (:issue:`38653`, :issue:`38741`) - Bug in :meth:`IntervalIndex.intersection` returning duplicates when at least one of both Indexes has duplicates which are present in the other (:issue:`38743`) - :meth:`IntervalIndex.union`, :meth:`IntervalIndex.intersection`, :meth:`IntervalIndex.difference`, and :meth:`IntervalIndex.symmetric_difference` now cast to the appropriate dtype instead of raising ``TypeError`` when operating with another :class:`IntervalIndex` with incompatible dtype (:issue:`39267`) +- :meth:`PeriodIndex.union`, :meth:`PeriodIndex.intersection`, :meth:`PeriodIndex.symmetric_difference`, :meth:`PeriodIndex.difference` now cast to object dtype instead of raising ``IncompatibleFrequency`` when opearting with another :class:`PeriodIndex` with incompatible dtype (:issue:`??`) Indexing ^^^^^^^^ diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 895b6c59e1d5c..bf8e534ce64c0 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -2859,7 +2859,7 @@ def _union(self, other, sort): if sort is None and self.is_monotonic and other.is_monotonic: try: result = self._outer_indexer(lvals, rvals)[0] - except TypeError: + except (TypeError, IncompatibleFrequency): # incomparable objects result = list(lvals) @@ -3163,6 +3163,7 @@ def symmetric_difference(self, other, result_name=None, sort=None): return Index(the_diff, name=result_name) + @final def _assert_can_do_setop(self, other): if not is_list_like(other): raise TypeError("Input must be Index or array-like") diff --git a/pandas/core/indexes/period.py b/pandas/core/indexes/period.py index b9b8f5d2ddca6..48488fd867da7 100644 --- a/pandas/core/indexes/period.py +++ b/pandas/core/indexes/period.py @@ -428,27 +428,6 @@ def insert(self, loc: int, item): return DatetimeIndexOpsMixin.insert(self, loc, item) - def join(self, other, how="left", level=None, return_indexers=False, sort=False): - """ - See Index.join - """ - self._assert_can_do_setop(other) - - if not isinstance(other, PeriodIndex): - return self.astype(object).join( - other, how=how, level=level, return_indexers=return_indexers, sort=sort - ) - - # _assert_can_do_setop ensures we have matching dtype - result = super().join( - other, - how=how, - level=level, - return_indexers=return_indexers, - sort=sort, - ) - return result - # ------------------------------------------------------------------------ # Indexing Methods @@ -610,14 +589,6 @@ def _get_string_slice(self, key: str): # ------------------------------------------------------------------------ # Set Operation Methods - def _assert_can_do_setop(self, other): - super()._assert_can_do_setop(other) - - # *Can't* use PeriodIndexes of different freqs - # *Can* use PeriodIndex/DatetimeIndex - if isinstance(other, PeriodIndex) and self.freq != other.freq: - raise raise_on_incompatible(self, other) - def _setop(self, other, sort, opname: str): """ Perform a set operation by dispatching to the Int64Index implementation. diff --git a/pandas/tests/indexes/period/test_join.py b/pandas/tests/indexes/period/test_join.py index 8a68561dd5819..56ca7a572c49c 100644 --- a/pandas/tests/indexes/period/test_join.py +++ b/pandas/tests/indexes/period/test_join.py @@ -39,6 +39,6 @@ def test_join_does_not_recur(self): def test_join_mismatched_freq_raises(self): index = period_range("1/1/2000", "1/20/2000", freq="D") index3 = period_range("1/1/2000", "1/20/2000", freq="2D") - msg = r".*Input has different freq=2D from PeriodIndex\(freq=D\)" + msg = r".*Input has different freq=2D from Period\(freq=D\)" with pytest.raises(IncompatibleFrequency, match=msg): index.join(index3) diff --git a/pandas/tests/indexes/period/test_setops.py b/pandas/tests/indexes/period/test_setops.py index 40d1263ff70cd..02c007c394ff5 100644 --- a/pandas/tests/indexes/period/test_setops.py +++ b/pandas/tests/indexes/period/test_setops.py @@ -1,7 +1,4 @@ import numpy as np -import pytest - -from pandas._libs.tslibs import IncompatibleFrequency import pandas as pd from pandas import PeriodIndex, date_range, period_range @@ -145,12 +142,12 @@ def test_union_misc(self, sort): tm.assert_index_equal(result, index) assert tm.equalContents(result, index) - # raise if different frequencies + # cast if different frequencies index = period_range("1/1/2000", "1/20/2000", freq="D") index2 = period_range("1/1/2000", "1/20/2000", freq="W-WED") - msg = r"Input has different freq=W-WED from PeriodIndex\(freq=D\)" - with pytest.raises(IncompatibleFrequency, match=msg): - index.union(index2, sort=sort) + result = index.union(index2, sort=sort) + expected = index.astype(object).union(index2.astype(object), sort=sort) + tm.assert_index_equal(result, expected) # TODO: belongs elsewhere def test_union_dataframe_index(self): @@ -178,17 +175,17 @@ def test_intersection(self, sort): tm.assert_index_equal(result, index[10:-5]) assert tm.equalContents(result, index[10:-5]) - # raise if different frequencies + # cast if different frequencies index = period_range("1/1/2000", "1/20/2000", freq="D") index2 = period_range("1/1/2000", "1/20/2000", freq="W-WED") - msg = r"Input has different freq=W-WED from PeriodIndex\(freq=D\)" - with pytest.raises(IncompatibleFrequency, match=msg): - index.intersection(index2, sort=sort) + + result = index.intersection(index2, sort=sort) + expected = pd.Index([], dtype=object) + tm.assert_index_equal(result, expected) index3 = period_range("1/1/2000", "1/20/2000", freq="2D") - msg = r"Input has different freq=2D from PeriodIndex\(freq=D\)" - with pytest.raises(IncompatibleFrequency, match=msg): - index.intersection(index3, sort=sort) + result = index.intersection(index3, sort=sort) + tm.assert_index_equal(result, expected) def test_intersection_cases(self, sort): base = period_range("6/1/2000", "6/30/2000", freq="D", name="idx") diff --git a/pandas/tests/series/test_arithmetic.py b/pandas/tests/series/test_arithmetic.py index 219aaddb116cd..1593cbc987a12 100644 --- a/pandas/tests/series/test_arithmetic.py +++ b/pandas/tests/series/test_arithmetic.py @@ -152,7 +152,7 @@ def test_add_series_with_period_index(self): result = ts + _permute(ts[::2]) tm.assert_series_equal(result, expected) - msg = "Input has different freq=D from PeriodIndex\\(freq=A-DEC\\)" + msg = "Input has different freq=D from Period\\(freq=A-DEC\\)" with pytest.raises(IncompatibleFrequency, match=msg): ts + ts.asfreq("D", how="end")