Skip to content

Commit

Permalink
Bug: adds support for unary plus (pandas-dev#19297)
Browse files Browse the repository at this point in the history
  • Loading branch information
deniederhut authored and jreback committed Feb 8, 2018
1 parent 34b86fd commit b835127
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 51 deletions.
1 change: 1 addition & 0 deletions doc/source/whatsnew/v0.23.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ Current Behavior:
Other Enhancements
^^^^^^^^^^^^^^^^^^

- Unary ``+`` now permitted for ``Series`` and ``DataFrame`` as numeric operator (:issue:`16073`)
- Better support for :func:`Dataframe.style.to_excel` output with the ``xlsxwriter`` engine. (:issue:`16149`)
- :func:`pandas.tseries.frequencies.to_offset` now accepts leading '+' signs e.g. '+1h'. (:issue:`18171`)
- :func:`MultiIndex.unique` now supports the ``level=`` argument, to get unique values from a specific index level (:issue:`17896`)
Expand Down
19 changes: 17 additions & 2 deletions pandas/core/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
is_list_like,
is_dict_like,
is_re_compilable,
is_period_arraylike,
pandas_dtype)
from pandas.core.dtypes.cast import maybe_promote, maybe_upcast_putmask
from pandas.core.dtypes.inference import is_hashable
Expand Down Expand Up @@ -1027,10 +1028,24 @@ def _indexed_same(self, other):

def __neg__(self):
values = com._values_from_object(self)
if values.dtype == np.bool_:
if is_bool_dtype(values):
arr = operator.inv(values)
else:
elif (is_numeric_dtype(values) or is_timedelta64_dtype(values)):
arr = operator.neg(values)
else:
raise TypeError("Unary negative expects numeric dtype, not {}"
.format(values.dtype))
return self.__array_wrap__(arr)

def __pos__(self):
values = com._values_from_object(self)
if (is_bool_dtype(values) or is_period_arraylike(values)):
arr = values
elif (is_numeric_dtype(values) or is_timedelta64_dtype(values)):
arr = operator.pos(values)
else:
raise TypeError("Unary plus expects numeric dtype, not {}"
.format(values.dtype))
return self.__array_wrap__(arr)

def __invert__(self):
Expand Down
60 changes: 18 additions & 42 deletions pandas/tests/computation/test_eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -542,66 +542,42 @@ def test_frame_pos(self):

# float
lhs = DataFrame(randn(5, 2))
if self.engine == 'python':
with pytest.raises(TypeError):
result = pd.eval(expr, engine=self.engine, parser=self.parser)
else:
expect = lhs
result = pd.eval(expr, engine=self.engine, parser=self.parser)
assert_frame_equal(expect, result)
expect = lhs
result = pd.eval(expr, engine=self.engine, parser=self.parser)
assert_frame_equal(expect, result)

# int
lhs = DataFrame(randint(5, size=(5, 2)))
if self.engine == 'python':
with pytest.raises(TypeError):
result = pd.eval(expr, engine=self.engine, parser=self.parser)
else:
expect = lhs
result = pd.eval(expr, engine=self.engine, parser=self.parser)
assert_frame_equal(expect, result)
expect = lhs
result = pd.eval(expr, engine=self.engine, parser=self.parser)
assert_frame_equal(expect, result)

# bool doesn't work with numexpr but works elsewhere
lhs = DataFrame(rand(5, 2) > 0.5)
if self.engine == 'python':
with pytest.raises(TypeError):
result = pd.eval(expr, engine=self.engine, parser=self.parser)
else:
expect = lhs
result = pd.eval(expr, engine=self.engine, parser=self.parser)
assert_frame_equal(expect, result)
expect = lhs
result = pd.eval(expr, engine=self.engine, parser=self.parser)
assert_frame_equal(expect, result)

def test_series_pos(self):
expr = self.ex('+')

# float
lhs = Series(randn(5))
if self.engine == 'python':
with pytest.raises(TypeError):
result = pd.eval(expr, engine=self.engine, parser=self.parser)
else:
expect = lhs
result = pd.eval(expr, engine=self.engine, parser=self.parser)
assert_series_equal(expect, result)
expect = lhs
result = pd.eval(expr, engine=self.engine, parser=self.parser)
assert_series_equal(expect, result)

# int
lhs = Series(randint(5, size=5))
if self.engine == 'python':
with pytest.raises(TypeError):
result = pd.eval(expr, engine=self.engine, parser=self.parser)
else:
expect = lhs
result = pd.eval(expr, engine=self.engine, parser=self.parser)
assert_series_equal(expect, result)
expect = lhs
result = pd.eval(expr, engine=self.engine, parser=self.parser)
assert_series_equal(expect, result)

