Skip to content
Open
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/v0.24.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1076,6 +1076,7 @@ The affected cases are:
df + arr[[0], :]

.. ipython:: python
:okwarning:

# Comparison operations and arithmetic operations both broadcast.
df == (1, 2)
Expand Down
1 change: 1 addition & 0 deletions doc/source/whatsnew/v3.0.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,7 @@ Other Deprecations
- Deprecated using ``epoch`` date format in :meth:`DataFrame.to_json` and :meth:`Series.to_json`, use ``iso`` instead. (:issue:`57063`)
- Deprecated allowing ``fill_value`` that cannot be held in the original dtype (excepting NA values for integer and bool dtypes) in :meth:`Series.unstack` and :meth:`DataFrame.unstack` (:issue:`12189`, :issue:`53868`)
- Deprecated allowing ``fill_value`` that cannot be held in the original dtype (excepting NA values for integer and bool dtypes) in :meth:`Series.shift` and :meth:`DataFrame.shift` (:issue:`53802`)
- Deprecated arithmetic operations between pandas objects (:class:`DataFrame`, :class:`Series`, :class:`Index`, and pandas-implemented :class:`ExtensionArray` subclasses) and list-likes other than ``list``, ``np.ndarray``, :class:`ExtensionArray`, :class:`Index`, :class:`Series`, :class:`DataFrame`. For e.g. ``tuple`` or ``range``, explicitly cast these to a supported object instead. In a future version, these will be treated as scalar-like for pointwise operation (:issue:`62423`)
- Deprecated slicing on a :class:`Series` or :class:`DataFrame` with a :class:`DatetimeIndex` using a ``datetime.date`` object, explicitly cast to :class:`Timestamp` instead (:issue:`35830`)

.. ---------------------------------------------------------------------------
Expand Down
14 changes: 14 additions & 0 deletions pandas/core/arrays/arrow/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -943,6 +943,20 @@ def _op_method_error_message(self, other, op) -> str:
)

def _evaluate_op_method(self, other, op, arrow_funcs) -> Self:
if (
is_list_like(other)
and not isinstance(other, (np.ndarray, ExtensionArray, list))
and not ops.has_castable_attr(other)
):
warnings.warn(
f"Operation with {type(other).__name__} are deprecated. "
"In a future version these will be treated as scalar-like. "
"To retain the old behavior, explicitly wrap in a Series "
"instead.",
Pandas4Warning,
stacklevel=find_stack_level(),
)

pa_type = self._pa_array.type
other_original = other
other = self._box_pa(other)
Expand Down
16 changes: 16 additions & 0 deletions pandas/core/arrays/boolean.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,25 @@
Self,
cast,
)
import warnings

import numpy as np

from pandas._libs import (
lib,
missing as libmissing,
)
from pandas.errors import Pandas4Warning
from pandas.util._decorators import set_module
from pandas.util._exceptions import find_stack_level

from pandas.core.dtypes.common import is_list_like
from pandas.core.dtypes.dtypes import register_extension_dtype
from pandas.core.dtypes.missing import isna

from pandas.core import ops
from pandas.core.array_algos import masked_accumulations
from pandas.core.arrays import ExtensionArray
from pandas.core.arrays.masked import (
BaseMaskedArray,
BaseMaskedDtype,
Expand Down Expand Up @@ -378,6 +382,18 @@ def _logical_method(self, other, op):
if isinstance(other, BooleanArray):
other, mask = other._data, other._mask
elif is_list_like(other):
if not isinstance(
other, (list, ExtensionArray, np.ndarray)
) and not ops.has_castable_attr(other):
warnings.warn(
f"Operation with {type(other).__name__} are deprecated. "
"In a future version these will be treated as scalar-like. "
"To retain the old behavior, explicitly wrap in a Series "
"instead.",
Pandas4Warning,
stacklevel=find_stack_level(),
)

other = np.asarray(other, dtype="bool")
if other.ndim > 1:
return NotImplemented
Expand Down
14 changes: 14 additions & 0 deletions pandas/core/arrays/datetimelike.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
from pandas.errors import (
AbstractMethodError,
InvalidComparison,
Pandas4Warning,
PerformanceWarning,
)
from pandas.util._decorators import (
Expand Down Expand Up @@ -968,6 +969,19 @@ def _cmp_method(self, other, op):
# TODO: handle 2D-like listlikes
return op(self.ravel(), other.ravel()).reshape(self.shape)

if is_list_like(other):
if not isinstance(
other, (list, np.ndarray, ExtensionArray)
) and not ops.has_castable_attr(other):
warnings.warn(
f"Operation with {type(other).__name__} are deprecated. "
"In a future version these will be treated as scalar-like. "
"To retain the old behavior, explicitly wrap in a Series "
"instead.",
Pandas4Warning,
stacklevel=find_stack_level(),
)

try:
other = self._validate_comparison_value(other)
except InvalidComparison:
Expand Down
19 changes: 18 additions & 1 deletion pandas/core/arrays/interval.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
TypeAlias,
overload,
)
import warnings

import numpy as np

Expand All @@ -38,7 +39,11 @@
npt,
)
from pandas.compat.numpy import function as nv
from pandas.errors import IntCastingNaNError
from pandas.errors import (
IntCastingNaNError,
Pandas4Warning,
)
from pandas.util._exceptions import find_stack_level

