Skip to content

Commit 8023029

Browse files
API/DEPR: Remove +/- as setops for Index (GH8227) (#14127)
1 parent e54d4db commit 8023029

File tree

6 files changed

+70
-36
lines changed

6 files changed

+70
-36
lines changed

doc/source/whatsnew/v0.19.0.txt

+37
Original file line numberDiff line numberDiff line change
@@ -926,6 +926,43 @@ New Behavior:
926926
pi = pd.PeriodIndex(['2011-01', '2011-02'], freq='M')
927927
pi.values
928928

929+
930+
.. _whatsnew_0190.api.setops:
931+
932+
Index ``+`` / ``-`` no longer used for set operations
933+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
934+
935+
Addition and subtraction of the base Index type (not the numeric subclasses)
936+
previously performed set operations (set union and difference). This
937+
behaviour was already deprecated since 0.15.0 (in favor using the specific
938+
``.union()`` and ``.difference()`` methods), and is now disabled. When
939+
possible, ``+`` and ``-`` are now used for element-wise operations, for
940+
example for concatenating strings (:issue:`8227`, :issue:`14127`).
941+
942+
Previous Behavior:
943+
944+
.. code-block:: ipython
945+
946+
In [1]: pd.Index(['a', 'b']) + pd.Index(['a', 'c'])
947+
FutureWarning: using '+' to provide set union with Indexes is deprecated, use '|' or .union()
948+
Out[1]: Index(['a', 'b', 'c'], dtype='object')
949+
950+
The same operation will now perform element-wise addition:
951+
952+
.. ipython:: python
953+
954+
pd.Index(['a', 'b']) + pd.Index(['a', 'c'])
955+
956+
Note that numeric Index objects already performed element-wise operations.
957+
For example, the behaviour of adding two integer Indexes:
958+
959+
.. ipython:: python
960+
961+
pd.Index([1, 2, 3]) + pd.Index([2, 3, 4])
962+
963+
is unchanged. The base ``Index`` is now made consistent with this behaviour.
964+
965+
929966
.. _whatsnew_0190.api.difference:
930967

931968
``Index.difference`` and ``.symmetric_difference`` changes

pandas/indexes/base.py

+7-18
Original file line numberDiff line numberDiff line change
@@ -1739,28 +1739,16 @@ def argsort(self, *args, **kwargs):
17391739
return result.argsort(*args, **kwargs)
17401740

17411741
def __add__(self, other):
1742-
if is_list_like(other):
1743-
warnings.warn("using '+' to provide set union with Indexes is "
1744-
"deprecated, use '|' or .union()", FutureWarning,
1745-
stacklevel=2)
1746-
if isinstance(other, Index):
1747-
return self.union(other)
17481742
return Index(np.array(self) + other)
17491743

17501744
def __radd__(self, other):
1751-
if is_list_like(other):
1752-
warnings.warn("using '+' to provide set union with Indexes is "
1753-
"deprecated, use '|' or .union()", FutureWarning,
1754-
stacklevel=2)
17551745
return Index(other + np.array(self))
17561746

17571747
__iadd__ = __add__
17581748

17591749
def __sub__(self, other):
1760-
warnings.warn("using '-' to provide set differences with Indexes is "
1761-
"deprecated, use .difference()", FutureWarning,
1762-
stacklevel=2)
1763-
return self.difference(other)
1750+
raise TypeError("cannot perform __sub__ with this index type: "
1751+
"{typ}".format(typ=type(self)))
17641752

17651753
def __and__(self, other):
17661754
return self.intersection(other)
@@ -1990,7 +1978,8 @@ def symmetric_difference(self, other, result_name=None):
19901978
-----
19911979
``symmetric_difference`` contains elements that appear in either
19921980
``idx1`` or ``idx2`` but not both. Equivalent to the Index created by
1993-
``(idx1 - idx2) + (idx2 - idx1)`` with duplicates dropped.
1981+
``idx1.difference(idx2) | idx2.difference(idx1)`` with duplicates
1982+
dropped.
19941983
19951984
Examples
19961985
--------
@@ -3333,8 +3322,8 @@ def _evaluate_compare(self, other):
33333322
cls.__ge__ = _make_compare(operator.ge)
33343323

33353324
@classmethod
3336-
def _add_numericlike_set_methods_disabled(cls):
3337-
""" add in the numeric set-like methods to disable """
3325+
def _add_numeric_methods_add_sub_disabled(cls):
3326+
""" add in the numeric add/sub methods to disable """
33383327

33393328
def _make_invalid_op(name):
33403329
def invalid_op(self, other=None):
@@ -3349,7 +3338,7 @@ def invalid_op(self, other=None):
33493338

33503339
@classmethod
33513340
def _add_numeric_methods_disabled(cls):
3352-
""" add in numeric methods to disable """
3341+
""" add in numeric methods to disable other than add/sub """
33533342

33543343
def _make_invalid_op(name):
33553344
def invalid_op(self, other=None):

pandas/indexes/category.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -649,7 +649,7 @@ def _add_accessors(cls):
649649
typ='method', overwrite=True)
650650

