Skip to content

Commit da3a2d3

Browse files
BUG: inconsistent name-retention in Series ops (#36760)
* BUG: inconsistent name-retention in Series ops * whatsnew Co-authored-by: Jeff Reback <[email protected]>
1 parent 5bf4e7f commit da3a2d3

File tree

4 files changed

+87
-4
lines changed

4 files changed

+87
-4
lines changed

doc/source/whatsnew/v1.2.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,7 @@ Numeric
357357
- 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`)
358358
- Bug in :meth:`pd._testing.assert_almost_equal` was incorrect for complex numeric types (:issue:`28235`)
359359
- Bug in :meth:`DataFrame.__rmatmul__` error handling reporting transposed shapes (:issue:`21581`)
360+
- 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`)
360361
- Bug in :class:`IntegerArray` multiplication with ``timedelta`` and ``np.timedelta64`` objects (:issue:`36870`)
361362

362363
Conversion

pandas/conftest.py

+37
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,43 @@ def all_arithmetic_operators(request):
677677
return request.param
678678

679679

680+
@pytest.fixture(
681+
params=[
682+
operator.add,
683+
ops.radd,
684+
operator.sub,
685+
ops.rsub,
686+
operator.mul,
687+
ops.rmul,
688+
operator.truediv,
689+
ops.rtruediv,
690+
operator.floordiv,
691+
ops.rfloordiv,
692+
operator.mod,
693+
ops.rmod,
694+
operator.pow,
695+
ops.rpow,
696+
operator.eq,
697+
operator.ne,
698+
operator.lt,
699+
operator.le,
700+
operator.gt,
701+
operator.ge,
702+
operator.and_,
703+
ops.rand_,
704+
operator.xor,
705+
ops.rxor,
706+
operator.or_,
707+
ops.ror_,
708+
]
709+
)
710+
def all_binary_operators(request):
711+
"""
712+
Fixture for operator and roperator arithmetic, comparison, and logical ops.
713+
"""
714+
return request.param
715+
716+
680717
@pytest.fixture(
681718
params=[
682719
operator.add,

pandas/core/ops/__init__.py

+7-4
Original file line numberDiff line numberDiff line change
@@ -310,9 +310,8 @@ def arith_method_SERIES(cls, op, special):
310310

311311
@unpack_zerodim_and_defer(op_name)
312312
def wrapper(left, right):
313-
314-
left, right = _align_method_SERIES(left, right)
315313
res_name = get_op_result_name(left, right)
314+
left, right = _align_method_SERIES(left, right)
316315

317316
lvalues = extract_array(left, extract_numpy=True)
318317
rvalues = extract_array(right, extract_numpy=True)
@@ -361,8 +360,8 @@ def bool_method_SERIES(cls, op, special):
361360

362361
@unpack_zerodim_and_defer(op_name)
363362
def wrapper(self, other):
364-
self, other = _align_method_SERIES(self, other, align_asobject=True)
365363
res_name = get_op_result_name(self, other)
364+
self, other = _align_method_SERIES(self, other, align_asobject=True)
366365

367366
lvalues = extract_array(self, extract_numpy=True)
368367
rvalues = extract_array(other, extract_numpy=True)
@@ -385,13 +384,17 @@ def flex_wrapper(self, other, level=None, fill_value=None, axis=0):
385384
if axis is not None:
386385
self._get_axis_number(axis)
387386

387+
res_name = get_op_result_name(self, other)
388+
388389
if isinstance(other, ABCSeries):
389390
return self._binop(other, op, level=level, fill_value=fill_value)
390391
elif isinstance(other, (np.ndarray, list, tuple)):
391392
if len(other) != len(self):
392393
raise ValueError("Lengths must be equal")
393394
other = self._constructor(other, self.index)
394-
return self._binop(other, op, level=level, fill_value=fill_value)
395+
result = self._binop(other, op, level=level, fill_value=fill_value)
396+
result.name = res_name
397+
return result
395398
else:
396399
if fill_value is not None:
397400
self = self.fillna(fill_value)

pandas/tests/series/test_arithmetic.py

+42
Original file line numberDiff line numberDiff line change
@@ -699,3 +699,45 @@ def test_datetime_understood(self):
699699
result = series - offset
700700
expected = pd.Series(pd.to_datetime(["2011-12-26", "2011-12-27", "2011-12-28"]))
701701
tm.assert_series_equal(result, expected)
702+
703+
704+
@pytest.mark.parametrize(
705+
"names",
706+
[
707+
("foo", None, None),
708+
("Egon", "Venkman", None),
709+
("NCC1701D", "NCC1701D", "NCC1701D"),
710+
],
711+
)
712+
@pytest.mark.parametrize("box", [list, tuple, np.array, pd.Index, pd.Series, pd.array])
713+
@pytest.mark.parametrize("flex", [True, False])
714+
def test_series_ops_name_retention(flex, box, names, all_binary_operators):
715+
# GH#33930 consistent name renteiton
716+
op = all_binary_operators
717+
718+
if op is ops.rfloordiv and box in [list, tuple]:
719+
pytest.xfail("op fails because of inconsistent ndarray-wrapping GH#28759")
720+
721+
left = pd.Series(range(10), name=names[0])
722+
right = pd.Series(range(10), name=names[1])
723+
724+
right = box(right)
725+
if flex:
726+
name = op.__name__.strip("_")
727+
if name in ["and", "rand", "xor", "rxor", "or", "ror"]:
728+
# Series doesn't have these as flex methods
729+
return
730+
result = getattr(left, name)(right)
731+
else:
732+
result = op(left, right)
733+
734+
if box is pd.Index and op.__name__.strip("_") in ["rxor", "ror", "rand"]:
735+
# Index treats these as set operators, so does not defer
736+
assert isinstance(result, pd.Index)
737+
return
738+
739+
assert isinstance(result, Series)
740+
if box in [pd.Index, pd.Series]:
741+
assert result.name == names[2]
742+
else:
743+
assert result.name == names[0]

0 commit comments

Comments
 (0)