Skip to content

Commit 9b07b42

Browse files
committed
ENH: improved handling of NAs in binary ops with object Series, GH #737
1 parent 7187159 commit 9b07b42

File tree

3 files changed

+44
-5
lines changed

3 files changed

+44
-5
lines changed

RELEASE.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ pandas 0.7.0
8989
#657)
9090
- Implement array interface on Panel so that ufuncs work (re: #740)
9191
- Add ``sort`` option to ``DataFrame.join`` (GH #731)
92+
- Improved handling of NAs (propagation) in binary operations with
93+
dtype=object arrays (GH #737)
9294

9395
**API Changes**
9496

pandas/core/series.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,27 +43,46 @@ def _arith_method(op, name):
4343
Wrapper function for Series arithmetic operations, to avoid
4444
code duplication.
4545
"""
46+
def na_op(x, y):
47+
try:
48+
result = op(x, y)
49+
except TypeError:
50+
if isinstance(x, np.ndarray) and isinstance(y, np.ndarray):
51+
mask = notnull(x) & notnull(y)
52+
result = np.empty(len(x), dtype=x.dtype)
53+
result[mask] = op(x[mask], y[mask])
54+
elif isinstance(x, np.ndarray):
55+
mask = notnull(x)
56+
result = np.empty(len(x), dtype=x.dtype)
57+
result[mask] = op(x[mask], y)
58+
else:
59+
mask = notnull(y)
60+
result = np.empty(len(y), dtype=y.dtype)
61+
result[mask] = op(x, y[mask])
62+
63+
return result
64+
4665
def wrapper(self, other):
4766
from pandas.core.frame import DataFrame
4867

4968
if isinstance(other, Series):
5069
if self.index.equals(other.index):
5170
name = _maybe_match_name(self, other)
52-
return Series(op(self.values, other.values), index=self.index,
53-
name=name)
71+
return Series(na_op(self.values, other.values),
72+
index=self.index, name=name)
5473

5574
this_reindexed, other_reindexed = self.align(other, join='outer',
5675
copy=False)
57-
arr = op(this_reindexed.values, other_reindexed.values)
76+
arr = na_op(this_reindexed.values, other_reindexed.values)
5877

5978
name = _maybe_match_name(self, other)
6079
return Series(arr, index=this_reindexed.index, name=name)
6180
elif isinstance(other, DataFrame):
6281
return NotImplemented
6382
else:
6483
# scalars
65-
return Series(op(self.values, other), index=self.index,
66-
name=self.name)
84+
return Series(na_op(self.values, other),
85+
index=self.index, name=self.name)
6786
return wrapper
6887

6988
def _radd_compat(left, right):

pandas/tests/test_series.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1135,6 +1135,24 @@ def test_operators_empty_int_corner(self):
11351135
# expected = (self.ts >= -0.5) & (self.ts <= 0.5)
11361136
# assert_series_equal(selector, expected)
11371137

1138+
def test_operators_na_handling(self):
1139+
from decimal import Decimal
1140+
from datetime import date
1141+
s = Series([Decimal('1.3'), Decimal('2.3')],
1142+
index=[date(2012,1,1), date(2012,1,2)])
1143+
1144+
result = s + s.shift(1)
1145+
self.assert_(isnull(result[0]))
1146+
1147+
s = Series(['foo', 'bar', 'baz', np.nan])
1148+
result = 'prefix_' + s
1149+
expected = Series(['prefix_foo', 'prefix_bar', 'prefix_baz', np.nan])
1150+
assert_series_equal(result, expected)
1151+
1152+
result = s + '_suffix'
1153+
expected = Series(['foo_suffix', 'bar_suffix', 'baz_suffix', np.nan])
1154+
assert_series_equal(result, expected)
1155+
11381156
def test_idxmin(self):
11391157
# test idxmin
11401158
# _check_stat_op approach can not be used here because of isnull check.

0 commit comments

Comments
 (0)