Repository: pandas-dev/pandas

Issue:
Unary plus missing for Series and DataFrame
#### Code Sample

```
In [1]: import numpy as np

In [2]: import pandas as pd

In [3]: pd.__version__
Out[3]: '0.19.2.post+ts5'

In [4]: a = np.array([-1,0,1])  # As expected

In [5]: +a
Out[5]: array([-1,  0,  1])

In [6]: s = pd.Series(data=a)

In [7]: +s  # Unexpected
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-7-d1319861d847> in <module>()
----> 1 +s

TypeError: bad operand type for unary +: 'Series'

In [8]: d = pd.DataFrame(a)

In [9]: +d  # Unexpected
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-9-e4fd8df22471> in <module>()
----> 1 +d

TypeError: bad operand type for unary +: 'DataFrame'

In [10]: -a
Out[10]: array([ 1,  0, -1])

In [11]: -s
Out[11]: 
0    1
1    0
2   -1
dtype: int64

In [12]: -d
Out[12]: 
   0
0  1
1  0
2 -1
```
#### Problem description

Unary plus is well-defined on built-in types and Numpy ndarrays.  It is not defined for Series or DataFrame.  It's not ever necessary, but the asymmetry with the present unary minus is unexpected.  It's also occasionally nice to use it to line up a positive expression against a negative expression.  Likely adding methods implemented as an identity is sufficient.

#### Expected Output

```
In [7]: +s
Out[7]: 
0   -1
1    0
2   1
dtype: int64

In [9]: +d
Out[9]: 
   0
0  -1
1  0
2 1
```

#### Output of ``pd.show_versions()``

<details>
In [13]: pd.show_versions()

INSTALLED VERSIONS
------------------
commit: None
python: 3.5.2.final.0
python-bits: 64
OS: Linux
OS-release: 4.1.35-pv-ts2
machine: x86_64
processor: 
byteorder: little
LC_ALL: en_US.UTF-8
LANG: en_US.UTF-8
LOCALE: en_US.UTF-8

pandas: 0.19.2.post+ts5
nose: 1.3.7
pip: 9.0.1
setuptools: 27.2.0
Cython: 0.25.2
numpy: 1.11.3
scipy: 0.19.0
statsmodels: None
xarray: None
IPython: 5.1.0
sphinx: 1.5.1
patsy: None
dateutil: 2.6.0
pytz: 2016.10
blosc: None
bottleneck: None
tables: None
numexpr: None
matplotlib: 1.5.1
openpyxl: 2.4.5
xlrd: 1.0.0
xlwt: None
xlsxwriter: None
lxml: None
bs4: 4.5.3
html5lib: None
httplib2: None
apiclient: None
sqlalchemy: 1.1.9
pymysql: None
psycopg2: None
jinja2: 2.9.4
boto: None
pandas_datareader: None
</details>

Unary plus missing for Series and DataFrame
#### Code Sample

```
In [1]: import numpy as np

In [2]: import pandas as pd

In [3]: pd.__version__
Out[3]: '0.19.2.post+ts5'

In [4]: a = np.array([-1,0,1])  # As expected

In [5]: +a
Out[5]: array([-1,  0,  1])

In [6]: s = pd.Series(data=a)

In [7]: +s  # Unexpected
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-7-d1319861d847> in <module>()
----> 1 +s

TypeError: bad operand type for unary +: 'Series'

In [8]: d = pd.DataFrame(a)

In [9]: +d  # Unexpected
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-9-e4fd8df22471> in <module>()
----> 1 +d

TypeError: bad operand type for unary +: 'DataFrame'

In [10]: -a
Out[10]: array([ 1,  0, -1])

In [11]: -s
Out[11]: 
0    1
1    0
2   -1
dtype: int64

In [12]: -d
Out[12]: 
   0
0  1
1  0
2 -1
```
#### Problem description

Unary plus is well-defined on built-in types and Numpy ndarrays.  It is not defined for Series or DataFrame.  It's not ever necessary, but the asymmetry with the present unary minus is unexpected.  It's also occasionally nice to use it to line up a positive expression against a negative expression.  Likely adding methods implemented as an identity is sufficient.

#### Expected Output

```
In [7]: +s
Out[7]: 
0   -1
1    0
2   1
dtype: int64

In [9]: +d
Out[9]: 
   0
0  -1
1  0
2 1
```

#### Output of ``pd.show_versions()``

<details>
In [13]: pd.show_versions()

INSTALLED VERSIONS
------------------
commit: None
python: 3.5.2.final.0
python-bits: 64
OS: Linux
OS-release: 4.1.35-pv-ts2
machine: x86_64
processor: 
byteorder: little
LC_ALL: en_US.UTF-8
LANG: en_US.UTF-8
LOCALE: en_US.UTF-8

pandas: 0.19.2.post+ts5
nose: 1.3.7
pip: 9.0.1
setuptools: 27.2.0
Cython: 0.25.2
numpy: 1.11.3
scipy: 0.19.0
statsmodels: None
xarray: None
IPython: 5.1.0
sphinx: 1.5.1
patsy: None
dateutil: 2.6.0
pytz: 2016.10
blosc: None
bottleneck: None
tables: None
numexpr: None
matplotlib: 1.5.1
openpyxl: 2.4.5
xlrd: 1.0.0
xlwt: None
xlsxwriter: None
lxml: None
bs4: 4.5.3
html5lib: None
httplib2: None
apiclient: None
sqlalchemy: 1.1.9
pymysql: None
psycopg2: None
jinja2: 2.9.4
boto: None
pandas_datareader: None
</details>



Discussion:
Obviously low priority.
sure this is pretty straightforward to add actually

see how we implement ``__neg__`` https://github.com/pandas-dev/pandas/blob/master/pandas/core/generic.py#L862

a pull request would be great! (with tests!)
Thank you @wuisawesome and @jreback. 
Obviously low priority.
sure this is pretty straightforward to add actually

see how we implement ``__neg__`` https://github.com/pandas-dev/pandas/blob/master/pandas/core/generic.py#L862

a pull request would be great! (with tests!)
Thank you @wuisawesome and @jreback. 

Resolving patch:
diff --git a/pandas/core/generic.py b/pandas/core/generic.py
--- a/pandas/core/generic.py
+++ b/pandas/core/generic.py
@@ -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
@@ -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):


Test patch:
diff --git a/pandas/tests/computation/test_eval.py b/pandas/tests/computation/test_eval.py
--- a/pandas/tests/computation/test_eval.py
+++ b/pandas/tests/computation/test_eval.py
@@ -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):
diff --git a/pandas/tests/frame/test_arithmetic.py b/pandas/tests/frame/test_arithmetic.py
--- a/pandas/tests/frame/test_arithmetic.py
+++ b/pandas/tests/frame/test_arithmetic.py
@@ -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')],
@@ -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)
diff --git a/pandas/tests/frame/test_operators.py b/pandas/tests/frame/test_operators.py
--- a/pandas/tests/frame/test_operators.py
+++ b/pandas/tests/frame/test_operators.py
@@ -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:
diff --git a/pandas/tests/series/test_arithmetic.py b/pandas/tests/series/test_arithmetic.py
--- a/pandas/tests/series/test_arithmetic.py
+++ b/pandas/tests/series/test_arithmetic.py
@@ -315,7 +315,7 @@ 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')
@@ -323,7 +323,7 @@ def test_ops_series_period(self):
 
         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):