Skip to content

DEPR: logical operations with dtype-less sequences #52264

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/source/whatsnew/v2.1.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ Deprecations
- Deprecated 'method', 'limit', and 'fill_axis' keywords in :meth:`DataFrame.align` and :meth:`Series.align`, explicitly call ``fillna`` on the alignment results instead (:issue:`51856`)
- Deprecated 'broadcast_axis' keyword in :meth:`Series.align` and :meth:`DataFrame.align`, upcast before calling ``align`` with ``left = DataFrame({col: left for col in right.columns}, index=right.index)`` (:issue:`51856`)
- Deprecated the 'axis' keyword in :meth:`.GroupBy.idxmax`, :meth:`.GroupBy.idxmin`, :meth:`.GroupBy.fillna`, :meth:`.GroupBy.take`, :meth:`.GroupBy.skew`, :meth:`.GroupBy.rank`, :meth:`.GroupBy.cumprod`, :meth:`.GroupBy.cumsum`, :meth:`.GroupBy.cummax`, :meth:`.GroupBy.cummin`, :meth:`.GroupBy.pct_change`, :meth:`GroupBy.diff`, :meth:`.GroupBy.shift`, and :meth:`DataFrameGroupBy.corrwith`; for ``axis=1`` operate on the underlying :class:`DataFrame` instead (:issue:`50405`, :issue:`51046`)
- Deprecated logical operations (``|``, ``&``, ``^``) between pandas objects and dtype-less sequences (e.g. ``list``, ``tuple``), wrap a sequence in a :class:`Series` or numpy array before operating instead (:issue:`51521`)
- Deprecated :meth:`DataFrame.swapaxes` and :meth:`Series.swapaxes`, use :meth:`DataFrame.transpose` or :meth:`Series.transpose` instead (:issue:`51946`)
-

Expand Down
10 changes: 10 additions & 0 deletions pandas/core/ops/array_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
TYPE_CHECKING,
Any,
)
import warnings

import numpy as np

Expand All @@ -22,6 +23,7 @@
ops as libops,
)
from pandas._libs.tslibs import BaseOffset
from pandas.util._exceptions import find_stack_level

