Skip to content

Commit 68ac291

Browse files
jbrockmendeljorisvandenbossche
authored andcommitted
ENH: Implement subtraction for object-dtype Index (pandas-dev#21981)
1 parent 0828c25 commit 68ac291

File tree

3 files changed

+41
-4
lines changed

3 files changed

+41
-4
lines changed

doc/source/whatsnew/v0.24.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,7 @@ Other API Changes
370370
- Invalid construction of ``IntervalDtype`` will now always raise a ``TypeError`` rather than a ``ValueError`` if the subdtype is invalid (:issue:`21185`)
371371
- Trying to reindex a ``DataFrame`` with a non unique ``MultiIndex`` now raises a ``ValueError`` instead of an ``Exception`` (:issue:`21770`)
372372
- :meth:`PeriodIndex.tz_convert` and :meth:`PeriodIndex.tz_localize` have been removed (:issue:`21781`)
373+
- :class:`Index` subtraction will attempt to operate element-wise instead of raising ``TypeError`` (:issue:`19369`)
373374

374375
.. _whatsnew_0240.deprecations:
375376

pandas/core/indexes/base.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -2650,8 +2650,10 @@ def __iadd__(self, other):
26502650
return self + other
26512651

26522652
def __sub__(self, other):
2653-
raise TypeError("cannot perform __sub__ with this index type: "
2654-
"{typ}".format(typ=type(self).__name__))
2653+
return Index(np.array(self) - other)
2654+
2655+
def __rsub__(self, other):
2656+
return Index(other - np.array(self))
26552657

26562658
def __and__(self, other):
26572659
return self.intersection(other)

pandas/tests/indexes/test_base.py

+36-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import pytest
44

55
from datetime import datetime, timedelta
6-
6+
from decimal import Decimal
77
from collections import defaultdict
88

99
import pandas.util.testing as tm
@@ -864,13 +864,47 @@ def test_add(self):
864864
expected = Index(['1a', '1b', '1c'])
865865
tm.assert_index_equal('1' + index, expected)
866866

867-
def test_sub(self):
867+
def test_sub_fail(self):
868868
index = self.strIndex
869869
pytest.raises(TypeError, lambda: index - 'a')
870870
pytest.raises(TypeError, lambda: index - index)
871871
pytest.raises(TypeError, lambda: index - index.tolist())
872872
pytest.raises(TypeError, lambda: index.tolist() - index)
873873

874+
def test_sub_object(self):
875+
# GH#19369
876+
index = pd.Index([Decimal(1), Decimal(2)])
877+
expected = pd.Index([Decimal(0), Decimal(1)])
878+
879+
result = index - Decimal(1)
880+
tm.assert_index_equal(result, expected)
881+
882+
result = index - pd.Index([Decimal(1), Decimal(1)])
883+
tm.assert_index_equal(result, expected)
884+
885+
with pytest.raises(TypeError):
886+
index - 'foo'
887+
888+
with pytest.raises(TypeError):
889+
index - np.array([2, 'foo'])
890+
891+
def test_rsub_object(self):
892+
# GH#19369
893+
index = pd.Index([Decimal(1), Decimal(2)])
894+
expected = pd.Index([Decimal(1), Decimal(0)])
895+
896+
result = Decimal(2) - index
897+
tm.assert_index_equal(result, expected)
898+
899+
result = np.array([Decimal(2), Decimal(2)]) - index
900+
tm.assert_index_equal(result, expected)
901+
902+
with pytest.raises(TypeError):
903+
'foo' - index
904+
905+
with pytest.raises(TypeError):
906+
np.array([True, pd.Timestamp.now()]) - index
907+
874908
def test_map_identity_mapping(self):
875909
# GH 12766
876910
# TODO: replace with fixture

0 commit comments

Comments
 (0)