From 680dd4b34dcd22d061f8731014019867a0b84653 Mon Sep 17 00:00:00 2001 From: Pietro Battiston Date: Tue, 29 Aug 2017 22:22:27 +0200 Subject: [PATCH] BUG: intersection of decreasing RangeIndexes closes #17296 --- doc/source/whatsnew/v0.21.0.txt | 1 + pandas/core/indexes/range.py | 22 +++++++++++++--------- pandas/tests/indexes/test_range.py | 13 +++++++++++++ 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/doc/source/whatsnew/v0.21.0.txt b/doc/source/whatsnew/v0.21.0.txt index 33b7e128ef8bfc..7ec710aae9c49b 100644 --- a/doc/source/whatsnew/v0.21.0.txt +++ b/doc/source/whatsnew/v0.21.0.txt @@ -359,6 +359,7 @@ Indexing - Bug in ``.iloc`` when used with inplace addition or assignment and an int indexer on a ``MultiIndex`` causing the wrong indexes to be read from and written to (:issue:`17148`) - Bug in ``.isin()`` in which checking membership in empty ``Series`` objects raised an error (:issue:`16991`) - Bug in ``CategoricalIndex`` reindexing in which specified indices containing duplicates were not being respected (:issue:`17323`) +- Bug in intersection of ``RangeIndex`` with negative step (:issue:`17296`) I/O ^^^ diff --git a/pandas/core/indexes/range.py b/pandas/core/indexes/range.py index 82412d3a7ef57a..b759abaed4e564 100644 --- a/pandas/core/indexes/range.py +++ b/pandas/core/indexes/range.py @@ -324,12 +324,13 @@ def intersection(self, other): if not len(self) or not len(other): return RangeIndex._simple_new(None) + first = self[::-1] if self._step < 0 else self + second = other[::-1] if other._step < 0 else other + # check whether intervals intersect # deals with in- and decreasing ranges - int_low = max(min(self._start, self._stop + 1), - min(other._start, other._stop + 1)) - int_high = min(max(self._stop, self._start + 1), - max(other._stop, other._start + 1)) + int_low = max(first._start, second._start) + int_high = min(first._stop, second._stop) if int_high <= int_low: return RangeIndex._simple_new(None) @@ -337,21 +338,24 @@ def intersection(self, other): # solve intersection problem # performance hint: for identical step sizes, could use # cheaper alternative - gcd, s, t = self._extended_gcd(self._step, other._step) + gcd, s, t = first._extended_gcd(first._step, second._step) # check whether element sets intersect - if (self._start - other._start) % gcd: + if (first._start - second._start) % gcd: return RangeIndex._simple_new(None) # calculate parameters for the RangeIndex describing the # intersection disregarding the lower bounds - tmp_start = self._start + (other._start - self._start) * \ - self._step // gcd * s - new_step = self._step * other._step // gcd + tmp_start = first._start + (second._start - first._start) * \ + first._step // gcd * s + new_step = first._step * second._step // gcd new_index = RangeIndex(tmp_start, int_high, new_step, fastpath=True) # adjust index to limiting interval new_index._start = new_index._min_fitting_element(int_low) + + if (self._step < 0 and other._step < 0) is not (new_index._step < 0): + new_index = new_index[::-1] return new_index def _min_fitting_element(self, lower_limit): diff --git a/pandas/tests/indexes/test_range.py b/pandas/tests/indexes/test_range.py index 5ecf467b57fc5c..2f93ab6ff5cf22 100644 --- a/pandas/tests/indexes/test_range.py +++ b/pandas/tests/indexes/test_range.py @@ -609,6 +609,19 @@ def test_intersection(self): expected = Index(np.sort(np.intersect1d(self.index.values, other.values))) tm.assert_index_equal(result, expected) + # reversed (GH 17296) + result = other.intersection(self.index) + tm.assert_index_equal(result, expected) + + # GH 17296: intersect two decreasing RangeIndexes + first = RangeIndex(10, -2, -2) + other = RangeIndex(5, -4, -1) + expected = first.astype(int).intersection(other.astype(int)) + result = first.intersection(other).astype(int) + tm.assert_index_equal(result, expected) + # reversed + result = other.intersection(first).astype(int) + tm.assert_index_equal(result, expected) index = RangeIndex(5)