Skip to content
Merged
3 changes: 2 additions & 1 deletion doc/source/whatsnew/v1.0.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -869,7 +869,8 @@ Other
- Bug in :meth:`DataFrame.append` that raised ``IndexError`` when appending with empty list (:issue:`28769`)
- Fix :class:`AbstractHolidayCalendar` to return correct results for
years after 2030 (now goes up to 2200) (:issue:`27790`)
- Fixed :class:`IntegerArray` returning ``NA`` rather than ``inf`` for operations dividing by 0 (:issue:`27398`)
- Fixed :class:`IntegerArray` returning ``inf`` rather than ``NaN`` for operations dividing by 0 (:issue:`27398`)
- Fixed ``pow`` operations for :class:`IntegerArray` when the other value is ``0`` or ``1`` (:issue:`29997`)
- Bug in :meth:`Series.count` raises if use_inf_as_na is enabled (:issue:`29478`)


Expand Down
25 changes: 18 additions & 7 deletions pandas/core/arrays/integer.py
Original file line number Diff line number Diff line change
Expand Up @@ -718,13 +718,13 @@ def _create_arithmetic_method(cls, op):
@unpack_zerodim_and_defer(op.__name__)
def integer_arithmetic_method(self, other):

mask = None
omask = None

if getattr(other, "ndim", 0) > 1:
raise NotImplementedError("can only perform ops with 1-d structures")

if isinstance(other, IntegerArray):
other, mask = other._data, other._mask
other, omask = other._data, other._mask

elif is_list_like(other):
other = np.asarray(other)
Expand All @@ -742,17 +742,28 @@ def integer_arithmetic_method(self, other):
raise TypeError("can only perform ops with numeric values")

# nans propagate
if mask is None:
if omask is None:
mask = self._mask.copy()
else:
mask = self._mask | mask
mask = self._mask | omask

# 1 ** np.nan is 1. So we have to unmask those.
if op_name == "pow":
mask = np.where(self == 1, False, mask)
# 1 ** x is 1.
mask = np.where((self._data == 1) & ~self._mask, False, mask)
# x ** 0 is 1.
if omask is not None:
mask = np.where((other == 0) & ~omask, False, mask)
else:
mask = np.where(other == 0, False, mask)

elif op_name == "rpow":
mask = np.where(other == 1, False, mask)
# 1 ** x is 1.
if omask is not None:
mask = np.where((other == 1) & ~omask, False, mask)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this check for self._data == 1 ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, no, because it is for the reversed op? (maybe add a comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your call on whether a comment is needed. It's clear IMO, though I wrote it :)

else:
mask = np.where(other == 1, False, mask)
# x ** 0 is 1.
mask = np.where((self._data == 0) & ~self._mask, False, mask)

with np.errstate(all="ignore"):
result = op(self._data, other)
Expand Down
50 changes: 41 additions & 9 deletions pandas/tests/arrays/test_integer.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,22 +346,54 @@ def test_divide_by_zero(self, zero, negative):
result = a / zero
expected = np.array([np.nan, np.inf, -np.inf, np.nan])
if negative:
values = [np.nan, -np.inf, np.inf, np.nan]
else:
values = [np.nan, np.inf, -np.inf, np.nan]
expected = np.array(values)
expected *= -1
tm.assert_numpy_array_equal(result, expected)

def test_pow(self):
# https://github.com/pandas-dev/pandas/issues/22022
a = integer_array([1, np.nan, np.nan, 1])
b = integer_array([1, np.nan, 1, np.nan])
def test_pow_scalar(self):
a = pd.array([0, 1, None, 2], dtype="Int64")
result = a ** 0
expected = pd.array([1, 1, 1, 1], dtype="Int64")
tm.assert_extension_array_equal(result, expected)

result = a ** 1
expected = pd.array([0, 1, None, 2], dtype="Int64")
tm.assert_extension_array_equal(result, expected)

# result = a ** pd.NA
# expected = pd.array([None, 1, None, None], dtype="Int64")
# tm.assert_extension_array_equal(result, expected)

result = a ** np.nan
expected = np.array([np.nan, 1, np.nan, np.nan], dtype="float64")
tm.assert_numpy_array_equal(result, expected)

# reversed
result = 0 ** a
expected = pd.array([1, 0, None, 0], dtype="Int64")
tm.assert_extension_array_equal(result, expected)

result = 1 ** a
expected = pd.array([1, 1, 1, 1], dtype="Int64")
tm.assert_extension_array_equal(result, expected)

# result = pd.NA ** a
# expected = pd.array([1, None, None, None], dtype="Int64")
# tm.assert_extension_array_equal(result, expected)

result = np.nan ** a
expected = np.array([1, np.nan, np.nan, np.nan], dtype="float64")
tm.assert_numpy_array_equal(result, expected)

def test_pow_array(self):
a = integer_array([0, 0, 0, 1, 1, 1, None, None, None])
b = integer_array([0, 1, None, 0, 1, None, 0, 1, None])
result = a ** b
expected = pd.core.arrays.integer_array([1, np.nan, np.nan, 1])
expected = integer_array([1, 0, None, 1, 1, 1, 1, None, None])
tm.assert_extension_array_equal(result, expected)

def test_rpow_one_to_na(self):
# https://github.com/pandas-dev/pandas/issues/22022
# https://github.com/pandas-dev/pandas/issues/29997
arr = integer_array([np.nan, np.nan])
result = np.array([1.0, 2.0]) ** arr
expected = np.array([1.0, np.nan])
Expand Down