from pandas.core.dtypes.cast import (
LossySetitemError,
Expand Down Expand Up @@ -70,6 +75,7 @@
notna,
)

from pandas.core import ops
from pandas.core.algorithms import (
isin,
take,
Expand Down Expand Up @@ -840,6 +846,17 @@ def __setitem__(self, key, value) -> None:
def _cmp_method(self, other, op):
# ensure pandas array for list-like and eliminate non-interval scalars
if is_list_like(other):
if not isinstance(
other, (list, np.ndarray, ExtensionArray)
) and not ops.has_castable_attr(other):
warnings.warn(
f"Operation with {type(other).__name__} are deprecated. "
"In a future version these will be treated as scalar-like. "
"To retain the old behavior, explicitly wrap in a Series "
"instead.",
Pandas4Warning,
stacklevel=find_stack_level(),
)
if len(self) != len(other):
raise ValueError("Lengths must match to compare")
other = pd_array(other)
Expand Down
31 changes: 30 additions & 1 deletion pandas/core/arrays/masked.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,12 @@
IS64,
is_platform_windows,
)
from pandas.errors import AbstractMethodError
from pandas.errors import (
AbstractMethodError,
Pandas4Warning,
)
from pandas.util._decorators import doc
from pandas.util._exceptions import find_stack_level

from pandas.core.dtypes.base import ExtensionDtype
from pandas.core.dtypes.cast import maybe_downcast_to_dtype
Expand Down Expand Up @@ -743,6 +747,20 @@ def _arith_method(self, other, op):
op_name = op.__name__
omask = None

if (
is_list_like(other)
and not isinstance(other, (list, np.ndarray, ExtensionArray))
and not ops.has_castable_attr(other)
):
warnings.warn(
f"Operation with {type(other).__name__} are deprecated. "
"In a future version these will be treated as scalar-like. "
"To retain the old behavior, explicitly wrap in a Series "
"instead.",
Pandas4Warning,
stacklevel=find_stack_level(),
)

if (
not hasattr(other, "dtype")
and is_list_like(other)
Expand Down Expand Up @@ -847,6 +865,17 @@ def _cmp_method(self, other, op) -> BooleanArray:
other, mask = other._data, other._mask

elif is_list_like(other):
if not isinstance(
other, (list, np.ndarray, ExtensionArray)
) and not ops.has_castable_attr(other):
warnings.warn(
f"Operation with {type(other).__name__} are deprecated. "
"In a future version these will be treated as scalar-like. "
"To retain the old behavior, explicitly wrap in a Series "
"instead.",
Pandas4Warning,
stacklevel=find_stack_level(),
)
other = np.asarray(other)
if other.ndim > 1:
raise NotImplementedError("can only perform ops with 1-d structures")
Expand Down
35 changes: 33 additions & 2 deletions pandas/core/arrays/sparse/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@
)
from pandas._libs.tslibs import NaT
from pandas.compat.numpy import function as nv
from pandas.errors import PerformanceWarning
from pandas.errors import (
Pandas4Warning,
PerformanceWarning,
)
from pandas.util._decorators import doc
from pandas.util._exceptions import find_stack_level
from pandas.util._validators import (
Expand Down Expand Up @@ -66,7 +69,10 @@
notna,
)

from pandas.core import arraylike
from pandas.core import (
arraylike,
ops,
)
import pandas.core.algorithms as algos
from pandas.core.arraylike import OpsMixin
from pandas.core.arrays import ExtensionArray
Expand Down Expand Up @@ -1805,6 +1811,18 @@ def _arith_method(self, other, op):
return _wrap_result(op_name, result, self.sp_index, fill)

else:
if not isinstance(
other, (list, np.ndarray, ExtensionArray)
) and not ops.has_castable_attr(other):
warnings.warn(
f"Operation with {type(other).__name__} are deprecated. "
"In a future version these will be treated as scalar-like. "
"To retain the old behavior, explicitly wrap in a Series "
"instead.",
Pandas4Warning,
stacklevel=find_stack_level(),
)

other = np.asarray(other)
with np.errstate(all="ignore"):
if len(self) != len(other):
Expand All @@ -1817,6 +1835,19 @@ def _arith_method(self, other, op):
return _sparse_array_op(self, other, op, op_name)

