Skip to content

Commit 7aa1ca9

Browse files
TomAugspurgerproost
authored andcommitted
Fix IntegerArray pow for special cases (pandas-dev#30210)
x^0 == 1 1^x == 1
1 parent cd34050 commit 7aa1ca9

File tree

3 files changed

+61
-17
lines changed

3 files changed

+61
-17
lines changed

doc/source/whatsnew/v1.0.0.rst

+2-1
Original file line numberDiff line numberDiff line change
@@ -871,7 +871,8 @@ Other
871871
- Bug in :meth:`DataFrame.append` that raised ``IndexError`` when appending with empty list (:issue:`28769`)
872872
- Fix :class:`AbstractHolidayCalendar` to return correct results for
873873
years after 2030 (now goes up to 2200) (:issue:`27790`)
874-
- Fixed :class:`IntegerArray` returning ``NA`` rather than ``inf`` for operations dividing by 0 (:issue:`27398`)
874+
- Fixed :class:`IntegerArray` returning ``inf`` rather than ``NaN`` for operations dividing by 0 (:issue:`27398`)
875+
- Fixed ``pow`` operations for :class:`IntegerArray` when the other value is ``0`` or ``1`` (:issue:`29997`)
875876
- Bug in :meth:`Series.count` raises if use_inf_as_na is enabled (:issue:`29478`)
876877

877878

pandas/core/arrays/integer.py

+18-7
Original file line numberDiff line numberDiff line change
@@ -718,13 +718,13 @@ def _create_arithmetic_method(cls, op):
718718
@unpack_zerodim_and_defer(op.__name__)
719719
def integer_arithmetic_method(self, other):
720720

721-
mask = None
721+
omask = None
722722

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

726726
if isinstance(other, IntegerArray):
727-
other, mask = other._data, other._mask
727+
other, omask = other._data, other._mask
728728

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

744744
# nans propagate
745-
if mask is None:
745+
if omask is None:
746746
mask = self._mask.copy()
747747
else:
748-
mask = self._mask | mask
748+
mask = self._mask | omask
749749

750-
# 1 ** np.nan is 1. So we have to unmask those.
751750
if op_name == "pow":
752-
mask = np.where(self == 1, False, mask)
751+
# 1 ** x is 1.
752+
mask = np.where((self._data == 1) & ~self._mask, False, mask)
753+
# x ** 0 is 1.
754+
if omask is not None:
755+
mask = np.where((other == 0) & ~omask, False, mask)
756+
else:
757+
mask = np.where(other == 0, False, mask)
753758

754759
elif op_name == "rpow":
755-
mask = np.where(other == 1, False, mask)
760+
# 1 ** x is 1.
761+
if omask is not None:
762+
mask = np.where((other == 1) & ~omask, False, mask)
763+
else:
764+
mask = np.where(other == 1, False, mask)
765+
# x ** 0 is 1.
766+
mask = np.where((self._data == 0) & ~self._mask, False, mask)
756767

757768
with np.errstate(all="ignore"):
758769
result = op(self._data, other)

pandas/tests/arrays/test_integer.py

+41-9
Original file line numberDiff line numberDiff line change
@@ -346,22 +346,54 @@ def test_divide_by_zero(self, zero, negative):
346346
result = a / zero
347347
expected = np.array([np.nan, np.inf, -np.inf, np.nan])
348348
if negative:
349-
values = [np.nan, -np.inf, np.inf, np.nan]
350-
else:
351-
values = [np.nan, np.inf, -np.inf, np.nan]
352-
expected = np.array(values)
349+
expected *= -1
353350
tm.assert_numpy_array_equal(result, expected)
354351

355-
def test_pow(self):
356-
# https://github.com/pandas-dev/pandas/issues/22022
357-
a = integer_array([1, np.nan, np.nan, 1])
358-
b = integer_array([1, np.nan, 1, np.nan])
352+
def test_pow_scalar(self):
353+
a = pd.array([0, 1, None, 2], dtype="Int64")
354+
result = a ** 0
355+
expected = pd.array([1, 1, 1, 1], dtype="Int64")
356+
tm.assert_extension_array_equal(result, expected)
357+
358+
result = a ** 1
359+
expected = pd.array([0, 1, None, 2], dtype="Int64")
360+
tm.assert_extension_array_equal(result, expected)
361+
362+
# result = a ** pd.NA
363+
# expected = pd.array([None, 1, None, None], dtype="Int64")
364+
# tm.assert_extension_array_equal(result, expected)
365+
366+
result = a ** np.nan
367+
expected = np.array([np.nan, 1, np.nan, np.nan], dtype="float64")
368+
tm.assert_numpy_array_equal(result, expected)
369+
370+
# reversed
371+
result = 0 ** a
372+
expected = pd.array([1, 0, None, 0], dtype="Int64")
373+
tm.assert_extension_array_equal(result, expected)
374+
375+
result = 1 ** a
376+
expected = pd.array([1, 1, 1, 1], dtype="Int64")
377+
tm.assert_extension_array_equal(result, expected)
378+
379+
# result = pd.NA ** a
380+
# expected = pd.array([1, None, None, None], dtype="Int64")
381+
# tm.assert_extension_array_equal(result, expected)
382+
383+
result = np.nan ** a
384+
expected = np.array([1, np.nan, np.nan, np.nan], dtype="float64")
385+
tm.assert_numpy_array_equal(result, expected)
386+
387+
def test_pow_array(self):
388+
a = integer_array([0, 0, 0, 1, 1, 1, None, None, None])
389+
b = integer_array([0, 1, None, 0, 1, None, 0, 1, None])
359390
result = a ** b
360-
expected = pd.core.arrays.integer_array([1, np.nan, np.nan, 1])
391+
expected = integer_array([1, 0, None, 1, 1, 1, 1, None, None])
361392
tm.assert_extension_array_equal(result, expected)
362393

363394
def test_rpow_one_to_na(self):
364395
# https://github.com/pandas-dev/pandas/issues/22022
396+
# https://github.com/pandas-dev/pandas/issues/29997
365397
arr = integer_array([np.nan, np.nan])
366398
result = np.array([1.0, 2.0]) ** arr
367399
expected = np.array([1.0, np.nan])

0 commit comments

Comments
 (0)