diff --git a/doc/source/whatsnew/v0.21.0.txt b/doc/source/whatsnew/v0.21.0.txt index b24a6f067cee4..b73d106f09dbc 100644 --- a/doc/source/whatsnew/v0.21.0.txt +++ b/doc/source/whatsnew/v0.21.0.txt @@ -416,6 +416,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 82412d3a7ef57..b759abaed4e56 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 5ecf467b57fc5..06c8f0ee392c7 100644 --- a/pandas/tests/indexes/test_range.py +++ b/pandas/tests/indexes/test_range.py @@ -610,6 +610,21 @@ def test_intersection(self): 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) # intersect of non-overlapping indices