651651

652-
CategoricalIndex._add_numericlike_set_methods_disabled()
652+
CategoricalIndex._add_numeric_methods_add_sub_disabled()
653653
CategoricalIndex._add_numeric_methods_disabled()
654654
CategoricalIndex._add_logical_methods_disabled()
655655
CategoricalIndex._add_comparison_methods()

pandas/indexes/multi.py

+1
Original file line numberDiff line numberDiff line change
@@ -2219,6 +2219,7 @@ def isin(self, values, level=None):
22192219

22202220

22212221
MultiIndex._add_numeric_methods_disabled()
2222+
MultiIndex._add_numeric_methods_add_sub_disabled()
22222223
MultiIndex._add_logical_methods_disabled()
22232224

22242225

pandas/tests/indexes/test_base.py

+14-10
Original file line numberDiff line numberDiff line change
@@ -730,16 +730,6 @@ def test_union(self):
730730
expected = Index(list('ab'), name='A')
731731
tm.assert_index_equal(union, expected)
732732

733-
def test_add(self):
734-
735-
# - API change GH 8226
736-
with tm.assert_produces_warning():
737-
self.strIndex + self.strIndex
738-
with tm.assert_produces_warning():
739-
self.strIndex + self.strIndex.tolist()
740-
with tm.assert_produces_warning():
741-
self.strIndex.tolist() + self.strIndex
742-
743733
with tm.assert_produces_warning(RuntimeWarning):
744734
firstCat = self.strIndex.union(self.dateIndex)
745735
secondCat = self.strIndex.union(self.strIndex)
@@ -755,13 +745,27 @@ def test_add(self):
755745
tm.assert_contains_all(self.strIndex, secondCat)
756746
tm.assert_contains_all(self.dateIndex, firstCat)
757747

748+
def test_add(self):
749+
idx = self.strIndex
750+
expected = Index(self.strIndex.values * 2)
751+
self.assert_index_equal(idx + idx, expected)
752+
self.assert_index_equal(idx + idx.tolist(), expected)
753+
self.assert_index_equal(idx.tolist() + idx, expected)
754+
758755
# test add and radd
759756
idx = Index(list('abc'))
760757
expected = Index(['a1', 'b1', 'c1'])
761758
self.assert_index_equal(idx + '1', expected)
762759
expected = Index(['1a', '1b', '1c'])
763760
self.assert_index_equal('1' + idx, expected)
764761

762+
def test_sub(self):
763+
idx = self.strIndex
764+
self.assertRaises(TypeError, lambda: idx - 'a')
765+
self.assertRaises(TypeError, lambda: idx - idx)
766+
self.assertRaises(TypeError, lambda: idx - idx.tolist())
767+
self.assertRaises(TypeError, lambda: idx.tolist() - idx)
768+
765769
def test_append_multiple(self):
766770
index = Index(['a', 'b', 'c', 'd', 'e', 'f'])
767771

pandas/tests/indexes/test_multi.py

+10-7
Original file line numberDiff line numberDiff line change
@@ -1408,21 +1408,24 @@ def test_intersection(self):
14081408
# result = self.index & tuples
14091409
# self.assertTrue(result.equals(tuples))
14101410

1411-
def test_difference(self):
1411+
def test_sub(self):
14121412

14131413
first = self.index
1414-
result = first.difference(self.index[-3:])
14151414

1416-
# - API change GH 8226
1417-
with tm.assert_produces_warning():
1415+
# - now raises (previously was set op difference)
1416+
with tm.assertRaises(TypeError):
14181417
first - self.index[-3:]
1419-
with tm.assert_produces_warning():
1418+
with tm.assertRaises(TypeError):
14201419
self.index[-3:] - first
1421-
with tm.assert_produces_warning():
1420+
with tm.assertRaises(TypeError):
14221421
self.index[-3:] - first.tolist()
1422+
with tm.assertRaises(TypeError):
1423+
first.tolist() - self.index[-3:]
14231424

1424-
self.assertRaises(TypeError, lambda: first.tolist() - self.index[-3:])
1425+
def test_difference(self):
14251426

1427+
first = self.index
1428+
result = first.difference(self.index[-3:])
14261429
expected = MultiIndex.from_tuples(sorted(self.index[:-3].values),
14271430
sortorder=0,
14281431
names=self.index.names)

0 commit comments

Comments
 (0)