Skip to content

Commit b835127

Browse files
deniederhutjreback
authored andcommitted
Bug: adds support for unary plus (#19297)
1 parent 34b86fd commit b835127

File tree

6 files changed

+80
-51
lines changed

6 files changed

+80
-51
lines changed

doc/source/whatsnew/v0.23.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ Current Behavior:
253253
Other Enhancements
254254
^^^^^^^^^^^^^^^^^^
255255

256+
- Unary ``+`` now permitted for ``Series`` and ``DataFrame`` as numeric operator (:issue:`16073`)
256257
- Better support for :func:`Dataframe.style.to_excel` output with the ``xlsxwriter`` engine. (:issue:`16149`)
257258
- :func:`pandas.tseries.frequencies.to_offset` now accepts leading '+' signs e.g. '+1h'. (:issue:`18171`)
258259
- :func:`MultiIndex.unique` now supports the ``level=`` argument, to get unique values from a specific index level (:issue:`17896`)

pandas/core/generic.py

+17-2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
is_list_like,
2626
is_dict_like,
2727
is_re_compilable,
28+
is_period_arraylike,
2829
pandas_dtype)
2930
from pandas.core.dtypes.cast import maybe_promote, maybe_upcast_putmask
3031
from pandas.core.dtypes.inference import is_hashable
@@ -1027,10 +1028,24 @@ def _indexed_same(self, other):
10271028

10281029
def __neg__(self):
10291030
values = com._values_from_object(self)
1030-
if values.dtype == np.bool_:
1031+
if is_bool_dtype(values):
10311032
arr = operator.inv(values)
1032-
else:
1033+
elif (is_numeric_dtype(values) or is_timedelta64_dtype(values)):
10331034
arr = operator.neg(values)
1035+
else:
1036+
raise TypeError("Unary negative expects numeric dtype, not {}"
1037+
.format(values.dtype))
1038+
return self.__array_wrap__(arr)
1039+
1040+
def __pos__(self):
1041+
values = com._values_from_object(self)
1042+
if (is_bool_dtype(values) or is_period_arraylike(values)):
1043+
arr = values
1044+
elif (is_numeric_dtype(values) or is_timedelta64_dtype(values)):
1045+
arr = operator.pos(values)
1046+
else:
1047+
raise TypeError("Unary plus expects numeric dtype, not {}"
1048+
.format(values.dtype))
10341049
return self.__array_wrap__(arr)
10351050

10361051
def __invert__(self):

pandas/tests/computation/test_eval.py

+18-42
Original file line numberDiff line numberDiff line change
@@ -542,66 +542,42 @@ def test_frame_pos(self):
542542

543543
# float
544544
lhs = DataFrame(randn(5, 2))
545-
if self.engine == 'python':
546-
with pytest.raises(TypeError):
547-
result = pd.eval(expr, engine=self.engine, parser=self.parser)
548-
else:
549-
expect = lhs
550-
result = pd.eval(expr, engine=self.engine, parser=self.parser)
551-
assert_frame_equal(expect, result)
545+
expect = lhs
546+
result = pd.eval(expr, engine=self.engine, parser=self.parser)
547+
assert_frame_equal(expect, result)
552548

553549
# int
554550
lhs = DataFrame(randint(5, size=(5, 2)))
555-
if self.engine == 'python':
556-
with pytest.raises(TypeError):
557-
result = pd.eval(expr, engine=self.engine, parser=self.parser)
558-
else:
559-
expect = lhs
560-
result = pd.eval(expr, engine=self.engine, parser=self.parser)
561-
assert_frame_equal(expect, result)
551+
expect = lhs
552+
result = pd.eval(expr, engine=self.engine, parser=self.parser)
553+
assert_frame_equal(expect, result)
562554

563555
# bool doesn't work with numexpr but works elsewhere
564556
lhs = DataFrame(rand(5, 2) > 0.5)
565-
if self.engine == 'python':
566-
with pytest.raises(TypeError):
567-
result = pd.eval(expr, engine=self.engine, parser=self.parser)
568-
else:
569-
expect = lhs
570-
result = pd.eval(expr, engine=self.engine, parser=self.parser)
571-
assert_frame_equal(expect, result)
557+
expect = lhs
558+
result = pd.eval(expr, engine=self.engine, parser=self.parser)
559+
assert_frame_equal(expect, result)
572560

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

576564
# float
577565
lhs = Series(randn(5))
578-
if self.engine == 'python':
579-
with pytest.raises(TypeError):
580-
result = pd.eval(expr, engine=self.engine, parser=self.parser)
581-
else:
582-
expect = lhs
583-
result = pd.eval(expr, engine=self.engine, parser=self.parser)
584-
assert_series_equal(expect, result)
566+
expect = lhs
567+
result = pd.eval(expr, engine=self.engine, parser=self.parser)
568+
assert_series_equal(expect, result)
585569

