diff --git a/RELEASE.rst b/RELEASE.rst index 58ca5d124f5d7..01f9c1a777d30 100644 --- a/RELEASE.rst +++ b/RELEASE.rst @@ -89,6 +89,8 @@ pandas 0.7.0 #657) - Implement array interface on Panel so that ufuncs work (re: #740) - Add ``sort`` option to ``DataFrame.join`` (GH #731) + - Improved handling of NAs (propagation) in binary operations with + dtype=object arrays (GH #737) **API Changes** diff --git a/pandas/core/series.py b/pandas/core/series.py index 0db939213b1a7..8def81a12d166 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -43,18 +43,37 @@ def _arith_method(op, name): Wrapper function for Series arithmetic operations, to avoid code duplication. """ + def na_op(x, y): + try: + result = op(x, y) + except TypeError: + if isinstance(x, np.ndarray) and isinstance(y, np.ndarray): + mask = notnull(x) & notnull(y) + result = np.empty(len(x), dtype=x.dtype) + result[mask] = op(x[mask], y[mask]) + elif isinstance(x, np.ndarray): + mask = notnull(x) + result = np.empty(len(x), dtype=x.dtype) + result[mask] = op(x[mask], y) + else: + mask = notnull(y) + result = np.empty(len(y), dtype=y.dtype) + result[mask] = op(x, y[mask]) + + return result + def wrapper(self, other): from pandas.core.frame import DataFrame if isinstance(other, Series): if self.index.equals(other.index): name = _maybe_match_name(self, other) - return Series(op(self.values, other.values), index=self.index, - name=name) + return Series(na_op(self.values, other.values), + index=self.index, name=name) this_reindexed, other_reindexed = self.align(other, join='outer', copy=False) - arr = op(this_reindexed.values, other_reindexed.values) + arr = na_op(this_reindexed.values, other_reindexed.values) name = _maybe_match_name(self, other) return Series(arr, index=this_reindexed.index, name=name) @@ -62,8 +81,8 @@ def wrapper(self, other): return NotImplemented else: # scalars - return Series(op(self.values, other), index=self.index, - name=self.name) + return Series(na_op(self.values, other), + index=self.index, name=self.name) return wrapper def _radd_compat(left, right): diff --git a/pandas/tests/test_series.py b/pandas/tests/test_series.py index 40326f020d732..b623ad8780aec 100644 --- a/pandas/tests/test_series.py +++ b/pandas/tests/test_series.py @@ -1135,6 +1135,24 @@ def test_operators_empty_int_corner(self): # expected = (self.ts >= -0.5) & (self.ts <= 0.5) # assert_series_equal(selector, expected) + def test_operators_na_handling(self): + from decimal import Decimal + from datetime import date + s = Series([Decimal('1.3'), Decimal('2.3')], + index=[date(2012,1,1), date(2012,1,2)]) + + result = s + s.shift(1) + self.assert_(isnull(result[0])) + + s = Series(['foo', 'bar', 'baz', np.nan]) + result = 'prefix_' + s + expected = Series(['prefix_foo', 'prefix_bar', 'prefix_baz', np.nan]) + assert_series_equal(result, expected) + + result = s + '_suffix' + expected = Series(['foo_suffix', 'bar_suffix', 'baz_suffix', np.nan]) + assert_series_equal(result, expected) + def test_idxmin(self): # test idxmin # _check_stat_op approach can not be used here because of isnull check.