from pandas.core.dtypes.cast import (
construct_1d_object_array_from_listlike,
Expand Down Expand Up @@ -416,6 +418,14 @@ def fill_bool(x, left=None):
right = lib.item_from_zerodim(right)
if is_list_like(right) and not hasattr(right, "dtype"):
# e.g. list, tuple
warnings.warn(
"Logical ops (and, or, xor) between Pandas objects and dtype-less "
"sequences (e.g. list, tuple) are deprecated and will raise in a "
"future version. Wrap the object in a Series, Index, or np.array "
"before operating instead.",
FutureWarning,
stacklevel=find_stack_level(),
)
right = construct_1d_object_array_from_listlike(right)

# NB: We assume extract_array has already been called on left and right
Expand Down
11 changes: 10 additions & 1 deletion pandas/tests/series/test_arithmetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -801,6 +801,14 @@ def test_series_ops_name_retention(self, flex, box, names, all_binary_operators)
name = op.__name__.strip("_")
is_logical = name in ["and", "rand", "xor", "rxor", "or", "ror"]

msg = (
r"Logical ops \(and, or, xor\) between Pandas objects and "
"dtype-less sequences"
)
warn = None
if box in [list, tuple] and is_logical:
warn = FutureWarning

right = box(right)
if flex:
if is_logical:
Expand All @@ -809,7 +817,8 @@ def test_series_ops_name_retention(self, flex, box, names, all_binary_operators)
result = getattr(left, name)(right)
else:
# GH#37374 logical ops behaving as set ops deprecated
result = op(left, right)
with tm.assert_produces_warning(warn, match=msg):
result = op(left, right)

assert isinstance(result, Series)
if box in [Index, Series]:
Expand Down
49 changes: 41 additions & 8 deletions pandas/tests/series/test_logical_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,26 +86,39 @@ def test_logical_operators_int_dtype_with_float(self):
# GH#9016: support bitwise op for integer types
s_0123 = Series(range(4), dtype="int64")

warn_msg = (
r"Logical ops \(and, or, xor\) between Pandas objects and "
"dtype-less sequences"
)

msg = "Cannot perform.+with a dtyped.+array and scalar of type"
with pytest.raises(TypeError, match=msg):
s_0123 & np.NaN
with pytest.raises(TypeError, match=msg):
s_0123 & 3.14
msg = "unsupported operand type.+for &:"
with pytest.raises(TypeError, match=msg):
s_0123 & [0.1, 4, 3.14, 2]
with tm.assert_produces_warning(FutureWarning, match=warn_msg):
s_0123 & [0.1, 4, 3.14, 2]
with pytest.raises(TypeError, match=msg):
s_0123 & np.array([0.1, 4, 3.14, 2])
with pytest.raises(TypeError, match=msg):
s_0123 & Series([0.1, 4, -3.14, 2])

def test_logical_operators_int_dtype_with_str(self):
s_1111 = Series([1] * 4, dtype="int8")

warn_msg = (
r"Logical ops \(and, or, xor\) between Pandas objects and "
"dtype-less sequences"
)

msg = "Cannot perform 'and_' with a dtyped.+array and scalar of type"
with pytest.raises(TypeError, match=msg):
s_1111 & "a"
with pytest.raises(TypeError, match="unsupported operand.+for &"):
s_1111 & ["a", "b", "c", "d"]
with tm.assert_produces_warning(FutureWarning, match=warn_msg):
s_1111 & ["a", "b", "c", "d"]

def test_logical_operators_int_dtype_with_bool(self):
# GH#9016: support bitwise op for integer types
Expand All @@ -116,10 +129,16 @@ def test_logical_operators_int_dtype_with_bool(self):
result = s_0123 & False
tm.assert_series_equal(result, expected)

result = s_0123 & [False]
warn_msg = (
r"Logical ops \(and, or, xor\) between Pandas objects and "
"dtype-less sequences"
)
with tm.assert_produces_warning(FutureWarning, match=warn_msg):
result = s_0123 & [False]
tm.assert_series_equal(result, expected)

result = s_0123 & (False,)
with tm.assert_produces_warning(FutureWarning, match=warn_msg):
result = s_0123 & (False,)
tm.assert_series_equal(result, expected)

result = s_0123 ^ False
Expand Down Expand Up @@ -157,8 +176,14 @@ def test_logical_ops_bool_dtype_with_ndarray(self):
left = Series([True, True, True, False, True])
right = [True, False, None, True, np.nan]

msg = (
r"Logical ops \(and, or, xor\) between Pandas objects and "
"dtype-less sequences"
)

expected = Series([True, False, False, False, False])
result = left & right
with tm.assert_produces_warning(FutureWarning, match=msg):
result = left & right
tm.assert_series_equal(result, expected)
result = left & np.array(right)
tm.assert_series_equal(result, expected)
Expand All @@ -168,7 +193,8 @@ def test_logical_ops_bool_dtype_with_ndarray(self):
tm.assert_series_equal(result, expected)

expected = Series([True, True, True, True, True])
result = left | right
with tm.assert_produces_warning(FutureWarning, match=msg):
result = left | right
tm.assert_series_equal(result, expected)
result = left | np.array(right)
tm.assert_series_equal(result, expected)
Expand All @@ -178,7 +204,8 @@ def test_logical_ops_bool_dtype_with_ndarray(self):
tm.assert_series_equal(result, expected)

expected = Series([False, True, True, True, True])
result = left ^ right
with tm.assert_produces_warning(FutureWarning, match=msg):
result = left ^ right
tm.assert_series_equal(result, expected)
result = left ^ np.array(right)
tm.assert_series_equal(result, expected)
Expand Down Expand Up @@ -231,7 +258,13 @@ def test_scalar_na_logical_ops_corners(self):

expected = Series(True, index=s.index)
expected[::2] = False
result = s & list(s)

msg = (
r"Logical ops \(and, or, xor\) between Pandas objects and "
"dtype-less sequences"
)
with tm.assert_produces_warning(FutureWarning, match=msg):
result = s & list(s)
tm.assert_series_equal(result, expected)

def test_scalar_na_logical_ops_corners_aligns(self):
Expand Down