586570
# int
587571
lhs = Series(randint(5, size=5))
588-
if self.engine == 'python':
589-
with pytest.raises(TypeError):
590-
result = pd.eval(expr, engine=self.engine, parser=self.parser)
591-
else:
592-
expect = lhs
593-
result = pd.eval(expr, engine=self.engine, parser=self.parser)
594-
assert_series_equal(expect, result)
572+
expect = lhs
573+
result = pd.eval(expr, engine=self.engine, parser=self.parser)
574+
assert_series_equal(expect, result)
595575

596576
# bool doesn't work with numexpr but works elsewhere
597577
lhs = Series(rand(5) > 0.5)
598-
if self.engine == 'python':
599-
with pytest.raises(TypeError):
600-
result = pd.eval(expr, engine=self.engine, parser=self.parser)
601-
else:
602-
expect = lhs
603-
result = pd.eval(expr, engine=self.engine, parser=self.parser)
604-
assert_series_equal(expect, result)
578+
expect = lhs
579+
result = pd.eval(expr, engine=self.engine, parser=self.parser)
580+
assert_series_equal(expect, result)
605581

606582
def test_scalar_unary(self):
607583
with pytest.raises(TypeError):

pandas/tests/frame/test_arithmetic.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ def test_ops_frame_period(self):
245245
exp = pd.DataFrame({'A': np.array([2, 1], dtype=object),
246246
'B': np.array([14, 13], dtype=object)})
247247
tm.assert_frame_equal(p - df, exp)
248-
tm.assert_frame_equal(df - p, -exp)
248+
tm.assert_frame_equal(df - p, -1 * exp)
249249

250250
df2 = pd.DataFrame({'A': [pd.Period('2015-05', freq='M'),
251251
pd.Period('2015-06', freq='M')],
@@ -257,4 +257,4 @@ def test_ops_frame_period(self):
257257
exp = pd.DataFrame({'A': np.array([4, 4], dtype=object),
258258
'B': np.array([16, 16], dtype=object)})
259259
tm.assert_frame_equal(df2 - df, exp)
260-
tm.assert_frame_equal(df - df2, -exp)
260+
tm.assert_frame_equal(df - df2, -1 * exp)

pandas/tests/frame/test_operators.py

+40-3
Original file line numberDiff line numberDiff line change
@@ -271,13 +271,50 @@ def test_logical_with_nas(self):
271271
expected = Series([True, True])
272272
assert_series_equal(result, expected)
273273

274-
def test_neg(self):
275-
# what to do?
276-
assert_frame_equal(-self.frame, -1 * self.frame)
274+
@pytest.mark.parametrize('df,expected', [
275+
(pd.DataFrame({'a': [-1, 1]}), pd.DataFrame({'a': [1, -1]})),
276+
(pd.DataFrame({'a': [False, True]}),
277+
pd.DataFrame({'a': [True, False]})),
278+
(pd.DataFrame({'a': pd.Series(pd.to_timedelta([-1, 1]))}),
279+
pd.DataFrame({'a': pd.Series(pd.to_timedelta([1, -1]))}))
280+
])
281+
def test_neg_numeric(self, df, expected):
282+
assert_frame_equal(-df, expected)
283+
assert_series_equal(-df['a'], expected['a'])
284+
285+
@pytest.mark.parametrize('df', [
286+
pd.DataFrame({'a': ['a', 'b']}),
287+
pd.DataFrame({'a': pd.to_datetime(['2017-01-22', '1970-01-01'])}),
288+
])
289+
def test_neg_raises(self, df):
290+
with pytest.raises(TypeError):
291+
(- df)
292+
with pytest.raises(TypeError):
293+
(- df['a'])
277294

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

298+
@pytest.mark.parametrize('df', [
299+
pd.DataFrame({'a': [-1, 1]}),
300+
pd.DataFrame({'a': [False, True]}),
301+
pd.DataFrame({'a': pd.Series(pd.to_timedelta([-1, 1]))}),
302+
])
303+
def test_pos_numeric(self, df):
304+
# GH 16073
305+
assert_frame_equal(+df, df)
306+
assert_series_equal(+df['a'], df['a'])
307+
308+
@pytest.mark.parametrize('df', [
309+
pd.DataFrame({'a': ['a', 'b']}),
310+
pd.DataFrame({'a': pd.to_datetime(['2017-01-22', '1970-01-01'])}),
311+
])
312+
def test_pos_raises(self, df):
313+
with pytest.raises(TypeError):
314+
(+ df)
315+
with pytest.raises(TypeError):
316+
(+ df['a'])
317+
281318
def test_arith_flex_frame(self):
282319
ops = ['add', 'sub', 'mul', 'div', 'truediv', 'pow', 'floordiv', 'mod']
283320
if not compat.PY3:

pandas/tests/series/test_arithmetic.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -315,15 +315,15 @@ def test_ops_series_period(self):
315315
# dtype will be object because of original dtype
316316
expected = pd.Series([9, 8], name='xxx', dtype=object)
317317
tm.assert_series_equal(per - ser, expected)
318-
tm.assert_series_equal(ser - per, -expected)
318+
tm.assert_series_equal(ser - per, -1 * expected)
319319

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

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

328328

329329
class TestTimestampSeriesArithmetic(object):

0 commit comments

Comments
 (0)