From b9c723fece8f73c84b16103246510a8bb07ddf4f Mon Sep 17 00:00:00 2001 From: Brock Date: Wed, 16 Jul 2025 14:40:47 -0700 Subject: [PATCH] API: Index.__cmp__(Series) return NotImplemented --- doc/source/whatsnew/v3.0.0.rst | 1 + pandas/core/ops/common.py | 16 +++++----------- pandas/tests/arithmetic/common.py | 9 ++------- pandas/tests/arithmetic/test_datetime64.py | 11 +++++++++-- pandas/tests/indexes/multi/test_equivalence.py | 4 ++-- pandas/tests/indexes/test_old_base.py | 4 ++-- 6 files changed, 21 insertions(+), 24 deletions(-) diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index f2650a64d2c59..461c2f49cf7a5 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -414,6 +414,7 @@ Other API changes - Index set operations (like union or intersection) will now ignore the dtype of an empty ``RangeIndex`` or empty ``Index`` with object dtype when determining the dtype of the resulting Index (:issue:`60797`) +- Comparison operations between :class:`Index` and :class:`Series` now consistently return :class:`Series` regardless of which object is on the left or right (:issue:`36759`) - Numpy functions like ``np.isinf`` that return a bool dtype when called on a :class:`Index` object now return a bool-dtype :class:`Index` instead of ``np.ndarray`` (:issue:`52676`) .. --------------------------------------------------------------------------- diff --git a/pandas/core/ops/common.py b/pandas/core/ops/common.py index 5cbe1c421e05a..e0aa4f44fe2be 100644 --- a/pandas/core/ops/common.py +++ b/pandas/core/ops/common.py @@ -56,20 +56,14 @@ def _unpack_zerodim_and_defer(method: F, name: str) -> F: ------- method """ - stripped_name = name.removeprefix("__").removesuffix("__") - is_cmp = stripped_name in {"eq", "ne", "lt", "le", "gt", "ge"} @wraps(method) def new_method(self, other): - if is_cmp and isinstance(self, ABCIndex) and isinstance(other, ABCSeries): - # For comparison ops, Index does *not* defer to Series - pass - else: - prio = getattr(other, "__pandas_priority__", None) - if prio is not None: - if prio > self.__pandas_priority__: - # e.g. other is DataFrame while self is Index/Series/EA - return NotImplemented + prio = getattr(other, "__pandas_priority__", None) + if prio is not None: + if prio > self.__pandas_priority__: + # e.g. other is DataFrame while self is Index/Series/EA + return NotImplemented other = item_from_zerodim(other) diff --git a/pandas/tests/arithmetic/common.py b/pandas/tests/arithmetic/common.py index 0730729e2fd94..7ea9d2b0ee23a 100644 --- a/pandas/tests/arithmetic/common.py +++ b/pandas/tests/arithmetic/common.py @@ -111,24 +111,19 @@ def xbox2(x): return x.astype(bool) return x - # rev_box: box to use for reversed comparisons - rev_box = xbox - if isinstance(right, Index) and isinstance(left, Series): - rev_box = np.array - result = xbox2(left == right) expected = xbox(np.zeros(result.shape, dtype=np.bool_)) tm.assert_equal(result, expected) result = xbox2(right == left) - tm.assert_equal(result, rev_box(expected)) + tm.assert_equal(result, xbox(expected)) result = xbox2(left != right) tm.assert_equal(result, ~expected) result = xbox2(right != left) - tm.assert_equal(result, rev_box(~expected)) + tm.assert_equal(result, xbox(~expected)) msg = "|".join( [ diff --git a/pandas/tests/arithmetic/test_datetime64.py b/pandas/tests/arithmetic/test_datetime64.py index d439ff723b355..8cf45b7729b70 100644 --- a/pandas/tests/arithmetic/test_datetime64.py +++ b/pandas/tests/arithmetic/test_datetime64.py @@ -770,11 +770,18 @@ def test_dti_cmp_tdi_tzawareness(self, other): result = dti == other expected = np.array([False] * 10) - tm.assert_numpy_array_equal(result, expected) + if isinstance(other, Series): + tm.assert_series_equal(result, Series(expected, index=other.index)) + else: + tm.assert_numpy_array_equal(result, expected) result = dti != other expected = np.array([True] * 10) - tm.assert_numpy_array_equal(result, expected) + if isinstance(other, Series): + tm.assert_series_equal(result, Series(expected, index=other.index)) + else: + tm.assert_numpy_array_equal(result, expected) + msg = "Invalid comparison between" with pytest.raises(TypeError, match=msg): dti < other diff --git a/pandas/tests/indexes/multi/test_equivalence.py b/pandas/tests/indexes/multi/test_equivalence.py index 9babbd5b8d56d..ca155b0e3639d 100644 --- a/pandas/tests/indexes/multi/test_equivalence.py +++ b/pandas/tests/indexes/multi/test_equivalence.py @@ -64,8 +64,8 @@ def test_equals_op(idx): with pytest.raises(ValueError, match="Lengths must match"): index_a == series_b - tm.assert_numpy_array_equal(index_a == series_a, expected1) - tm.assert_numpy_array_equal(index_a == series_c, expected2) + tm.assert_series_equal(index_a == series_a, Series(expected1)) + tm.assert_series_equal(index_a == series_c, Series(expected2)) # cases where length is 1 for one of them with pytest.raises(ValueError, match="Lengths must match"): diff --git a/pandas/tests/indexes/test_old_base.py b/pandas/tests/indexes/test_old_base.py index 5f36b8c3f5dbf..3ba19b2a4b254 100644 --- a/pandas/tests/indexes/test_old_base.py +++ b/pandas/tests/indexes/test_old_base.py @@ -560,8 +560,8 @@ def test_equals_op(self, simple_index): with pytest.raises(ValueError, match=msg): index_a == series_b - tm.assert_numpy_array_equal(index_a == series_a, expected1) - tm.assert_numpy_array_equal(index_a == series_c, expected2) + tm.assert_series_equal(index_a == series_a, Series(expected1)) + tm.assert_series_equal(index_a == series_c, Series(expected2)) # cases where length is 1 for one of them with pytest.raises(ValueError, match="Lengths must match"):