# bool doesn't work with numexpr but works elsewhere
lhs = Series(rand(5) > 0.5)
if self.engine == 'python':
with pytest.raises(TypeError):
result = pd.eval(expr, engine=self.engine, parser=self.parser)
else:
expect = lhs
result = pd.eval(expr, engine=self.engine, parser=self.parser)
assert_series_equal(expect, result)
expect = lhs
result = pd.eval(expr, engine=self.engine, parser=self.parser)
assert_series_equal(expect, result)

def test_scalar_unary(self):
with pytest.raises(TypeError):
Expand Down
4 changes: 2 additions & 2 deletions pandas/tests/frame/test_arithmetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ def test_ops_frame_period(self):
exp = pd.DataFrame({'A': np.array([2, 1], dtype=object),
'B': np.array([14, 13], dtype=object)})
tm.assert_frame_equal(p - df, exp)
tm.assert_frame_equal(df - p, -exp)
tm.assert_frame_equal(df - p, -1 * exp)

df2 = pd.DataFrame({'A': [pd.Period('2015-05', freq='M'),
pd.Period('2015-06', freq='M')],
Expand All @@ -257,4 +257,4 @@ def test_ops_frame_period(self):
exp = pd.DataFrame({'A': np.array([4, 4], dtype=object),
'B': np.array([16, 16], dtype=object)})
tm.assert_frame_equal(df2 - df, exp)
tm.assert_frame_equal(df - df2, -exp)
tm.assert_frame_equal(df - df2, -1 * exp)
43 changes: 40 additions & 3 deletions pandas/tests/frame/test_operators.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,13 +271,50 @@ def test_logical_with_nas(self):
expected = Series([True, True])
assert_series_equal(result, expected)

def test_neg(self):
# what to do?
assert_frame_equal(-self.frame, -1 * self.frame)
@pytest.mark.parametrize('df,expected', [
(pd.DataFrame({'a': [-1, 1]}), pd.DataFrame({'a': [1, -1]})),
(pd.DataFrame({'a': [False, True]}),
pd.DataFrame({'a': [True, False]})),
(pd.DataFrame({'a': pd.Series(pd.to_timedelta([-1, 1]))}),
pd.DataFrame({'a': pd.Series(pd.to_timedelta([1, -1]))}))
])
def test_neg_numeric(self, df, expected):
assert_frame_equal(-df, expected)
assert_series_equal(-df['a'], expected['a'])

@pytest.mark.parametrize('df', [
pd.DataFrame({'a': ['a', 'b']}),
pd.DataFrame({'a': pd.to_datetime(['2017-01-22', '1970-01-01'])}),
])
def test_neg_raises(self, df):
with pytest.raises(TypeError):
(- df)
with pytest.raises(TypeError):
(- df['a'])

def test_invert(self):
assert_frame_equal(-(self.frame < 0), ~(self.frame < 0))

@pytest.mark.parametrize('df', [
pd.DataFrame({'a': [-1, 1]}),
pd.DataFrame({'a': [False, True]}),
pd.DataFrame({'a': pd.Series(pd.to_timedelta([-1, 1]))}),
])
def test_pos_numeric(self, df):
# GH 16073
assert_frame_equal(+df, df)
assert_series_equal(+df['a'], df['a'])

@pytest.mark.parametrize('df', [
pd.DataFrame({'a': ['a', 'b']}),
pd.DataFrame({'a': pd.to_datetime(['2017-01-22', '1970-01-01'])}),
])
def test_pos_raises(self, df):
with pytest.raises(TypeError):
(+ df)
with pytest.raises(TypeError):
(+ df['a'])

def test_arith_flex_frame(self):
ops = ['add', 'sub', 'mul', 'div', 'truediv', 'pow', 'floordiv', 'mod']
if not compat.PY3:
Expand Down
4 changes: 2 additions & 2 deletions pandas/tests/series/test_arithmetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,15 +315,15 @@ def test_ops_series_period(self):
# dtype will be object because of original dtype
expected = pd.Series([9, 8], name='xxx', dtype=object)
tm.assert_series_equal(per - ser, expected)
tm.assert_series_equal(ser - per, -expected)
tm.assert_series_equal(ser - per, -1 * expected)

s2 = pd.Series([pd.Period('2015-01-05', freq='D'),
pd.Period('2015-01-04', freq='D')], name='xxx')
assert s2.dtype == object

expected = pd.Series([4, 2], name='xxx', dtype=object)
tm.assert_series_equal(s2 - ser, expected)
tm.assert_series_equal(ser - s2, -expected)
tm.assert_series_equal(ser - s2, -1 * expected)


class TestTimestampSeriesArithmetic(object):
Expand Down

0 comments on commit b835127

Please sign in to comment.