def _cmp_method(self, other, op) -> SparseArray:
if (
is_list_like(other)
and not isinstance(other, (list, np.ndarray, ExtensionArray))
and not ops.has_castable_attr(other)
):
warnings.warn(
f"Operation with {type(other).__name__} are deprecated. "
"In a future version these will be treated as scalar-like. "
"To retain the old behavior, explicitly wrap in a Series "
"instead.",
Pandas4Warning,
stacklevel=find_stack_level(),
)
if not is_scalar(other) and not isinstance(other, type(self)):
# convert list-like to ndarray
other = np.asarray(other)
Expand Down
11 changes: 11 additions & 0 deletions pandas/core/arrays/string_.py
Original file line number Diff line number Diff line change
Expand Up @@ -1102,6 +1102,17 @@ def _cmp_method(self, other, op):
valid = ~mask

if lib.is_list_like(other):
if not isinstance(
other, (list, ExtensionArray, np.ndarray)
) and not ops.has_castable_attr(other):
warnings.warn(
f"Operation with {type(other).__name__} are deprecated. "
"In a future version these will be treated as scalar-like. "
"To retain the old behavior, explicitly wrap in a Series "
"instead.",
Pandas4Warning,
stacklevel=find_stack_level(),
)
if len(other) != len(self):
# prevent improper broadcasting when other is 2D
raise ValueError(
Expand Down
12 changes: 12 additions & 0 deletions pandas/core/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -8485,6 +8485,18 @@ def to_series(right):
)

elif is_list_like(right) and not isinstance(right, (Series, DataFrame)):
if not isinstance(
right, (np.ndarray, ExtensionArray, Index, list, dict)
) and not ops.has_castable_attr(right):
warnings.warn(
f"Operation with {type(right).__name__} are deprecated. "
"In a future version these will be treated as scalar-like. "
"To retain the old behavior, explicitly wrap in a Series "
"instead.",
Pandas4Warning,
stacklevel=find_stack_level(),
)

# GH#36702. Raise when attempting arithmetic with list of array-like.
if any(is_array_like(el) for el in right):
raise ValueError(
Expand Down
2 changes: 2 additions & 0 deletions pandas/core/ops/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
)
from pandas.core.ops.common import (
get_op_result_name,
has_castable_attr,
unpack_zerodim_and_defer,
)
from pandas.core.ops.docstrings import make_flex_doc
Expand Down Expand Up @@ -71,6 +72,7 @@
"fill_binop",
"get_array_op",
"get_op_result_name",
"has_castable_attr",
"invalid_comparison",
"kleene_and",
"kleene_or",
Expand Down
5 changes: 5 additions & 0 deletions pandas/core/ops/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
from pandas._typing import F


def has_castable_attr(obj) -> bool:
attrs = ["__array__", "__dlpack__", "__arrow_c_array__", "__arrow_c_stream__"]
return any(hasattr(obj, name) for name in attrs)


def unpack_zerodim_and_defer(name: str) -> Callable[[F], F]:
"""
Boilerplate for pandas conventions in arithmetic and comparison methods.
Expand Down
9 changes: 9 additions & 0 deletions pandas/core/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -6063,6 +6063,15 @@ def _flex_method(self, other, op, *, level=None, fill_value=None, axis: Axis = 0
if isinstance(other, Series):
return self._binop(other, op, level=level, fill_value=fill_value)
elif isinstance(other, (np.ndarray, list, tuple)):
if isinstance(other, tuple):
op_name = op.__name__.strip("_")
warnings.warn(
f"Series.{op_name} with a tuple is deprecated and will be "
"treated as scalar-like in a future version. "
"Explicitly wrap in a numpy array instead.",
Pandas4Warning,
stacklevel=find_stack_level(),
)
if len(other) != len(self):
raise ValueError("Lengths must be equal")
other = self._constructor(other, self.index, copy=False)
Expand Down
10 changes: 5 additions & 5 deletions pandas/tests/arithmetic/test_interval.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,19 +207,19 @@ def test_compare_list_like_interval_mixed_closed(
@pytest.mark.parametrize(
"other",
[
(
[
Interval(0, 1),
Interval(Timedelta("1 day"), Timedelta("2 days")),
Interval(4, 5, "both"),
Interval(10, 20, "neither"),
),
(0, 1.5, Timestamp("20170103"), np.nan),
(
],
[0, 1.5, Timestamp("20170103"), np.nan],
[
Timestamp("20170102", tz="US/Eastern"),
Timedelta("2 days"),
"baz",
pd.NaT,
),
],
],
)
def test_compare_list_like_object(self, op, interval_array, other):
Expand Down
10 changes: 8 additions & 2 deletions pandas/tests/arrays/test_datetimes.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,10 +306,16 @@ def test_cmp_dt64_arraylike_tznaive(self, comparison_op):
tuple(right),
right.astype(object),
]:
result = op(arr, other)
depr_msg = "Operation with tuple are deprecated."
warn = None
if isinstance(other, tuple):
warn = Pandas4Warning
with tm.assert_produces_warning(warn, match=depr_msg):
result = op(arr, other)
tm.assert_numpy_array_equal(result, expected)

result = op(other, arr)
with tm.assert_produces_warning(warn, match=depr_msg):
result = op(other, arr)
tm.assert_numpy_array_equal(result, expected)


Expand Down
Loading
Loading