diff --git a/pandas/core/arrays/masked.py b/pandas/core/arrays/masked.py index 57efde1a928bc..7261fb2f082b3 100644 --- a/pandas/core/arrays/masked.py +++ b/pandas/core/arrays/masked.py @@ -924,6 +924,9 @@ def _maybe_mask_result( return IntegerArray(result, mask, copy=False) + elif result.dtype == object: + result[mask] = self.dtype.na_value + return result else: result[mask] = np.nan return result diff --git a/pandas/core/ops/array_ops.py b/pandas/core/ops/array_ops.py index ecd2e2e4963d3..329b4e089b3eb 100644 --- a/pandas/core/ops/array_ops.py +++ b/pandas/core/ops/array_ops.py @@ -261,6 +261,10 @@ def arithmetic_op(left: ArrayLike, right: Any, op): # and `maybe_prepare_scalar_for_op` has already been called on `right` # We need to special-case datetime64/timedelta64 dtypes (e.g. because numpy # casts integer dtypes to timedelta64 when operating with timedelta64 - GH#22390) + if isinstance(right, list): + # GH#62423 + right = np.array(right) + right = ensure_wrapped_if_datetimelike(right) if ( should_extension_dispatch(left, right) @@ -311,6 +315,7 @@ def comparison_op(left: ArrayLike, right: Any, op) -> ArrayLike: # We don't catch tuple here bc we may be comparing e.g. MultiIndex # to a tuple that represents a single entry, see test_compare_tuple_strs rvalues = np.asarray(rvalues) + rvalues = ensure_wrapped_if_datetimelike(rvalues) if isinstance(rvalues, (np.ndarray, ABCExtensionArray)): # TODO: make this treatment consistent across ops and classes. diff --git a/pandas/core/ops/common.py b/pandas/core/ops/common.py index e0aa4f44fe2be..c3b6eb7af560b 100644 --- a/pandas/core/ops/common.py +++ b/pandas/core/ops/common.py @@ -7,10 +7,13 @@ from functools import wraps from typing import TYPE_CHECKING +import numpy as np + from pandas._libs.lib import item_from_zerodim from pandas._libs.missing import is_matching_na from pandas.core.dtypes.generic import ( + ABCExtensionArray, ABCIndex, ABCSeries, ) @@ -67,6 +70,15 @@ def new_method(self, other): other = item_from_zerodim(other) + if isinstance(self, ABCExtensionArray): + if isinstance(other, list): + # See GH#62423 + other = np.array(other) + if other.dtype.kind in "mM": + from pandas.core.construction import ensure_wrapped_if_datetimelike + + other = ensure_wrapped_if_datetimelike(other) + return method(self, other) # error: Incompatible return value type (got "Callable[[Any, Any], Any]", diff --git a/pandas/tests/arrays/integer/test_arithmetic.py b/pandas/tests/arrays/integer/test_arithmetic.py index e16ab6f23b417..119e8dfa0717e 100644 --- a/pandas/tests/arrays/integer/test_arithmetic.py +++ b/pandas/tests/arrays/integer/test_arithmetic.py @@ -201,10 +201,6 @@ def test_error_invalid_values(data, all_arithmetic_operators): ]: # (data[~data.isna()] >= 0).all(): res = ops(str_ser) expected = pd.Series(["foo" * x for x in data], index=s.index) - expected = expected.fillna(np.nan) - # TODO: doing this fillna to keep tests passing as we make - # assert_almost_equal stricter, but the expected with pd.NA seems - # more-correct than np.nan here. tm.assert_series_equal(res, expected) else: with tm.external_error_raised(TypeError): diff --git a/pandas/tests/indexes/categorical/test_category.py b/pandas/tests/indexes/categorical/test_category.py index b23e40f4d7f97..12f0a591c706f 100644 --- a/pandas/tests/indexes/categorical/test_category.py +++ b/pandas/tests/indexes/categorical/test_category.py @@ -329,6 +329,7 @@ def test_disallow_addsub_ops(self, func, op_name): f"cannot perform {op_name} with this index type: CategoricalIndex", "can only concatenate list", rf"unsupported operand type\(s\) for [\+-]: {cat_or_list}", + "Object with dtype category cannot perform the numpy op ", ] ) with pytest.raises(TypeError, match=msg): diff --git a/pandas/tests/series/test_arithmetic.py b/pandas/tests/series/test_arithmetic.py index 8b6ab5f18dee1..22bd1868cd0f4 100644 --- a/pandas/tests/series/test_arithmetic.py +++ b/pandas/tests/series/test_arithmetic.py @@ -10,7 +10,6 @@ import pytest from pandas._libs import lib -from pandas.compat._optional import import_optional_dependency import pandas as pd from pandas import ( @@ -26,7 +25,6 @@ import pandas._testing as tm from pandas.core import ops from pandas.core.computation import expressions as expr -from pandas.util.version import Version @pytest.fixture(autouse=True, params=[0, 1000000], ids=["numexpr", "python"]) @@ -336,36 +334,25 @@ def test_mask_div_propagate_na_for_non_na_dtype(self): result = ser2 / ser1 tm.assert_series_equal(result, expected) - @pytest.mark.parametrize("val, dtype", [(3, "Int64"), (3.5, "Float64")]) - def test_add_list_to_masked_array(self, val, dtype): + @pytest.mark.parametrize("val", [3, 3.5]) + def test_add_list_to_masked_array(self, val): # GH#22962 ser = Series([1, None, 3], dtype="Int64") result = ser + [1, None, val] - expected = Series([2, None, 3 + val], dtype=dtype) + expected = Series([2, pd.NA, 3 + val], dtype=object) tm.assert_series_equal(result, expected) result = [1, None, val] + ser tm.assert_series_equal(result, expected) - def test_add_list_to_masked_array_boolean(self, request): + def test_add_list_to_masked_array_boolean(self): # GH#22962 - ne = import_optional_dependency("numexpr", errors="ignore") - warning = ( - UserWarning - if request.node.callspec.id == "numexpr" - and ne - and Version(ne.__version__) < Version("2.13.1") - else None - ) ser = Series([True, None, False], dtype="boolean") - msg = "operator is not supported by numexpr for the bool dtype" - with tm.assert_produces_warning(warning, match=msg): - result = ser + [True, None, True] - expected = Series([True, None, True], dtype="boolean") + result = ser + [True, None, True] + expected = Series([2, pd.NA, 1], dtype=object) tm.assert_series_equal(result, expected) - with tm.assert_produces_warning(warning, match=msg): - result = [True, None, True] + ser + result = [True, None, True] + ser tm.assert_series_equal(result, expected)