Skip to content

Commit

Permalink
BUG: intersection of decreasing RangeIndexes
Browse files Browse the repository at this point in the history
closes #17296
  • Loading branch information
toobaz committed Aug 30, 2017
1 parent 0d676a3 commit 680dd4b
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 9 deletions.
1 change: 1 addition & 0 deletions doc/source/whatsnew/v0.21.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
^^^
Expand Down
22 changes: 13 additions & 9 deletions pandas/core/indexes/range.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,34 +324,38 @@ 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)

# Method hint: linear Diophantine equation
# 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):
Expand Down
13 changes: 13 additions & 0 deletions pandas/tests/indexes/test_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down

0 comments on commit 680dd4b

Please sign in to comment.