From a73ec0e5ed64eb90ae3e5cfb2b2fd3a79236609b Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Thu, 19 Jul 2018 12:08:53 -0700 Subject: [PATCH 1/4] implement subtraction for object-dtype Index --- doc/source/whatsnew/v0.24.0.txt | 1 + pandas/core/indexes/base.py | 6 ++++-- pandas/tests/indexes/test_base.py | 12 ++++++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index b015495b095b6..5801338d64890 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 always ``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..db48ea366050b 100644 --- a/pandas/tests/indexes/test_base.py +++ b/pandas/tests/indexes/test_base.py @@ -871,6 +871,18 @@ def test_sub(self): pytest.raises(TypeError, lambda: index - index.tolist()) pytest.raises(TypeError, lambda: index.tolist() - index) + def test_sub_object(self): + # GH#19369 + from decimal import Decimal + 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) + def test_map_identity_mapping(self): # GH 12766 # TODO: replace with fixture From 731059823549f828e7eba87b39d45c44da31c238 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Thu, 19 Jul 2018 12:13:18 -0700 Subject: [PATCH 2/4] fixup typo --- doc/source/whatsnew/v0.24.0.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index 5801338d64890..4439529faf208 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -310,7 +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 always ``TypeError`` (:issue:`19369`) +- :class:`Index` subtraction will attempt to operate element-wise instead of raising ``TypeError`` (:issue:`19369`) .. _whatsnew_0240.deprecations: From 966a12f8d9d2bbc2bdcfd58b815edcac67087c7f Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Fri, 20 Jul 2018 13:53:16 -0700 Subject: [PATCH 3/4] explicit tests for rsub, fail case --- pandas/tests/indexes/test_base.py | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/pandas/tests/indexes/test_base.py b/pandas/tests/indexes/test_base.py index db48ea366050b..c91f35fc74ed5 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,7 +864,7 @@ 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) @@ -873,7 +873,6 @@ def test_sub(self): def test_sub_object(self): # GH#19369 - from decimal import Decimal index = pd.Index([Decimal(1), Decimal(2)]) expected = pd.Index([Decimal(0), Decimal(1)]) @@ -883,6 +882,29 @@ def test_sub_object(self): 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([2, pd.Timestamp.now()]) - index + def test_map_identity_mapping(self): # GH 12766 # TODO: replace with fixture From bd6e88c504098492b585fb8d6472442a2e9cf1f1 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Fri, 20 Jul 2018 14:00:15 -0700 Subject: [PATCH 4/4] implement explicit rsub test --- pandas/tests/indexes/test_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/indexes/test_base.py b/pandas/tests/indexes/test_base.py index c91f35fc74ed5..754703dfc4bee 100644 --- a/pandas/tests/indexes/test_base.py +++ b/pandas/tests/indexes/test_base.py @@ -903,7 +903,7 @@ def test_rsub_object(self): 'foo' - index with pytest.raises(TypeError): - np.array([2, pd.Timestamp.now()]) - index + np.array([True, pd.Timestamp.now()]) - index def test_map_identity_mapping(self): # GH 12766