From e40c3c64781d9b21a9c809276bb441d7306a1ede Mon Sep 17 00:00:00 2001 From: zitorelova Date: Fri, 19 Feb 2021 01:30:12 +0000 Subject: [PATCH 01/23] Add neg, pos, abs methods to FloatingArray --- pandas/core/arrays/floating.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pandas/core/arrays/floating.py b/pandas/core/arrays/floating.py index a43b30f5043e2..d8937cf611107 100644 --- a/pandas/core/arrays/floating.py +++ b/pandas/core/arrays/floating.py @@ -257,6 +257,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)(abs(self._data), self._mask) + @classmethod def _from_sequence( cls, scalars, *, dtype=None, copy: bool = False From 9929bc461b18839f03162f9134b10b7a59d7bc63 Mon Sep 17 00:00:00 2001 From: zitorelova Date: Fri, 19 Feb 2021 19:17:36 +0000 Subject: [PATCH 02/23] Add tests for arrays --- .../tests/arrays/floating/test_arithmetic.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/pandas/tests/arrays/floating/test_arithmetic.py b/pandas/tests/arrays/floating/test_arithmetic.py index 7ba4da8a5ede9..3252efe62929d 100644 --- a/pandas/tests/arrays/floating/test_arithmetic.py +++ b/pandas/tests/arrays/floating/test_arithmetic.py @@ -180,3 +180,46 @@ def test_cross_type_arithmetic(): result = df.A + df.B expected = pd.Series([2, np.nan, np.nan], dtype="Float64") tm.assert_series_equal(result, expected) + + +@pytest.mark.parametrize( + "source, target", + [ + ([1.1, 2.2, 3.3], [-1.1, -2.2, -3.3]), + ([1.1, 2.2, None], [-1.1, -2.2, None]), + ([-1.1, 0.0, 1.1], [1.1, 0.0, -1.1]) + ] +) +def test_unary_minus_float(float_ea_dtype, source, target): + dtype = float_ea_dtype + arr = pd.array(source, dtype=dtype) + result = -arr + expected = pd.array(target, dtype=dtype) + tm.assert_extension_array_equal(result, expected) + + +@pytest.mark.parametrize( + "source", + [[1.1, 2.2, 3.3], [1.1, 2.2, None], [-1.1, 0.0, 1]] +) +def test_unary_plus_float(float_ea_dtype, source): + dtype = float_ea_dtype + expected = pd.array(source, dtype=dtype) + result = +expected + tm.assert_extension_array_equal(result, expected) + + +@pytest.mark.parametrize( + "source, target", + [ + ([1.1, 2.2, 3.3], [1.1, 2.2, 3.3]), + ([1.1, -2.2, None], [1.1, 2.2, None]), + ([-1.1, 0.0, 1.1], [1.1, 0.0, 1.1]) + ] +) +def test_unary_abs_float(float_ea_dtype, source, target): + dtype = float_ea_dtype + arr = pd.array(source, dtype=dtype) + result = abs(arr) + expected = pd.array(target, dtype=dtype) + tm.assert_extension_array_equal(result, expected) From 19524676f9bda411891e0c580b72b368ed003c28 Mon Sep 17 00:00:00 2001 From: zitorelova Date: Fri, 19 Feb 2021 19:41:26 +0000 Subject: [PATCH 03/23] Add tests for Series objects --- pandas/tests/series/test_unary.py | 40 +++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/pandas/tests/series/test_unary.py b/pandas/tests/series/test_unary.py index 40d5e56203c6c..f1f8694ea4ff7 100644 --- a/pandas/tests/series/test_unary.py +++ b/pandas/tests/series/test_unary.py @@ -55,3 +55,43 @@ def test_abs_nullable_int(self, any_signed_nullable_int_dtype, source, target): result = abs(ser) expected = Series(target, dtype=dtype) tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize( + "source, target", + [ + ([1.1, 2.2, 3.3], [-1.1, -2.2, -3.3]), + ([1.1, 2.2, None], [-1.1, -2.2, None]), + ([-1.1, 0.0, 1.1], [1.1, 0.0, -1.1]) + ] + ) + def test_unary_minus_float(self, float_ea_dtype, source, target): + dtype = float_ea_dtype + ser = Series(source, dtype=dtype) + result = -ser + expected = Series(target, dtype=dtype) + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize( + "source", + [[1.1, 2.2, 3.3], [1.1, 2.2, None], [-1.1, 0.0, 1]] + ) + def test_unary_plus_float(self, float_ea_dtype, source): + dtype = float_ea_dtype + expected = Series(source, dtype=dtype) + result = +expected + tm.assert_series_equal(result, expected) + + @pytest.mark.parametrize( + "source, target", + [ + ([1.1, 2.2, 3.3], [1.1, 2.2, 3.3]), + ([1.1, -2.2, None], [1.1, 2.2, None]), + ([-1.1, 0.0, 1.1], [1.1, 0.0, 1.1]) + ] + ) + def test_unary_abs_float(self, float_ea_dtype, source, target): + dtype = float_ea_dtype + ser = Series(source, dtype=dtype) + result = abs(ser) + expected = Series(target, dtype=dtype) + tm.assert_series_equal(result, expected) From 5fdf6676595ea0ba9d399f92ab376bfad67d2dff Mon Sep 17 00:00:00 2001 From: zitorelova Date: Fri, 19 Feb 2021 20:00:41 +0000 Subject: [PATCH 04/23] Add whatsnew entry for v1.3.0 --- doc/source/whatsnew/v1.3.0.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/whatsnew/v1.3.0.rst b/doc/source/whatsnew/v1.3.0.rst index cbe53edaf12b5..ee6fa33886f0a 100644 --- a/doc/source/whatsnew/v1.3.0.rst +++ b/doc/source/whatsnew/v1.3.0.rst @@ -466,6 +466,7 @@ ExtensionArray - Bug in :meth:`DataFrame.where` when ``other`` is a :class:`Series` with :class:`ExtensionArray` dtype (:issue:`38729`) - Fixed bug where :meth:`Series.idxmax`, :meth:`Series.idxmin` and ``argmax/min`` fail when the underlying data is :class:`ExtensionArray` (:issue:`32749`, :issue:`33719`, :issue:`36566`) +- Bug in :class:`FloatingArray` raises ``TypeError`` when trying to apply a negative operator (:issue:`38749`) - Other From de24336f2e2f7f70f0e4588faafa3b3f2c120486 Mon Sep 17 00:00:00 2001 From: zitorelova Date: Fri, 19 Feb 2021 20:17:25 +0000 Subject: [PATCH 05/23] Fix pre-commit errors --- pandas/tests/arrays/floating/test_arithmetic.py | 13 +++++-------- pandas/tests/series/test_unary.py | 11 +++++------ 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/pandas/tests/arrays/floating/test_arithmetic.py b/pandas/tests/arrays/floating/test_arithmetic.py index 3252efe62929d..47b88867b6125 100644 --- a/pandas/tests/arrays/floating/test_arithmetic.py +++ b/pandas/tests/arrays/floating/test_arithmetic.py @@ -187,8 +187,8 @@ def test_cross_type_arithmetic(): [ ([1.1, 2.2, 3.3], [-1.1, -2.2, -3.3]), ([1.1, 2.2, None], [-1.1, -2.2, None]), - ([-1.1, 0.0, 1.1], [1.1, 0.0, -1.1]) - ] + ([-1.1, 0.0, 1.1], [1.1, 0.0, -1.1]), + ], ) def test_unary_minus_float(float_ea_dtype, source, target): dtype = float_ea_dtype @@ -198,10 +198,7 @@ def test_unary_minus_float(float_ea_dtype, source, target): tm.assert_extension_array_equal(result, expected) -@pytest.mark.parametrize( - "source", - [[1.1, 2.2, 3.3], [1.1, 2.2, None], [-1.1, 0.0, 1]] -) +@pytest.mark.parametrize("source", [[1.1, 2.2, 3.3], [1.1, 2.2, None], [-1.1, 0.0, 1]]) def test_unary_plus_float(float_ea_dtype, source): dtype = float_ea_dtype expected = pd.array(source, dtype=dtype) @@ -214,8 +211,8 @@ def test_unary_plus_float(float_ea_dtype, source): [ ([1.1, 2.2, 3.3], [1.1, 2.2, 3.3]), ([1.1, -2.2, None], [1.1, 2.2, None]), - ([-1.1, 0.0, 1.1], [1.1, 0.0, 1.1]) - ] + ([-1.1, 0.0, 1.1], [1.1, 0.0, 1.1]), + ], ) def test_unary_abs_float(float_ea_dtype, source, target): dtype = float_ea_dtype diff --git a/pandas/tests/series/test_unary.py b/pandas/tests/series/test_unary.py index f1f8694ea4ff7..984279059ca18 100644 --- a/pandas/tests/series/test_unary.py +++ b/pandas/tests/series/test_unary.py @@ -61,8 +61,8 @@ def test_abs_nullable_int(self, any_signed_nullable_int_dtype, source, target): [ ([1.1, 2.2, 3.3], [-1.1, -2.2, -3.3]), ([1.1, 2.2, None], [-1.1, -2.2, None]), - ([-1.1, 0.0, 1.1], [1.1, 0.0, -1.1]) - ] + ([-1.1, 0.0, 1.1], [1.1, 0.0, -1.1]), + ], ) def test_unary_minus_float(self, float_ea_dtype, source, target): dtype = float_ea_dtype @@ -72,8 +72,7 @@ def test_unary_minus_float(self, float_ea_dtype, source, target): tm.assert_series_equal(result, expected) @pytest.mark.parametrize( - "source", - [[1.1, 2.2, 3.3], [1.1, 2.2, None], [-1.1, 0.0, 1]] + "source", [[1.1, 2.2, 3.3], [1.1, 2.2, None], [-1.1, 0.0, 1]] ) def test_unary_plus_float(self, float_ea_dtype, source): dtype = float_ea_dtype @@ -86,8 +85,8 @@ def test_unary_plus_float(self, float_ea_dtype, source): [ ([1.1, 2.2, 3.3], [1.1, 2.2, 3.3]), ([1.1, -2.2, None], [1.1, 2.2, None]), - ([-1.1, 0.0, 1.1], [1.1, 0.0, 1.1]) - ] + ([-1.1, 0.0, 1.1], [1.1, 0.0, 1.1]), + ], ) def test_unary_abs_float(self, float_ea_dtype, source, target): dtype = float_ea_dtype From 853bc5d5de4d9183a5f56e517c45d81d26c6dd8e Mon Sep 17 00:00:00 2001 From: zitorelova Date: Fri, 19 Feb 2021 22:58:40 +0000 Subject: [PATCH 06/23] Simplify testing for FloatingArray unary operators --- .../tests/arrays/floating/test_arithmetic.py | 43 ++++++------------- 1 file changed, 12 insertions(+), 31 deletions(-) diff --git a/pandas/tests/arrays/floating/test_arithmetic.py b/pandas/tests/arrays/floating/test_arithmetic.py index 47b88867b6125..e674b49a99bd4 100644 --- a/pandas/tests/arrays/floating/test_arithmetic.py +++ b/pandas/tests/arrays/floating/test_arithmetic.py @@ -183,40 +183,21 @@ def test_cross_type_arithmetic(): @pytest.mark.parametrize( - "source, target", + "source, neg_target, abs_target", [ - ([1.1, 2.2, 3.3], [-1.1, -2.2, -3.3]), - ([1.1, 2.2, None], [-1.1, -2.2, None]), - ([-1.1, 0.0, 1.1], [1.1, 0.0, -1.1]), + ([1.1, 2.2, 3.3], [-1.1, -2.2, -3.3], [1.1, 2.2, 3.3]), + ([1.1, 2.2, None], [-1.1, -2.2, None], [1.1, 2.2, None]), + ([-1.1, 0.0, 1.1], [1.1, 0.0, -1.1], [1.1, 0.0, 1.1]), ], ) -def test_unary_minus_float(float_ea_dtype, source, target): +def test_unary_float_operators(float_ea_dtype, source, neg_target, abs_target): + # GH38794 dtype = float_ea_dtype arr = pd.array(source, dtype=dtype) - result = -arr - expected = pd.array(target, dtype=dtype) - tm.assert_extension_array_equal(result, expected) - + neg_result, pos_result, abs_result = -arr, +arr, abs(arr) + neg_target = pd.array(neg_target, dtype=dtype) + abs_target = pd.array(abs_target, dtype=dtype) -@pytest.mark.parametrize("source", [[1.1, 2.2, 3.3], [1.1, 2.2, None], [-1.1, 0.0, 1]]) -def test_unary_plus_float(float_ea_dtype, source): - dtype = float_ea_dtype - expected = pd.array(source, dtype=dtype) - result = +expected - tm.assert_extension_array_equal(result, expected) - - -@pytest.mark.parametrize( - "source, target", - [ - ([1.1, 2.2, 3.3], [1.1, 2.2, 3.3]), - ([1.1, -2.2, None], [1.1, 2.2, None]), - ([-1.1, 0.0, 1.1], [1.1, 0.0, 1.1]), - ], -) -def test_unary_abs_float(float_ea_dtype, source, target): - dtype = float_ea_dtype - arr = pd.array(source, dtype=dtype) - result = abs(arr) - expected = pd.array(target, dtype=dtype) - tm.assert_extension_array_equal(result, expected) + tm.assert_extension_array_equal(neg_result, neg_target) + tm.assert_extension_array_equal(pos_result, arr) + tm.assert_extension_array_equal(abs_result, abs_target) From 6a3e07da02a429d36c6ac8ba90005324070a4ca5 Mon Sep 17 00:00:00 2001 From: zitorelova Date: Fri, 19 Feb 2021 23:10:11 +0000 Subject: [PATCH 07/23] Move whatsnew entry to enhancements --- doc/source/whatsnew/v1.3.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v1.3.0.rst b/doc/source/whatsnew/v1.3.0.rst index ee6fa33886f0a..f9aa92cd1a159 100644 --- a/doc/source/whatsnew/v1.3.0.rst +++ b/doc/source/whatsnew/v1.3.0.rst @@ -70,6 +70,7 @@ Other enhancements - :meth:`Series.loc.__getitem__` and :meth:`Series.loc.__setitem__` with :class:`MultiIndex` now raising helpful error message when indexer has too many dimensions (:issue:`35349`) - :meth:`pandas.read_stata` and :class:`StataReader` support reading data from compressed files. - Add support for parsing ``ISO 8601``-like timestamps with negative signs to :meth:`pandas.Timedelta` (:issue:`37172`) +- Add support for unary operators in :class:`FloatingArray` (:issue:`38749`) .. --------------------------------------------------------------------------- @@ -466,7 +467,6 @@ ExtensionArray - Bug in :meth:`DataFrame.where` when ``other`` is a :class:`Series` with :class:`ExtensionArray` dtype (:issue:`38729`) - Fixed bug where :meth:`Series.idxmax`, :meth:`Series.idxmin` and ``argmax/min`` fail when the underlying data is :class:`ExtensionArray` (:issue:`32749`, :issue:`33719`, :issue:`36566`) -- Bug in :class:`FloatingArray` raises ``TypeError`` when trying to apply a negative operator (:issue:`38749`) - Other From 03801fc4728970944941128722d0e547c040d734 Mon Sep 17 00:00:00 2001 From: zitorelova Date: Sat, 20 Feb 2021 08:19:56 -0800 Subject: [PATCH 08/23] Add fixture for signed nullable numeric dtypes --- pandas/conftest.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pandas/conftest.py b/pandas/conftest.py index ce572e42abec6..4d957e3de5d08 100644 --- a/pandas/conftest.py +++ b/pandas/conftest.py @@ -1347,6 +1347,22 @@ def any_signed_nullable_int_dtype(request): return request.param +@pytest.fixture(params=tm.SIGNED_EA_INT_DTYPES + tm.FLOAT_EA_DTYPES) +def any_signed_nullable_numeric_dtype(request): + """ + Parameterized fixture for any signed nullable integer dtype + and float ea dtypes. + + * 'Int8' + * 'Int16' + * 'Int32' + * 'Int64' + * 'Float32' + * 'Float64' + """ + return request.param + + @pytest.fixture(params=tm.ALL_REAL_DTYPES) def any_real_dtype(request): """ From 1b9759d54170e01554368e36b5b9f7a35676e975 Mon Sep 17 00:00:00 2001 From: zitorelova Date: Sat, 20 Feb 2021 16:34:08 +0000 Subject: [PATCH 09/23] Consolidate all numeric unary operator tests --- pandas/tests/series/test_unary.py | 84 ++++++------------------------- 1 file changed, 14 insertions(+), 70 deletions(-) diff --git a/pandas/tests/series/test_unary.py b/pandas/tests/series/test_unary.py index 984279059ca18..b0c413c932a07 100644 --- a/pandas/tests/series/test_unary.py +++ b/pandas/tests/series/test_unary.py @@ -18,79 +18,23 @@ def test_invert(self): tm.assert_series_equal(-(ser < 0), ~(ser < 0)) @pytest.mark.parametrize( - "source, target", + "source, neg_target, abs_target", [ - ([1, 2, 3], [-1, -2, -3]), - ([1, 2, None], [-1, -2, None]), - ([-1, 0, 1], [1, 0, -1]), + ([1, 2, 3], [-1, -2, -3], [1, 2, 3]), + ([1, 2, None], [-1, -2, None], [1, 2, None]), + ([-1, 0, 1], [1, 0, -1], [1, 0, 1]), ], ) - def test_unary_minus_nullable_int( - self, any_signed_nullable_int_dtype, source, target + def test_all_numeric_unary_operators( + self, any_signed_nullable_numeric_dtype, source, neg_target, abs_target ): - dtype = any_signed_nullable_int_dtype + # GH38794 + dtype = any_signed_nullable_numeric_dtype ser = Series(source, dtype=dtype) - result = -ser - expected = Series(target, dtype=dtype) - tm.assert_series_equal(result, expected) + neg_result, pos_result, abs_result = -ser, +ser, abs(ser) + neg_target = Series(neg_target, dtype=dtype) + abs_target = Series(abs_target, dtype=dtype) - @pytest.mark.parametrize("source", [[1, 2, 3], [1, 2, None], [-1, 0, 1]]) - def test_unary_plus_nullable_int(self, any_signed_nullable_int_dtype, source): - dtype = any_signed_nullable_int_dtype - expected = Series(source, dtype=dtype) - result = +expected - tm.assert_series_equal(result, expected) - - @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, any_signed_nullable_int_dtype, source, target): - dtype = any_signed_nullable_int_dtype - ser = Series(source, dtype=dtype) - result = abs(ser) - expected = Series(target, dtype=dtype) - tm.assert_series_equal(result, expected) - - @pytest.mark.parametrize( - "source, target", - [ - ([1.1, 2.2, 3.3], [-1.1, -2.2, -3.3]), - ([1.1, 2.2, None], [-1.1, -2.2, None]), - ([-1.1, 0.0, 1.1], [1.1, 0.0, -1.1]), - ], - ) - def test_unary_minus_float(self, float_ea_dtype, source, target): - dtype = float_ea_dtype - ser = Series(source, dtype=dtype) - result = -ser - expected = Series(target, dtype=dtype) - tm.assert_series_equal(result, expected) - - @pytest.mark.parametrize( - "source", [[1.1, 2.2, 3.3], [1.1, 2.2, None], [-1.1, 0.0, 1]] - ) - def test_unary_plus_float(self, float_ea_dtype, source): - dtype = float_ea_dtype - expected = Series(source, dtype=dtype) - result = +expected - tm.assert_series_equal(result, expected) - - @pytest.mark.parametrize( - "source, target", - [ - ([1.1, 2.2, 3.3], [1.1, 2.2, 3.3]), - ([1.1, -2.2, None], [1.1, 2.2, None]), - ([-1.1, 0.0, 1.1], [1.1, 0.0, 1.1]), - ], - ) - def test_unary_abs_float(self, float_ea_dtype, source, target): - dtype = float_ea_dtype - ser = Series(source, dtype=dtype) - result = abs(ser) - expected = Series(target, dtype=dtype) - tm.assert_series_equal(result, expected) + tm.assert_series_equal(neg_result, neg_target) + tm.assert_series_equal(pos_result, ser) + tm.assert_series_equal(abs_result, abs_target) From bb77076aa7ca9ba65441b71b97682b3a572b0d9e Mon Sep 17 00:00:00 2001 From: zitorelova Date: Sat, 20 Feb 2021 18:03:40 +0000 Subject: [PATCH 10/23] Consolidate integer unary operator tests --- .../tests/arrays/integer/test_arithmetic.py | 40 ++++++------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/pandas/tests/arrays/integer/test_arithmetic.py b/pandas/tests/arrays/integer/test_arithmetic.py index 0c1b10f66a73b..2eb88b669bcb1 100644 --- a/pandas/tests/arrays/integer/test_arithmetic.py +++ b/pandas/tests/arrays/integer/test_arithmetic.py @@ -284,36 +284,22 @@ def test_reduce_to_float(op): @pytest.mark.parametrize( - "source, target", + "source, neg_target, abs_target", [ - ([1, 2, 3], [-1, -2, -3]), - ([1, 2, None], [-1, -2, None]), - ([-1, 0, 1], [1, 0, -1]), + ([1, 2, 3], [-1, -2, -3], [1, 2, 3]), + ([1, 2, None], [-1, -2, None], [1, 2, None]), + ([-1, 0, 1], [1, 0, -1], [1, 0, 1]), ], ) -def test_unary_minus_nullable_int(any_signed_nullable_int_dtype, source, target): +def test_unary_int_operators( + any_signed_nullable_int_dtype, source, neg_target, abs_target +): dtype = any_signed_nullable_int_dtype arr = pd.array(source, dtype=dtype) - result = -arr - expected = pd.array(target, dtype=dtype) - tm.assert_extension_array_equal(result, expected) - - -@pytest.mark.parametrize("source", [[1, 2, 3], [1, 2, None], [-1, 0, 1]]) -def test_unary_plus_nullable_int(any_signed_nullable_int_dtype, source): - dtype = any_signed_nullable_int_dtype - expected = pd.array(source, dtype=dtype) - result = +expected - tm.assert_extension_array_equal(result, expected) + neg_result, pos_result, abs_result = -arr, +arr, abs(arr) + neg_target = pd.array(neg_target, dtype=dtype) + abs_target = pd.array(abs_target, dtype=dtype) - -@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(any_signed_nullable_int_dtype, source, target): - dtype = any_signed_nullable_int_dtype - s = pd.array(source, dtype=dtype) - result = abs(s) - expected = pd.array(target, dtype=dtype) - tm.assert_extension_array_equal(result, expected) + tm.assert_extension_array_equal(neg_result, neg_target) + tm.assert_extension_array_equal(pos_result, arr) + tm.assert_extension_array_equal(abs_result, abs_target) From 42e8cdf3fd3b17fe60e09538b7cd1fc694db4628 Mon Sep 17 00:00:00 2001 From: zitorelova Date: Mon, 22 Feb 2021 17:32:54 +0000 Subject: [PATCH 11/23] Fix shared mask bug --- pandas/core/arrays/floating.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pandas/core/arrays/floating.py b/pandas/core/arrays/floating.py index d8937cf611107..4f6f0c9c3463c 100644 --- a/pandas/core/arrays/floating.py +++ b/pandas/core/arrays/floating.py @@ -258,13 +258,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 + return self.copy() def __abs__(self): - return type(self)(abs(self._data), self._mask) + return type(self)(abs(self._data), self._mask.copy()) @classmethod def _from_sequence( From 957edc3ae0bf9f8e4b541da7310f114a56cfdcf9 Mon Sep 17 00:00:00 2001 From: zitorelova Date: Mon, 22 Feb 2021 18:01:25 +0000 Subject: [PATCH 12/23] Add test for float op mask --- pandas/tests/series/test_unary.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pandas/tests/series/test_unary.py b/pandas/tests/series/test_unary.py index b0c413c932a07..f859c5f178fb0 100644 --- a/pandas/tests/series/test_unary.py +++ b/pandas/tests/series/test_unary.py @@ -38,3 +38,12 @@ def test_all_numeric_unary_operators( tm.assert_series_equal(neg_result, neg_target) tm.assert_series_equal(pos_result, ser) tm.assert_series_equal(abs_result, abs_target) + + @pytest.mark.parametrize("op", ["__neg__", "__abs__", "__pos__"]) + def test_unary_float_op_mask(self, float_ea_dtype, op): + dtype = float_ea_dtype + ser = Series([1.1, 2.2, 3.3], dtype=dtype) + result = getattr(ser, op)() + target = result.copy(deep=True) + ser[0] = None + tm.assert_series_equal(result, target) From 38c610bf06696a34b27316fa61a9236e7832faf1 Mon Sep 17 00:00:00 2001 From: zitorelova Date: Mon, 22 Feb 2021 18:27:43 +0000 Subject: [PATCH 13/23] Do not return copy when using pos op --- pandas/core/arrays/floating.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/arrays/floating.py b/pandas/core/arrays/floating.py index 4f6f0c9c3463c..893271753782a 100644 --- a/pandas/core/arrays/floating.py +++ b/pandas/core/arrays/floating.py @@ -261,7 +261,7 @@ def __neg__(self): return type(self)(-self._data, self._mask.copy()) def __pos__(self): - return self.copy() + return self def __abs__(self): return type(self)(abs(self._data), self._mask.copy()) From eba6e0476602060d506d060357b5ee1b105f5320 Mon Sep 17 00:00:00 2001 From: zitorelova Date: Tue, 23 Feb 2021 00:52:10 +0000 Subject: [PATCH 14/23] Don't test pos --- pandas/tests/series/test_unary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/series/test_unary.py b/pandas/tests/series/test_unary.py index f859c5f178fb0..65a9b99441a3f 100644 --- a/pandas/tests/series/test_unary.py +++ b/pandas/tests/series/test_unary.py @@ -39,7 +39,7 @@ def test_all_numeric_unary_operators( tm.assert_series_equal(pos_result, ser) tm.assert_series_equal(abs_result, abs_target) - @pytest.mark.parametrize("op", ["__neg__", "__abs__", "__pos__"]) + @pytest.mark.parametrize("op", ["__neg__", "__abs__"]) def test_unary_float_op_mask(self, float_ea_dtype, op): dtype = float_ea_dtype ser = Series([1.1, 2.2, 3.3], dtype=dtype) From be5eb84e8d03e29df06ea356a0b20c787cfe33e4 Mon Sep 17 00:00:00 2001 From: zitorelova Date: Tue, 23 Feb 2021 03:45:07 +0000 Subject: [PATCH 15/23] Remove fixture --- pandas/conftest.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/pandas/conftest.py b/pandas/conftest.py index 4d957e3de5d08..ce572e42abec6 100644 --- a/pandas/conftest.py +++ b/pandas/conftest.py @@ -1347,22 +1347,6 @@ def any_signed_nullable_int_dtype(request): return request.param -@pytest.fixture(params=tm.SIGNED_EA_INT_DTYPES + tm.FLOAT_EA_DTYPES) -def any_signed_nullable_numeric_dtype(request): - """ - Parameterized fixture for any signed nullable integer dtype - and float ea dtypes. - - * 'Int8' - * 'Int16' - * 'Int32' - * 'Int64' - * 'Float32' - * 'Float64' - """ - return request.param - - @pytest.fixture(params=tm.ALL_REAL_DTYPES) def any_real_dtype(request): """ From 9367b1b767748fb4957b5b2b66d91714ddaf4cbf Mon Sep 17 00:00:00 2001 From: zitorelova Date: Tue, 23 Feb 2021 03:46:18 +0000 Subject: [PATCH 16/23] Edit test to include all numeric dtypes --- pandas/tests/series/test_unary.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pandas/tests/series/test_unary.py b/pandas/tests/series/test_unary.py index 65a9b99441a3f..d7e7fdfc04d72 100644 --- a/pandas/tests/series/test_unary.py +++ b/pandas/tests/series/test_unary.py @@ -22,17 +22,20 @@ def test_invert(self): [ ([1, 2, 3], [-1, -2, -3], [1, 2, 3]), ([1, 2, None], [-1, -2, None], [1, 2, None]), - ([-1, 0, 1], [1, 0, -1], [1, 0, 1]), ], ) def test_all_numeric_unary_operators( - self, any_signed_nullable_numeric_dtype, source, neg_target, abs_target + self, any_nullable_numeric_dtype, source, neg_target, abs_target ): # GH38794 - dtype = any_signed_nullable_numeric_dtype + dtype = any_nullable_numeric_dtype ser = Series(source, dtype=dtype) neg_result, pos_result, abs_result = -ser, +ser, abs(ser) - neg_target = Series(neg_target, dtype=dtype) + if dtype.startswith('U'): + neg_target = -Series(source, dtype=dtype) + else: + neg_target = Series(neg_target, dtype=dtype) + abs_target = Series(abs_target, dtype=dtype) tm.assert_series_equal(neg_result, neg_target) From 3e6765556624ef3710de2ff24a8c87764e0b1d54 Mon Sep 17 00:00:00 2001 From: zitorelova Date: Tue, 23 Feb 2021 04:44:41 +0000 Subject: [PATCH 17/23] Fix pre-commit --- pandas/tests/series/test_unary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/series/test_unary.py b/pandas/tests/series/test_unary.py index d7e7fdfc04d72..67bb89b42a56d 100644 --- a/pandas/tests/series/test_unary.py +++ b/pandas/tests/series/test_unary.py @@ -31,7 +31,7 @@ def test_all_numeric_unary_operators( dtype = any_nullable_numeric_dtype ser = Series(source, dtype=dtype) neg_result, pos_result, abs_result = -ser, +ser, abs(ser) - if dtype.startswith('U'): + if dtype.startswith("U"): neg_target = -Series(source, dtype=dtype) else: neg_target = Series(neg_target, dtype=dtype) From 495ddb7335b28bbe1d32fb34e3e00a02866a0089 Mon Sep 17 00:00:00 2001 From: zitorelova Date: Fri, 26 Feb 2021 02:53:07 +0000 Subject: [PATCH 18/23] Move unary operator definitions to NumericArray --- pandas/core/arrays/floating.py | 9 --------- pandas/core/arrays/numeric.py | 9 +++++++++ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pandas/core/arrays/floating.py b/pandas/core/arrays/floating.py index 893271753782a..a43b30f5043e2 100644 --- a/pandas/core/arrays/floating.py +++ b/pandas/core/arrays/floating.py @@ -257,15 +257,6 @@ 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.copy()) - - def __pos__(self): - return self - - def __abs__(self): - return type(self)(abs(self._data), self._mask.copy()) - @classmethod def _from_sequence( cls, scalars, *, dtype=None, copy: bool = False diff --git a/pandas/core/arrays/numeric.py b/pandas/core/arrays/numeric.py index 57017e44a66e9..00a19b39d93d0 100644 --- a/pandas/core/arrays/numeric.py +++ b/pandas/core/arrays/numeric.py @@ -199,3 +199,12 @@ def reconstruct(x): return tuple(reconstruct(x) for x in result) else: return reconstruct(result) + + def __neg__(self): + return type(self)(-self._data, self._mask.copy()) + + def __pos__(self): + return self + + def __abs__(self): + return type(self)(abs(self._data), self._mask.copy()) \ No newline at end of file From 0162e0746740b079fb95ef74e36fc682bad6478b Mon Sep 17 00:00:00 2001 From: zitorelova Date: Fri, 26 Feb 2021 20:53:00 +0000 Subject: [PATCH 19/23] Test mask on all EA dtypes --- pandas/tests/arrays/masked/test_arithmetic.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pandas/tests/arrays/masked/test_arithmetic.py b/pandas/tests/arrays/masked/test_arithmetic.py index 1fc7f824c6daa..53798324997bd 100644 --- a/pandas/tests/arrays/masked/test_arithmetic.py +++ b/pandas/tests/arrays/masked/test_arithmetic.py @@ -165,12 +165,12 @@ def test_error_len_mismatch(data, all_arithmetic_operators): @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): +def test_unary_op_does_not_propagate_mask(data, op): # https://github.com/pandas-dev/pandas/issues/39943 - s = pd.Series(values, dtype=dtype) + data, _ = data + if data.dtype in ["Float32", "Float64"] and op == "__invert__": + pytest.skip("invert is not implemented for float ea dtypes") + s = pd.Series(data) result = getattr(s, op)() expected = result.copy(deep=True) s[0] = None From 7dda62d74232693b0d7864349e18895a2eb5ea1e Mon Sep 17 00:00:00 2001 From: zitorelova Date: Fri, 26 Feb 2021 20:55:15 +0000 Subject: [PATCH 20/23] Add newline to numeric.py --- pandas/core/arrays/numeric.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/arrays/numeric.py b/pandas/core/arrays/numeric.py index 00a19b39d93d0..0dd98c5e3d3f2 100644 --- a/pandas/core/arrays/numeric.py +++ b/pandas/core/arrays/numeric.py @@ -207,4 +207,4 @@ def __pos__(self): return self def __abs__(self): - return type(self)(abs(self._data), self._mask.copy()) \ No newline at end of file + return type(self)(abs(self._data), self._mask.copy()) From 41e42fce3ad3e906370b33c75ac3b884e471e6c5 Mon Sep 17 00:00:00 2001 From: zitorelova Date: Fri, 26 Feb 2021 20:59:23 +0000 Subject: [PATCH 21/23] Remove definitions cause they've been moved to NumericArray --- pandas/core/arrays/integer.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/pandas/core/arrays/integer.py b/pandas/core/arrays/integer.py index b16b4b3ae856a..61d63d2eed6e9 100644 --- a/pandas/core/arrays/integer.py +++ b/pandas/core/arrays/integer.py @@ -315,15 +315,6 @@ 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.copy()) - - def __pos__(self): - return self - - def __abs__(self): - return type(self)(np.abs(self._data), self._mask.copy()) - @classmethod def _from_sequence( cls, scalars, *, dtype: Optional[Dtype] = None, copy: bool = False From cf2732a0afd0641918c5d3feceb7d33cb51588ac Mon Sep 17 00:00:00 2001 From: zitorelova Date: Sat, 27 Feb 2021 17:42:52 +0000 Subject: [PATCH 22/23] Xfail instead of skip for invert ops on float ea dtypes --- pandas/tests/arrays/masked/test_arithmetic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/arrays/masked/test_arithmetic.py b/pandas/tests/arrays/masked/test_arithmetic.py index 53798324997bd..02669837f0b0d 100644 --- a/pandas/tests/arrays/masked/test_arithmetic.py +++ b/pandas/tests/arrays/masked/test_arithmetic.py @@ -169,7 +169,7 @@ def test_unary_op_does_not_propagate_mask(data, op): # https://github.com/pandas-dev/pandas/issues/39943 data, _ = data if data.dtype in ["Float32", "Float64"] and op == "__invert__": - pytest.skip("invert is not implemented for float ea dtypes") + pytest.xfail("invert is not implemented for float ea dtypes") s = pd.Series(data) result = getattr(s, op)() expected = result.copy(deep=True) From 3cc521217904f9152d8c61ab642d4736404351e0 Mon Sep 17 00:00:00 2001 From: zitorelova Date: Sat, 27 Feb 2021 18:31:14 +0000 Subject: [PATCH 23/23] Fix pre-commit error --- pandas/tests/arrays/masked/test_arithmetic.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pandas/tests/arrays/masked/test_arithmetic.py b/pandas/tests/arrays/masked/test_arithmetic.py index 02669837f0b0d..adb52fce17f8b 100644 --- a/pandas/tests/arrays/masked/test_arithmetic.py +++ b/pandas/tests/arrays/masked/test_arithmetic.py @@ -165,11 +165,13 @@ def test_error_len_mismatch(data, all_arithmetic_operators): @pytest.mark.parametrize("op", ["__neg__", "__abs__", "__invert__"]) -def test_unary_op_does_not_propagate_mask(data, op): +def test_unary_op_does_not_propagate_mask(data, op, request): # https://github.com/pandas-dev/pandas/issues/39943 data, _ = data if data.dtype in ["Float32", "Float64"] and op == "__invert__": - pytest.xfail("invert is not implemented for float ea dtypes") + request.node.add_marker( + pytest.mark.xfail(reason="invert is not implemented for float ea dtypes") + ) s = pd.Series(data) result = getattr(s, op)() expected = result.copy(deep=True)