Skip to content

Commit 0ca3022

Browse files
committed
fixes pandas-dev#60908: Fix ValueError when subtracting Series with NaNs
and mismatched MultiIndex and Index lengths
1 parent a811388 commit 0ca3022

File tree

2 files changed

+65
-23
lines changed

2 files changed

+65
-23
lines changed

pandas/core/series.py

+27-23
Original file line numberDiff line numberDiff line change
@@ -5890,32 +5890,36 @@ def _arith_method(self, other, op):
58905890
self, other = self._align_for_op(other)
58915891
return base.IndexOpsMixin._arith_method(self, other, op)
58925892

5893-
def _align_for_op(self, right, align_asobject: bool = False):
5894-
"""align lhs and rhs Series"""
5895-
# TODO: Different from DataFrame._align_for_op, list, tuple and ndarray
5896-
# are not coerced here
5897-
# because Series has inconsistencies described in GH#13637
5893+
def _align_for_op(self, right, align_asobject=False, fill_value=np.nan):
5894+
"""Align lhs and rhs Series for arithmetic operations"""
5895+
58985896
left = self
58995897

5900-
if isinstance(right, Series):
5901-
# avoid repeated alignment
5902-
if not left.index.equals(right.index):
5903-
if align_asobject:
5904-
if left.dtype not in (object, np.bool_) or right.dtype not in (
5905-
object,
5906-
np.bool_,
5907-
):
5908-
pass
5909-
# GH#52538 no longer cast in these cases
5910-
else:
5911-
# to keep original value's dtype for bool ops
5912-
left = left.astype(object)
5913-
right = right.astype(object)
5914-
5915-
left, right = left.align(right)
5916-
5917-
return left, right
5898+
if not isinstance(right, Series):
5899+
return left, right
5900+
5901+
if left.index.equals(right.index):
5902+
return left, right
59185903

5904+
if not (hasattr(left.index, "levels") or hasattr(right.index, "levels")):
5905+
if align_asobject:
5906+
if left.empty or right.empty:
5907+
if left.dtype not in (object, np.bool_) or right.dtype not in (object, np.bool_):
5908+
return left.iloc[0:0], right.iloc[0:0]
5909+
return left.align(right, join='outer', fill_value=fill_value)
5910+
5911+
if hasattr(left.index, "levels") and not hasattr(right.index, "levels"):
5912+
if left.empty or right.empty:
5913+
return left.iloc[0:0], right.iloc[0:0]
5914+
else:
5915+
first_level = left.index.get_level_values(0)
5916+
left = left.astype(object)
5917+
right = right.astype(object)
5918+
right_aligned = right.reindex(first_level, fill_value=fill_value)
5919+
return left, right_aligned
5920+
5921+
return left.align(right, join='outer', fill_value=fill_value)
5922+
59195923
def _binop(self, other: Series, func, level=None, fill_value=None) -> Series:
59205924
"""
59215925
Perform generic binary operation with optional fill value.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import pytest
2+
import pandas as pd
3+
import numpy as np
4+
import pandas.testing as tm
5+
6+
def test_series_subtraction_with_nan_and_levels():
7+
ix1 = pd.MultiIndex.from_arrays(
8+
[
9+
[np.nan, 81, 81, 82, 82],
10+
[np.nan] * 5,
11+
pd.to_datetime([np.nan, '2018-06-01', '2018-07-01', '2018-07-01', '2018-08-01'])
12+
],
13+
names=['foo', 'bar', 'date']
14+
)
15+
16+
s1 = pd.Series(
17+
[np.nan, 25.058969, 22.519751, 20.847981, 21.625236],
18+
index=ix1
19+
)
20+
21+
ix2 = pd.Index([81, 82, 83, 84, 85, 86, 87], name='foo')
22+
s2 = pd.Series(
23+
[28.2800, 25.2500, 22.2200, 16.7660, 14.0087, 14.9480, 29.2900],
24+
index=ix2
25+
)
26+
27+
expected = pd.Series(
28+
[np.nan, -3.221031, -5.760249, -4.402019, -3.624764],
29+
index=ix1,
30+
dtype='float64'
31+
)
32+
33+
result = s1 - s2
34+
35+
result = result.astype('float64')
36+
37+
tm.assert_series_equal(result, expected)
38+

0 commit comments

Comments
 (0)