From 3968846460bfcd00b54e4406311589e9fa5d0438 Mon Sep 17 00:00:00 2001 From: Daniel Saxton Date: Mon, 22 Feb 2021 08:07:58 -0600 Subject: [PATCH 1/5] REGR: Fix assignment bug for unary operators --- doc/source/whatsnew/v1.2.3.rst | 2 +- pandas/core/arrays/integer.py | 4 ++-- pandas/tests/arrays/integer/test_arithmetic.py | 10 ++++++++++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/doc/source/whatsnew/v1.2.3.rst b/doc/source/whatsnew/v1.2.3.rst index 5ed8fd84472c4..3369e84dd5b17 100644 --- a/doc/source/whatsnew/v1.2.3.rst +++ b/doc/source/whatsnew/v1.2.3.rst @@ -16,7 +16,7 @@ Fixed regressions ~~~~~~~~~~~~~~~~~ - Fixed regression in :meth:`~DataFrame.to_excel` raising ``KeyError`` when giving duplicate columns with ``columns`` attribute (:issue:`39695`) -- +- Fixed regression in :class:`IntegerArray` unary ops propagating mask on assignment (:issue:`39943`) .. --------------------------------------------------------------------------- diff --git a/pandas/core/arrays/integer.py b/pandas/core/arrays/integer.py index d62a05253b265..b16b4b3ae856a 100644 --- a/pandas/core/arrays/integer.py +++ b/pandas/core/arrays/integer.py @@ -316,13 +316,13 @@ 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) + return type(self)(-self._data, self._mask.copy()) def __pos__(self): return self def __abs__(self): - return type(self)(np.abs(self._data), self._mask) + return type(self)(np.abs(self._data), self._mask.copy()) @classmethod def _from_sequence( diff --git a/pandas/tests/arrays/integer/test_arithmetic.py b/pandas/tests/arrays/integer/test_arithmetic.py index 0c1b10f66a73b..06b85fcc528a2 100644 --- a/pandas/tests/arrays/integer/test_arithmetic.py +++ b/pandas/tests/arrays/integer/test_arithmetic.py @@ -317,3 +317,13 @@ def test_abs_nullable_int(any_signed_nullable_int_dtype, source, target): result = abs(s) expected = pd.array(target, dtype=dtype) tm.assert_extension_array_equal(result, expected) + + +@pytest.mark.parametrize("op", ["__neg__", "__abs__"]) +def test_unary_op_does_not_propagate_mask_on_assignment(op): + # https://github.com/pandas-dev/pandas/issues/39943 + s = pd.Series([1, 2, 3], dtype="Int64") + result = getattr(s, op)() + expected = result.copy(deep=True) + s[0] = None + tm.assert_series_equal(result, expected) From 7fa3768444336efb44c23152b19210db37ace39e Mon Sep 17 00:00:00 2001 From: Daniel Saxton Date: Mon, 22 Feb 2021 10:10:31 -0600 Subject: [PATCH 2/5] Nit --- pandas/tests/arrays/integer/test_arithmetic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/arrays/integer/test_arithmetic.py b/pandas/tests/arrays/integer/test_arithmetic.py index 06b85fcc528a2..d21d21f072dfc 100644 --- a/pandas/tests/arrays/integer/test_arithmetic.py +++ b/pandas/tests/arrays/integer/test_arithmetic.py @@ -320,7 +320,7 @@ def test_abs_nullable_int(any_signed_nullable_int_dtype, source, target): @pytest.mark.parametrize("op", ["__neg__", "__abs__"]) -def test_unary_op_does_not_propagate_mask_on_assignment(op): +def test_unary_op_does_not_propagate_mask(op): # https://github.com/pandas-dev/pandas/issues/39943 s = pd.Series([1, 2, 3], dtype="Int64") result = getattr(s, op)() From 70a8bd8b1fb52ecf01d0307934ea8314649c39c4 Mon Sep 17 00:00:00 2001 From: Daniel Saxton Date: Mon, 22 Feb 2021 10:40:56 -0600 Subject: [PATCH 3/5] Fix --- pandas/core/arrays/masked.py | 2 +- pandas/tests/arrays/integer/test_arithmetic.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/core/arrays/masked.py b/pandas/core/arrays/masked.py index bae14f4e560c2..8cf876fa32d7b 100644 --- a/pandas/core/arrays/masked.py +++ b/pandas/core/arrays/masked.py @@ -172,7 +172,7 @@ def __len__(self) -> int: return len(self._data) def __invert__(self: BaseMaskedArrayT) -> BaseMaskedArrayT: - return type(self)(~self._data, self._mask) + return type(self)(~self._data, self._mask.copy()) def to_numpy( self, diff --git a/pandas/tests/arrays/integer/test_arithmetic.py b/pandas/tests/arrays/integer/test_arithmetic.py index d21d21f072dfc..626a22dda3778 100644 --- a/pandas/tests/arrays/integer/test_arithmetic.py +++ b/pandas/tests/arrays/integer/test_arithmetic.py @@ -319,7 +319,7 @@ def test_abs_nullable_int(any_signed_nullable_int_dtype, source, target): tm.assert_extension_array_equal(result, expected) -@pytest.mark.parametrize("op", ["__neg__", "__abs__"]) +@pytest.mark.parametrize("op", ["__neg__", "__abs__", "__invert__"]) def test_unary_op_does_not_propagate_mask(op): # https://github.com/pandas-dev/pandas/issues/39943 s = pd.Series([1, 2, 3], dtype="Int64") From 57b2b273917eac1389379c21f11aa5d92e809882 Mon Sep 17 00:00:00 2001 From: Daniel Saxton Date: Mon, 22 Feb 2021 12:18:47 -0600 Subject: [PATCH 4/5] Include boolean dtype --- pandas/tests/arrays/integer/test_arithmetic.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pandas/tests/arrays/integer/test_arithmetic.py b/pandas/tests/arrays/integer/test_arithmetic.py index 626a22dda3778..37d1b978991b4 100644 --- a/pandas/tests/arrays/integer/test_arithmetic.py +++ b/pandas/tests/arrays/integer/test_arithmetic.py @@ -320,9 +320,12 @@ def test_abs_nullable_int(any_signed_nullable_int_dtype, source, target): @pytest.mark.parametrize("op", ["__neg__", "__abs__", "__invert__"]) -def test_unary_op_does_not_propagate_mask(op): +@pytest.mark.parametrize( + "values, dtype", [([1, 2, 3], "Int64"), ([True, False, True], "boolean")] +) +def test_unary_op_does_not_propagate_mask(op, values, dtype): # https://github.com/pandas-dev/pandas/issues/39943 - s = pd.Series([1, 2, 3], dtype="Int64") + s = pd.Series(values, dtype=dtype) result = getattr(s, op)() expected = result.copy(deep=True) s[0] = None From 52bc411649c1f90293d584860e4b9e3670643842 Mon Sep 17 00:00:00 2001 From: Daniel Saxton Date: Mon, 22 Feb 2021 14:20:55 -0600 Subject: [PATCH 5/5] Move to masked --- pandas/tests/arrays/integer/test_arithmetic.py | 13 ------------- pandas/tests/arrays/masked/test_arithmetic.py | 13 +++++++++++++ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/pandas/tests/arrays/integer/test_arithmetic.py b/pandas/tests/arrays/integer/test_arithmetic.py index 37d1b978991b4..0c1b10f66a73b 100644 --- a/pandas/tests/arrays/integer/test_arithmetic.py +++ b/pandas/tests/arrays/integer/test_arithmetic.py @@ -317,16 +317,3 @@ def test_abs_nullable_int(any_signed_nullable_int_dtype, source, target): result = abs(s) expected = pd.array(target, dtype=dtype) tm.assert_extension_array_equal(result, expected) - - -@pytest.mark.parametrize("op", ["__neg__", "__abs__", "__invert__"]) -@pytest.mark.parametrize( - "values, dtype", [([1, 2, 3], "Int64"), ([True, False, True], "boolean")] -) -def test_unary_op_does_not_propagate_mask(op, values, dtype): - # https://github.com/pandas-dev/pandas/issues/39943 - s = pd.Series(values, dtype=dtype) - result = getattr(s, op)() - expected = result.copy(deep=True) - s[0] = None - tm.assert_series_equal(result, expected) diff --git a/pandas/tests/arrays/masked/test_arithmetic.py b/pandas/tests/arrays/masked/test_arithmetic.py index 88e11f57a7835..1fc7f824c6daa 100644 --- a/pandas/tests/arrays/masked/test_arithmetic.py +++ b/pandas/tests/arrays/masked/test_arithmetic.py @@ -162,3 +162,16 @@ def test_error_len_mismatch(data, all_arithmetic_operators): s = pd.Series(data) with pytest.raises(ValueError, match="Lengths must match"): op(s, other) + + +@pytest.mark.parametrize("op", ["__neg__", "__abs__", "__invert__"]) +@pytest.mark.parametrize( + "values, dtype", [([1, 2, 3], "Int64"), ([True, False, True], "boolean")] +) +def test_unary_op_does_not_propagate_mask(op, values, dtype): + # https://github.com/pandas-dev/pandas/issues/39943 + s = pd.Series(values, dtype=dtype) + result = getattr(s, op)() + expected = result.copy(deep=True) + s[0] = None + tm.assert_series_equal(result, expected)