diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index b015495b095b6..4439529faf208 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -310,6 +310,7 @@ Other API Changes - Invalid construction of ``IntervalDtype`` will now always raise a ``TypeError`` rather than a ``ValueError`` if the subdtype is invalid (:issue:`21185`) - Trying to reindex a ``DataFrame`` with a non unique ``MultiIndex`` now raises a ``ValueError`` instead of an ``Exception`` (:issue:`21770`) - :meth:`PeriodIndex.tz_convert` and :meth:`PeriodIndex.tz_localize` have been removed (:issue:`21781`) +- :class:`Index` subtraction will attempt to operate element-wise instead of raising ``TypeError`` (:issue:`19369`) .. _whatsnew_0240.deprecations: diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 83b70baf4065b..3a42c7963f21b 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -2630,8 +2630,10 @@ def __iadd__(self, other): return self + other def __sub__(self, other): - raise TypeError("cannot perform __sub__ with this index type: " - "{typ}".format(typ=type(self).__name__)) + return Index(np.array(self) - other) + + def __rsub__(self, other): + return Index(other - np.array(self)) def __and__(self, other): return self.intersection(other) diff --git a/pandas/tests/indexes/test_base.py b/pandas/tests/indexes/test_base.py index 7b105390db40b..754703dfc4bee 100644 --- a/pandas/tests/indexes/test_base.py +++ b/pandas/tests/indexes/test_base.py @@ -3,7 +3,7 @@ import pytest from datetime import datetime, timedelta - +from decimal import Decimal from collections import defaultdict import pandas.util.testing as tm @@ -864,13 +864,47 @@ def test_add(self): expected = Index(['1a', '1b', '1c']) tm.assert_index_equal('1' + index, expected) - def test_sub(self): + def test_sub_fail(self): index = self.strIndex pytest.raises(TypeError, lambda: index - 'a') pytest.raises(TypeError, lambda: index - index) pytest.raises(TypeError, lambda: index - index.tolist()) pytest.raises(TypeError, lambda: index.tolist() - index) + def test_sub_object(self): + # GH#19369 + index = pd.Index([Decimal(1), Decimal(2)]) + expected = pd.Index([Decimal(0), Decimal(1)]) + + result = index - Decimal(1) + tm.assert_index_equal(result, expected) + + result = index - pd.Index([Decimal(1), Decimal(1)]) + tm.assert_index_equal(result, expected) + + with pytest.raises(TypeError): + index - 'foo' + + with pytest.raises(TypeError): + index - np.array([2, 'foo']) + + def test_rsub_object(self): + # GH#19369 + index = pd.Index([Decimal(1), Decimal(2)]) + expected = pd.Index([Decimal(1), Decimal(0)]) + + result = Decimal(2) - index + tm.assert_index_equal(result, expected) + + result = np.array([Decimal(2), Decimal(2)]) - index + tm.assert_index_equal(result, expected) + + with pytest.raises(TypeError): + 'foo' - index + + with pytest.raises(TypeError): + np.array([True, pd.Timestamp.now()]) - index + def test_map_identity_mapping(self): # GH 12766 # TODO: replace with fixture