Repository: pandas-dev/pandas

Issue:
pandas 0.23 broke unary negative expression on Decimal data type
#### Code Sample, a copy-pastable example if possible

```python
import pandas as pd
from decimal import Decimal as D
series = pd.Series([D(1)])
print(series)
print(-(series))
```
#### Problem description

I'm dealing with decimal data where exact representation is required, thus I use Python's Decimal type with pandas. With the update from 0.22 to 0.23, the unary negative expression broke.

#### Expected Output (from 0.22)
```
>>> import pandas as pd
>>> from decimal import Decimal as D
>>> series = pd.Series([D(1)])
>>> print(series)
0    1
dtype: object
>>> print(-(series))
0    -1
dtype: object
```

#### Actual Output (from 0.23)
```
>>> import pandas as pd
>>> from decimal import Decimal as D
>>> series = pd.Series([D(1)])
>>> print(series)
0    1
dtype: object
>>> print(-(series))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "python3.6/site-packages/pandas/core/generic.py", line 1124, in __neg__
    .format(values.dtype))
TypeError: Unary negative expects numeric dtype, not object
```
#### Workaround (in 0.23)
Broadcasting against 0 has the expected effect:
```
>>> 0-series
0    -1
dtype: object
>>> (0-series).iloc[0]
Decimal('-1')
```


#### Output of ``pd.show_versions()``

<details>
>>> pd.show_versions()

INSTALLED VERSIONS
------------------
commit: None
python: 3.6.5.final.0
python-bits: 64
OS: Linux
OS-release: 4.16.13-300.fc28.x86_64
machine: x86_64
processor: x86_64
byteorder: little
LC_ALL: None
LANG: en_US.UTF-8
LOCALE: en_US.UTF-8

pandas: 0.23.0
pytest: None
pip: 9.0.3
setuptools: 38.5.1
Cython: None
numpy: 1.14.4
scipy: 1.0.0
pyarrow: None
xarray: None
IPython: 6.2.1
sphinx: None
patsy: 0.4.1
dateutil: 2.7.3
pytz: 2018.4
blosc: None
bottleneck: None
tables: None
numexpr: None
feather: None
matplotlib: 2.1.0
openpyxl: None
xlrd: None
xlwt: None
xlsxwriter: None
lxml: None
bs4: None
html5lib: 0.999999999
sqlalchemy: 1.1.15
pymysql: None
psycopg2: 2.7.3.2 (dt dec pq3 ext lo64)
jinja2: 2.10
s3fs: None
fastparquet: None
pandas_gbq: None
pandas_datareader: None
</details>



Discussion:
@rbu Thanks for the report. I tagged it as a regression for now, we should further look into the reason for the change.

Resolving patch:
diff --git a/pandas/core/generic.py b/pandas/core/generic.py
--- a/pandas/core/generic.py
+++ b/pandas/core/generic.py
@@ -27,6 +27,7 @@
     is_dict_like,
     is_re_compilable,
     is_period_arraylike,
+    is_object_dtype,
     pandas_dtype)
 from pandas.core.dtypes.cast import maybe_promote, maybe_upcast_putmask
 from pandas.core.dtypes.inference import is_hashable
@@ -1117,7 +1118,8 @@ def __neg__(self):
         values = com._values_from_object(self)
         if is_bool_dtype(values):
             arr = operator.inv(values)
-        elif (is_numeric_dtype(values) or is_timedelta64_dtype(values)):
+        elif (is_numeric_dtype(values) or is_timedelta64_dtype(values)
+                or is_object_dtype(values)):
             arr = operator.neg(values)
         else:
             raise TypeError("Unary negative expects numeric dtype, not {}"
@@ -1128,7 +1130,8 @@ 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)):
+        elif (is_numeric_dtype(values) or is_timedelta64_dtype(values)
+                or is_object_dtype(values)):
             arr = operator.pos(values)
         else:
             raise TypeError("Unary plus expects numeric dtype, not {}"


Test patch:
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
@@ -3,6 +3,7 @@
 from __future__ import print_function
 from collections import deque
 from datetime import datetime
+from decimal import Decimal
 import operator
 
 import pytest
@@ -282,6 +283,17 @@ def test_neg_numeric(self, df, expected):
         assert_frame_equal(-df, expected)
         assert_series_equal(-df['a'], expected['a'])
 
+    @pytest.mark.parametrize('df, expected', [
+        (np.array([1, 2], dtype=object), np.array([-1, -2], dtype=object)),
+        ([Decimal('1.0'), Decimal('2.0')], [Decimal('-1.0'), Decimal('-2.0')]),
+    ])
+    def test_neg_object(self, df, expected):
+        # GH 21380
+        df = pd.DataFrame({'a': df})
+        expected = pd.DataFrame({'a': 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'])}),
@@ -307,6 +319,15 @@ def test_pos_numeric(self, df):
 
     @pytest.mark.parametrize('df', [
         pd.DataFrame({'a': ['a', 'b']}),
+        pd.DataFrame({'a': np.array([-1, 2], dtype=object)}),
+        pd.DataFrame({'a': [Decimal('-1.0'), Decimal('2.0')]}),
+    ])
+    def test_pos_object(self, df):
+        # GH 21380
+        assert_frame_equal(+df, df)
+        assert_series_equal(+df['a'], df['a'])
+
+    @pytest.mark.parametrize('df', [
         pd.DataFrame({'a': pd.to_datetime(['2017-01-22', '1970-01-01'])}),
     ])
     def test_pos_raises(self, df):