diff --git a/doc/source/whatsnew/v1.3.0.rst b/doc/source/whatsnew/v1.3.0.rst index cc1b1df3c3252..e2d84ed3056ae 100644 --- a/doc/source/whatsnew/v1.3.0.rst +++ b/doc/source/whatsnew/v1.3.0.rst @@ -321,7 +321,7 @@ I/O Period ^^^^^^ - +- Comparisons of :class:`Period` objects or :class:`Index`, :class:`Series`, or :class:`DataFrame` with mismatched ``PeriodDtype`` now behave like other mismatched-type comparisons, returning ``False`` for equals, ``True`` for not-equal, and raising ``TypeError`` for inequality checks (:issue:`??`) - - diff --git a/pandas/_libs/tslibs/period.pyx b/pandas/_libs/tslibs/period.pyx index 5d3ad559ea718..e6014a72b0192 100644 --- a/pandas/_libs/tslibs/period.pyx +++ b/pandas/_libs/tslibs/period.pyx @@ -1541,6 +1541,10 @@ cdef class _Period(PeriodMixin): def __richcmp__(self, other, op): if is_period_object(other): if other.freq != self.freq: + if op == Py_EQ: + return False + elif op == Py_NE: + return True msg = DIFFERENT_FREQ.format(cls=type(self).__name__, own_freq=self.freqstr, other_freq=other.freqstr) diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index ef93e80d83d04..b2629e606f8f5 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -21,6 +21,7 @@ from pandas._libs import algos, lib from pandas._libs.tslibs import ( BaseOffset, + IncompatibleFrequency, NaT, NaTType, Period, @@ -441,7 +442,7 @@ def _validate_comparison_value(self, other): try: # GH#18435 strings get a pass from tzawareness compat other = self._scalar_from_string(other) - except ValueError: + except (ValueError, IncompatibleFrequency): # failed to parse as Timestamp/Timedelta/Period raise InvalidComparison(other) @@ -451,7 +452,7 @@ def _validate_comparison_value(self, other): other = self._scalar_type(other) # type: ignore[call-arg] try: self._check_compatible_with(other) - except TypeError as err: + except (TypeError, IncompatibleFrequency) as err: # e.g. tzawareness mismatch raise InvalidComparison(other) from err @@ -465,7 +466,7 @@ def _validate_comparison_value(self, other): try: other = self._validate_listlike(other, allow_object=True) self._check_compatible_with(other) - except TypeError as err: + except (TypeError, IncompatibleFrequency) as err: if is_object_dtype(getattr(other, "dtype", None)): # We will have to operate element-wise pass diff --git a/pandas/tests/arithmetic/test_period.py b/pandas/tests/arithmetic/test_period.py index c7a99fbb5c1a8..577b8dec1181d 100644 --- a/pandas/tests/arithmetic/test_period.py +++ b/pandas/tests/arithmetic/test_period.py @@ -272,38 +272,38 @@ def test_parr_cmp_pi(self, freq, box_with_array): tm.assert_equal(base <= idx, exp) @pytest.mark.parametrize("freq", ["M", "2M", "3M"]) - def test_parr_cmp_pi_mismatched_freq_raises(self, freq, box_with_array): + def test_parr_cmp_pi_mismatched_freq(self, freq, box_with_array): # GH#13200 # different base freq base = PeriodIndex(["2011-01", "2011-02", "2011-03", "2011-04"], freq=freq) base = tm.box_expected(base, box_with_array) - msg = "Input has different freq=A-DEC from " - with pytest.raises(IncompatibleFrequency, match=msg): + msg = rf"Invalid comparison between dtype=period\[{freq}\] and Period" + with pytest.raises(TypeError, match=msg): base <= Period("2011", freq="A") - with pytest.raises(IncompatibleFrequency, match=msg): + with pytest.raises(TypeError, match=msg): Period("2011", freq="A") >= base # TODO: Could parametrize over boxes for idx? idx = PeriodIndex(["2011", "2012", "2013", "2014"], freq="A") - rev_msg = r"Input has different freq=(M|2M|3M) from PeriodArray\(freq=A-DEC\)" + rev_msg = r"Invalid comparison between dtype=period\[A-DEC\] and PeriodArray" idx_msg = rev_msg if box_with_array in [tm.to_array, pd.array] else msg - with pytest.raises(IncompatibleFrequency, match=idx_msg): + with pytest.raises(TypeError, match=idx_msg): base <= idx # Different frequency - msg = "Input has different freq=4M from " - with pytest.raises(IncompatibleFrequency, match=msg): + msg = rf"Invalid comparison between dtype=period\[{freq}\] and Period" + with pytest.raises(TypeError, match=msg): base <= Period("2011", freq="4M") - with pytest.raises(IncompatibleFrequency, match=msg): + with pytest.raises(TypeError, match=msg): Period("2011", freq="4M") >= base idx = PeriodIndex(["2011", "2012", "2013", "2014"], freq="4M") - rev_msg = r"Input has different freq=(M|2M|3M) from PeriodArray\(freq=4M\)" + rev_msg = r"Invalid comparison between dtype=period\[4M\] and PeriodArray" idx_msg = rev_msg if box_with_array in [tm.to_array, pd.array] else msg - with pytest.raises(IncompatibleFrequency, match=idx_msg): + with pytest.raises(TypeError, match=idx_msg): base <= idx @pytest.mark.parametrize("freq", ["M", "2M", "3M"]) @@ -354,12 +354,13 @@ def test_pi_cmp_nat_mismatched_freq_raises(self, freq): idx1 = PeriodIndex(["2011-01", "2011-02", "NaT", "2011-05"], freq=freq) diff = PeriodIndex(["2011-02", "2011-01", "2011-04", "NaT"], freq="4M") - msg = "Input has different freq=4M from Period(Array|Index)" - with pytest.raises(IncompatibleFrequency, match=msg): + msg = rf"Invalid comparison between dtype=period\[{freq}\] and PeriodArray" + with pytest.raises(TypeError, match=msg): idx1 > diff - with pytest.raises(IncompatibleFrequency, match=msg): - idx1 == diff + result = idx1 == diff + expected = np.array([False, False, False, False], dtype=bool) + tm.assert_numpy_array_equal(result, expected) # TODO: De-duplicate with test_pi_cmp_nat @pytest.mark.parametrize("dtype", [object, None]) diff --git a/pandas/tests/scalar/period/test_period.py b/pandas/tests/scalar/period/test_period.py index 9b87e32510b41..1a52cc945ad09 100644 --- a/pandas/tests/scalar/period/test_period.py +++ b/pandas/tests/scalar/period/test_period.py @@ -34,9 +34,7 @@ def test_construction(self): i4 = Period("2005", freq="M") i5 = Period("2005", freq="m") - msg = r"Input has different freq=M from Period\(freq=A-DEC\)" - with pytest.raises(IncompatibleFrequency, match=msg): - i1 != i4 + assert i1 != i4 assert i4 == i5 i1 = Period.now("Q") @@ -1071,11 +1069,9 @@ def test_comparison_mismatched_freq(self): jan = Period("2000-01", "M") day = Period("2012-01-01", "D") + assert not jan == day + assert jan != day msg = r"Input has different freq=D from Period\(freq=M\)" - with pytest.raises(IncompatibleFrequency, match=msg): - jan == day - with pytest.raises(IncompatibleFrequency, match=msg): - jan != day with pytest.raises(IncompatibleFrequency, match=msg): jan < day with pytest.raises(IncompatibleFrequency, match=msg):