Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
2 changes: 1 addition & 1 deletion doc/source/whatsnew/v1.1.3.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ including other versions of pandas.

Fixed regressions
~~~~~~~~~~~~~~~~~
-
- Fixed regression in :class:`IntegerArray` unary plus and minus operations raising a ``TypeError`` (:issue:`36063`)

.. ---------------------------------------------------------------------------

Expand Down
9 changes: 9 additions & 0 deletions pandas/core/arrays/integer.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,15 @@ def __init__(self, values: np.ndarray, mask: np.ndarray, copy: bool = False):
)
super().__init__(values, mask, copy=copy)

def __neg__(self):
return type(self)(-self._data, self._mask)

def __pos__(self):
return self

def __abs__(self):
return type(self)(np.sign(self._data) * self._data, self._mask)

@classmethod
def _from_sequence(cls, scalars, dtype=None, copy: bool = False) -> "IntegerArray":
return integer_array(scalars, dtype=dtype, copy=copy)
Expand Down
14 changes: 8 additions & 6 deletions pandas/core/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -1408,16 +1408,18 @@ def __neg__(self):

def __pos__(self):
values = self._values
if is_bool_dtype(values):
arr = values
elif (
is_numeric_dtype(values)
if (
is_bool_dtype(values)
or is_numeric_dtype(values)
or is_timedelta64_dtype(values)
or is_object_dtype(values)
):
arr = operator.pos(values)
arr = values
else:
raise TypeError(f"Unary plus expects numeric dtype, not {values.dtype}")
raise TypeError(
"Unary plus expects bool, numeric, timedelta, "
f"or object dtype, not {values.dtype}"
)
return self.__array_wrap__(arr)

def __invert__(self):
Expand Down
38 changes: 38 additions & 0 deletions pandas/tests/arrays/integer/test_arithmetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,3 +261,41 @@ def test_reduce_to_float(op):
index=pd.Index(["a", "b"], name="A"),
)
tm.assert_frame_equal(result, expected)


@pytest.mark.parametrize("dtype", ["Int64", "Int32", "Int16", "Int8"])
@pytest.mark.parametrize(
"source, target",
[
([1, 2, 3], [-1, -2, -3]),
([1, 2, None], [-1, -2, None]),
([-1, 0, 1], [1, 0, -1]),
],
)
def test_unary_minus_nullable_int(dtype, source, target):
arr = pd.array(source, dtype=dtype)
result = -arr
expected = pd.array(target, dtype=dtype)
tm.assert_extension_array_equal(result, expected)


@pytest.mark.parametrize("dtype", ["Int64", "Int32", "Int16", "Int8"])
@pytest.mark.parametrize(
"source", [[1, 2, 3], [1, 2, None], [-1, 0, 1]],
)
def test_unary_plus_nullable_int(dtype, source):
expected = pd.array(source, dtype=dtype)
result = +expected
tm.assert_extension_array_equal(result, expected)


@pytest.mark.parametrize("dtype", ["Int64", "Int32", "Int16", "Int8"])
@pytest.mark.parametrize(
"source, target",
[([1, 2, 3], [1, 2, 3]), ([1, -2, None], [1, 2, None]), ([-1, 0, 1], [1, 0, 1])],
)
def test_abs_nullable_int(dtype, source, target):
s = pd.array(source, dtype=dtype)
result = abs(s)
expected = pd.array(target, dtype=dtype)
tm.assert_extension_array_equal(result, expected)
2 changes: 1 addition & 1 deletion pandas/tests/frame/test_operators.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ def test_pos_object(self, df):
"df", [pd.DataFrame({"a": pd.to_datetime(["2017-01-22", "1970-01-01"])})]
)
def test_pos_raises(self, df):
msg = re.escape("Unary plus expects numeric dtype, not datetime64[ns]")
msg = "Unary plus expects .* dtype, not datetime64\\[ns\\]"
with pytest.raises(TypeError, match=msg):
(+df)
with pytest.raises(TypeError, match=msg):
Expand Down
39 changes: 39 additions & 0 deletions pandas/tests/series/test_operators.py
Original file line number Diff line number Diff line change
Expand Up @@ -536,3 +536,42 @@ def test_invert(self):
ser = tm.makeStringSeries()
ser.name = "series"
tm.assert_series_equal(-(ser < 0), ~(ser < 0))

@pytest.mark.parametrize("dtype", ["Int64", "Int32", "Int16", "Int8"])
@pytest.mark.parametrize(
"source, target",
[
([1, 2, 3], [-1, -2, -3]),
([1, 2, None], [-1, -2, None]),
([-1, 0, 1], [1, 0, -1]),
],
)
def test_unary_minus_nullable_int(self, dtype, source, target):
s = pd.Series(source, dtype=dtype)
result = -s
expected = pd.Series(target, dtype=dtype)
tm.assert_series_equal(result, expected)

@pytest.mark.parametrize("dtype", ["Int64", "Int32", "Int16", "Int8"])
@pytest.mark.parametrize(
"source", [[1, 2, 3], [1, 2, None], [-1, 0, 1]],
)
def test_unary_plus_nullable_int(self, dtype, source):
expected = pd.Series(source, dtype=dtype)
result = +expected
tm.assert_series_equal(result, expected)

@pytest.mark.parametrize("dtype", ["Int64", "Int32", "Int16", "Int8"])
@pytest.mark.parametrize(
"source, target",
[
([1, 2, 3], [1, 2, 3]),
([1, -2, None], [1, 2, None]),
([-1, 0, 1], [1, 0, 1]),
],
)
def test_abs_nullable_int(self, dtype, source, target):
s = pd.Series(source, dtype=dtype)
result = abs(s)
expected = pd.Series(target, dtype=dtype)
tm.assert_series_equal(result, expected)