From db80c83401b2dd4b48c9286cebf189388af5b584 Mon Sep 17 00:00:00 2001 From: Brock Date: Mon, 23 Nov 2020 17:54:30 -0800 Subject: [PATCH 1/4] BUG: return RangeIndex from RangeIndex.difference --- pandas/core/indexes/range.py | 4 ++-- pandas/tests/indexes/ranges/test_setops.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pandas/core/indexes/range.py b/pandas/core/indexes/range.py index 8e12f84895361..93641ebb48647 100644 --- a/pandas/core/indexes/range.py +++ b/pandas/core/indexes/range.py @@ -664,9 +664,9 @@ def difference(self, other, sort=None): if overlap[0] == first.start: # The difference is everything after the intersection new_rng = range(overlap[-1] + first.step, first.stop, first.step) - elif overlap[-1] == first.stop: + elif overlap[-1] == first[-1]: # The difference is everything before the intersection - new_rng = range(first.start, overlap[0] - first.step, first.step) + new_rng = range(first.start, overlap[0], first.step) else: # The difference is not range-like return super().difference(other, sort=sort) diff --git a/pandas/tests/indexes/ranges/test_setops.py b/pandas/tests/indexes/ranges/test_setops.py index 9c9f5dbdf7e7f..3b06e8f493345 100644 --- a/pandas/tests/indexes/ranges/test_setops.py +++ b/pandas/tests/indexes/ranges/test_setops.py @@ -247,16 +247,16 @@ def test_difference(self): result = obj.difference(obj) expected = RangeIndex.from_range(range(0), name="foo") - tm.assert_index_equal(result, expected) + tm.assert_index_equal(result, expected, exact=True) result = obj.difference(expected.rename("bar")) - tm.assert_index_equal(result, obj.rename(None)) + tm.assert_index_equal(result, obj.rename(None), exact=True) result = obj.difference(obj[:3]) - tm.assert_index_equal(result, obj[3:]) + tm.assert_index_equal(result, obj[3:], exact=True) result = obj.difference(obj[-3:]) - tm.assert_index_equal(result, obj[:-3]) + tm.assert_index_equal(result, obj[:-3], exact=True) result = obj.difference(obj[2:6]) expected = Int64Index([1, 2, 7, 8, 9], name="foo") From 2188cd5d9c2900f2ed3eb7ba825cfff88e58aec0 Mon Sep 17 00:00:00 2001 From: Brock Date: Mon, 23 Nov 2020 17:58:37 -0800 Subject: [PATCH 2/4] test coverage --- pandas/tests/indexes/ranges/test_setops.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pandas/tests/indexes/ranges/test_setops.py b/pandas/tests/indexes/ranges/test_setops.py index 3b06e8f493345..592a6c272d1ac 100644 --- a/pandas/tests/indexes/ranges/test_setops.py +++ b/pandas/tests/indexes/ranges/test_setops.py @@ -258,6 +258,12 @@ def test_difference(self): result = obj.difference(obj[-3:]) tm.assert_index_equal(result, obj[:-3], exact=True) + result = obj[::-1].difference(obj[-3:]) + tm.assert_index_equal(result, obj[:-3][::-1], exact=True) + + result = obj[::-1].difference(obj[-3:][::-1]) + tm.assert_index_equal(result, obj[:-3][::-1], exact=True) + result = obj.difference(obj[2:6]) expected = Int64Index([1, 2, 7, 8, 9], name="foo") tm.assert_index_equal(result, expected) From d5d97c6cb3ddf397df18f2cde372b59bef7a1513 Mon Sep 17 00:00:00 2001 From: Brock Date: Mon, 23 Nov 2020 18:58:12 -0800 Subject: [PATCH 3/4] BUG: RangeIndex.difference with mismatched step --- pandas/core/indexes/range.py | 4 ++++ pandas/tests/indexes/ranges/test_setops.py | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/pandas/core/indexes/range.py b/pandas/core/indexes/range.py index 93641ebb48647..8e5725fd72544 100644 --- a/pandas/core/indexes/range.py +++ b/pandas/core/indexes/range.py @@ -660,6 +660,10 @@ def difference(self, other, sort=None): if not isinstance(overlap, RangeIndex): # We wont end up with RangeIndex, so fall back return super().difference(other, sort=sort) + if overlap.step != first.step: + # In some cases we might be able to get a RangeIndex back, + # but not worth the effort. + return super().difference(other, sort=sort) if overlap[0] == first.start: # The difference is everything after the intersection diff --git a/pandas/tests/indexes/ranges/test_setops.py b/pandas/tests/indexes/ranges/test_setops.py index 592a6c272d1ac..1fd41b017221b 100644 --- a/pandas/tests/indexes/ranges/test_setops.py +++ b/pandas/tests/indexes/ranges/test_setops.py @@ -268,6 +268,17 @@ def test_difference(self): expected = Int64Index([1, 2, 7, 8, 9], name="foo") tm.assert_index_equal(result, expected) + def test_difference_mismatched_step(self): + obj = RangeIndex.from_range(range(1, 10), name="foo") + + result = obj.difference(obj[::2]) + expected = obj[1::2]._int64index + tm.assert_index_equal(result, expected, exact=True) + + result = obj.difference(obj[1::2]) + expected = obj[::2]._int64index + tm.assert_index_equal(result, expected, exact=True) + def test_symmetric_difference(self): # GH#12034 Cases where we operate against another RangeIndex and may # get back another RangeIndex From 58e0dbccc970787f26525fab554503b1a2776af7 Mon Sep 17 00:00:00 2001 From: Brock Date: Thu, 26 Nov 2020 10:56:58 -0800 Subject: [PATCH 4/4] whatsnew --- doc/source/whatsnew/v1.2.0.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/whatsnew/v1.2.0.rst b/doc/source/whatsnew/v1.2.0.rst index 08edc7531bcd6..4f1f54af2dd6a 100644 --- a/doc/source/whatsnew/v1.2.0.rst +++ b/doc/source/whatsnew/v1.2.0.rst @@ -772,6 +772,7 @@ Other - Bug in :meth:`Index.union` behaving differently depending on whether operand is an :class:`Index` or other list-like (:issue:`36384`) - Passing an array with 2 or more dimensions to the :class:`Series` constructor now raises the more specific ``ValueError`` rather than a bare ``Exception`` (:issue:`35744`) - Bug in ``dir`` where ``dir(obj)`` wouldn't show attributes defined on the instance for pandas objects (:issue:`37173`) +- Bug in :meth:`RangeIndex.difference` returning :class:`Int64Index` in some cases where it should return :class:`RangeIndex` (:issue:`38028`) .. ---------------------------------------------------------------------------