Skip to content

Commit fd46d82

Browse files
committed
BUG: intersection of decreasing RangeIndexes
closes pandas-dev#17296
1 parent 0d676a3 commit fd46d82

File tree

3 files changed

+35
-9
lines changed

3 files changed

+35
-9
lines changed

doc/source/whatsnew/v0.21.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,7 @@ Indexing
359359
- 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`)
360360
- Bug in ``.isin()`` in which checking membership in empty ``Series`` objects raised an error (:issue:`16991`)
361361
- Bug in ``CategoricalIndex`` reindexing in which specified indices containing duplicates were not being respected (:issue:`17323`)
362+
- Bug in intersection of ``RangeIndex``es with negative step (:issue:`17296`)
362363

363364
I/O
364365
^^^

pandas/core/indexes/range.py

+21-9
Original file line numberDiff line numberDiff line change
@@ -324,34 +324,38 @@ def intersection(self, other):
324324
if not len(self) or not len(other):
325325
return RangeIndex._simple_new(None)
326326

327+
first = self._reverse() if self._step < 0 else self
328+
second = other._reverse() if other._step < 0 else other
329+
327330
# check whether intervals intersect
328331
# deals with in- and decreasing ranges
329-
int_low = max(min(self._start, self._stop + 1),
330-
min(other._start, other._stop + 1))
331-
int_high = min(max(self._stop, self._start + 1),
332-
max(other._stop, other._start + 1))
332+
int_low = max(first._start, second._start)
333+
int_high = min(first._stop, second._stop)
333334
if int_high <= int_low:
334335
return RangeIndex._simple_new(None)
335336

336337
# Method hint: linear Diophantine equation
337338
# solve intersection problem
338339
# performance hint: for identical step sizes, could use
339340
# cheaper alternative
340-
gcd, s, t = self._extended_gcd(self._step, other._step)
341+
gcd, s, t = first._extended_gcd(first._step, second._step)
341342

342343
# check whether element sets intersect
343-
if (self._start - other._start) % gcd:
344+
if (first._start - second._start) % gcd:
344345
return RangeIndex._simple_new(None)
345346

346347
# calculate parameters for the RangeIndex describing the
347348
# intersection disregarding the lower bounds
348-
tmp_start = self._start + (other._start - self._start) * \
349-
self._step // gcd * s
350-
new_step = self._step * other._step // gcd
349+
tmp_start = first._start + (second._start - first._start) * \
350+
first._step // gcd * s
351+
new_step = first._step * second._step // gcd
351352
new_index = RangeIndex(tmp_start, int_high, new_step, fastpath=True)
352353

353354
# adjust index to limiting interval
354355
new_index._start = new_index._min_fitting_element(int_low)
356+
357+
if (self._step < 0 and other._step < 0) is not (new_index._step < 0):
358+
new_index = new_index._reverse()
355359
return new_index
356360

357361
def _min_fitting_element(self, lower_limit):
@@ -544,6 +548,14 @@ def __floordiv__(self, other):
544548
fastpath=True)
545549
return self._int64index // other
546550

551+
def _reverse(self):
552+
"""
553+
Return RangeIndex containing the same values but with opposite step.
554+
"""
555+
params = ([self.min(), self.max() + 1] if self._step < 0
556+
else [self.max(), self.min() - 1]) + [-self._step]
557+
return RangeIndex(*params)
558+
547559
@classmethod
548560
def _add_numeric_methods_binary(cls):
549561
""" add in numeric methods, specialized to RangeIndex """

pandas/tests/indexes/test_range.py

+13
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,19 @@ def test_intersection(self):
609609
expected = Index(np.sort(np.intersect1d(self.index.values,
610610
other.values)))
611611
tm.assert_index_equal(result, expected)
612+
# reversed
613+
result = other.intersection(self.index)
614+
tm.assert_index_equal(result, expected)
615+
616+
# intersect two decreasing RangeIndexes
617+
first = RangeIndex(10, -2, -2)
618+
other = RangeIndex(5, -4, -1)
619+
expected = first.astype(int).intersection(other.astype(int))
620+
result = first.intersection(other).astype(int)
621+
tm.assert_index_equal(result, expected)
622+
# reversed
623+
result = other.intersection(first).astype(int)
624+
tm.assert_index_equal(result, expected)
612625

613626
index = RangeIndex(5)
614627

0 commit comments

Comments
 (0)