From 5d601dd36bde83c018e9b517710c47fbbd94bd53 Mon Sep 17 00:00:00 2001 From: Brock Date: Wed, 30 Sep 2020 19:13:00 -0700 Subject: [PATCH 1/2] BUG: inconsistent name-retention in Series ops --- pandas/conftest.py | 37 +++++++++++++++++++++++ pandas/core/ops/__init__.py | 11 ++++--- pandas/tests/series/test_arithmetic.py | 42 ++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 4 deletions(-) diff --git a/pandas/conftest.py b/pandas/conftest.py index 3865d287c6905..a902e617f549c 100644 --- a/pandas/conftest.py +++ b/pandas/conftest.py @@ -677,6 +677,43 @@ def all_arithmetic_operators(request): return request.param +@pytest.fixture( + params=[ + operator.add, + ops.radd, + operator.sub, + ops.rsub, + operator.mul, + ops.rmul, + operator.truediv, + ops.rtruediv, + operator.floordiv, + ops.rfloordiv, + operator.mod, + ops.rmod, + operator.pow, + ops.rpow, + operator.eq, + operator.ne, + operator.lt, + operator.le, + operator.gt, + operator.ge, + operator.and_, + ops.rand_, + operator.xor, + ops.rxor, + operator.or_, + ops.ror_, + ] +) +def all_binary_operators(request): + """ + Fixture for operator and roperator arithmetic, comparison, and logical ops. + """ + return request.param + + @pytest.fixture( params=[ operator.add, diff --git a/pandas/core/ops/__init__.py b/pandas/core/ops/__init__.py index 2dc97a3583dfb..ac16edd0a7bd5 100644 --- a/pandas/core/ops/__init__.py +++ b/pandas/core/ops/__init__.py @@ -334,9 +334,8 @@ def arith_method_SERIES(cls, op, special): @unpack_zerodim_and_defer(op_name) def wrapper(left, right): - - left, right = _align_method_SERIES(left, right) res_name = get_op_result_name(left, right) + left, right = _align_method_SERIES(left, right) lvalues = extract_array(left, extract_numpy=True) rvalues = extract_array(right, extract_numpy=True) @@ -385,8 +384,8 @@ def bool_method_SERIES(cls, op, special): @unpack_zerodim_and_defer(op_name) def wrapper(self, other): - self, other = _align_method_SERIES(self, other, align_asobject=True) res_name = get_op_result_name(self, other) + self, other = _align_method_SERIES(self, other, align_asobject=True) lvalues = extract_array(self, extract_numpy=True) rvalues = extract_array(other, extract_numpy=True) @@ -409,13 +408,17 @@ def flex_wrapper(self, other, level=None, fill_value=None, axis=0): if axis is not None: self._get_axis_number(axis) + res_name = get_op_result_name(self, other) + if isinstance(other, ABCSeries): return self._binop(other, op, level=level, fill_value=fill_value) elif isinstance(other, (np.ndarray, list, tuple)): if len(other) != len(self): raise ValueError("Lengths must be equal") other = self._constructor(other, self.index) - return self._binop(other, op, level=level, fill_value=fill_value) + result = self._binop(other, op, level=level, fill_value=fill_value) + result.name = res_name + return result else: if fill_value is not None: self = self.fillna(fill_value) diff --git a/pandas/tests/series/test_arithmetic.py b/pandas/tests/series/test_arithmetic.py index f30246ff12fac..09181201beee4 100644 --- a/pandas/tests/series/test_arithmetic.py +++ b/pandas/tests/series/test_arithmetic.py @@ -699,3 +699,45 @@ def test_datetime_understood(self): result = series - offset expected = pd.Series(pd.to_datetime(["2011-12-26", "2011-12-27", "2011-12-28"])) tm.assert_series_equal(result, expected) + + +@pytest.mark.parametrize( + "names", + [ + ("foo", None, None), + ("Egon", "Venkman", None), + ("NCC1701D", "NCC1701D", "NCC1701D"), + ], +) +@pytest.mark.parametrize("box", [list, tuple, np.array, pd.Index, pd.Series, pd.array]) +@pytest.mark.parametrize("flex", [True, False]) +def test_series_ops_name_retention(flex, box, names, all_binary_operators): + # GH#33930 consistent name renteiton + op = all_binary_operators + + if op is ops.rfloordiv and box in [list, tuple]: + pytest.xfail("op fails because of inconsistent ndarray-wrapping GH#28759") + + left = pd.Series(range(10), name=names[0]) + right = pd.Series(range(10), name=names[1]) + + right = box(right) + if flex: + name = op.__name__.strip("_") + if name in ["and", "rand", "xor", "rxor", "or", "ror"]: + # Series doesn't have these as flex methods + return + result = getattr(left, name)(right) + else: + result = op(left, right) + + if box is pd.Index and op.__name__.strip("_") in ["rxor", "ror", "rand"]: + # Index treats these as set operators, so does not defer + assert isinstance(result, pd.Index) + return + + assert isinstance(result, Series) + if box in [pd.Index, pd.Series]: + assert result.name == names[2] + else: + assert result.name == names[0] From 6c1ab289dde0eb938a69a783bc1fcec692bb550c Mon Sep 17 00:00:00 2001 From: Brock Date: Fri, 2 Oct 2020 16:03:46 -0700 Subject: [PATCH 2/2] whatsnew --- doc/source/whatsnew/v1.2.0.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/whatsnew/v1.2.0.rst b/doc/source/whatsnew/v1.2.0.rst index 6d1196b783f74..9b1dfc060cb7c 100644 --- a/doc/source/whatsnew/v1.2.0.rst +++ b/doc/source/whatsnew/v1.2.0.rst @@ -329,6 +329,7 @@ Numeric - Bug in :class:`Series` where two :class:`Series` each have a :class:`DatetimeIndex` with different timezones having those indexes incorrectly changed when performing arithmetic operations (:issue:`33671`) - Bug in :meth:`pd._testing.assert_almost_equal` was incorrect for complex numeric types (:issue:`28235`) - Bug in :meth:`DataFrame.__rmatmul__` error handling reporting transposed shapes (:issue:`21581`) +- Bug in :class:`Series` flex arithmetic methods where the result when operating with a ``list``, ``tuple`` or ``np.ndarray`` would have an incorrect name (:issue:`36760`) Conversion ^^^^^^^^^^