diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst index fe047b4a141ef..4544daf063197 100644 --- a/doc/source/whatsnew/v0.25.0.rst +++ b/doc/source/whatsnew/v0.25.0.rst @@ -273,7 +273,7 @@ Datetimelike Timedelta ^^^^^^^^^ -- +- Bug when :meth:`TimedeltaIndex.intersection` with another decreasing TimedeltaIndex (:issue:`17391`) - - diff --git a/pandas/core/indexes/timedeltas.py b/pandas/core/indexes/timedeltas.py index c3ed26b5b1cca..ecf0e0a0cc3e3 100644 --- a/pandas/core/indexes/timedeltas.py +++ b/pandas/core/indexes/timedeltas.py @@ -16,6 +16,7 @@ from pandas.core.dtypes.missing import isna from pandas.core.accessor import delegate_names +from pandas.core.algorithms import unique1d from pandas.core.arrays import datetimelike as dtl from pandas.core.arrays.timedeltas import TimedeltaArray, _is_convertible_to_td from pandas.core.base import _shared_docs @@ -474,21 +475,34 @@ def intersection(self, other): return self if len(other) == 0: return other - # to make our life easier, "sort" the two ranges - if self[0] <= other[0]: - left, right = self, other - else: - left, right = other, self - end = min(left[-1], right[-1]) - start = right[0] + if self.is_monotonic: + other = other.sort_values() + # to make our life easier, "sort" the two ranges + if self[0] <= other[0]: + left, right = self, other + else: + left, right = other, self - if end < start: - return type(self)(data=[]) - else: - lslice = slice(*left.slice_locs(start, end)) - left_chunk = left.values[lslice] - return self._shallow_copy(left_chunk) + end = min(left[-1], right[-1]) + start = right[0] + + if end < start: + return type(self)(data=[]) + else: + lstart, lend = left.slice_locs(start, end) + lslice = np.arange(lstart, lend) + mask = [left_elem in right for left_elem in left[lstart: lend]] + lslice = lslice[mask] + left_chunk = left.values[lslice] + return self._shallow_copy(left_chunk) + + indexer = other.get_indexer(self) + indexer = indexer.take((indexer != -1).nonzero()[0]) + indexer = unique1d(indexer) + + taken = other.take(indexer).values + return self._shallow_copy(taken) def _maybe_promote(self, other): if other.inferred_type == 'timedelta': diff --git a/pandas/tests/indexes/timedeltas/test_setops.py b/pandas/tests/indexes/timedeltas/test_setops.py index f7c3f764df0a0..afe0a19f509dc 100644 --- a/pandas/tests/indexes/timedeltas/test_setops.py +++ b/pandas/tests/indexes/timedeltas/test_setops.py @@ -1,4 +1,5 @@ import numpy as np +import pytest import pandas as pd from pandas import Int64Index, TimedeltaIndex, timedelta_range @@ -73,3 +74,37 @@ def test_intersection_bug_1708(self): result = index_1 & index_2 expected = timedelta_range('1 day 01:00:00', periods=3, freq='h') tm.assert_index_equal(result, expected) + + @pytest.mark.parametrize('left, right, expected', [ + (pd.to_timedelta(range(3), unit='s'), + pd.to_timedelta(range(2, -1, -1), unit='s'), + pd.to_timedelta(range(3), unit='s')), + (pd.to_timedelta(range(6, 3, -1), unit='s'), + pd.to_timedelta(range(5, 1, -1), unit='s'), + TimedeltaIndex(['00:00:05', '00:00:04'])), + (pd.to_timedelta(range(5, 1, -1), unit='s'), + pd.to_timedelta(range(6, 3, -1), unit='s'), + TimedeltaIndex(['00:00:05', '00:00:04'])), + (pd.to_timedelta(range(1, 20), unit='s'), + pd.to_timedelta((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20), unit='s'), + pd.to_timedelta(range(1, 11), unit='s')), + (pd.to_timedelta((1, 2, 5, 6, 7), unit='s'), + pd.to_timedelta((3, 5, 6, 7), unit='s'), + pd.to_timedelta((5, 6, 7), unit='s')), + (pd.TimedeltaIndex(['00:00:09', '00:00:09', '00:00:07']), + pd.to_timedelta(range(10), unit='s'), + pd.TimedeltaIndex(['00:00:09', '00:00:07'])) + ]) + def test_intersect(self, left, right, expected): + # GH 17391 + result = left.intersection(right) + tm.assert_index_equal(result, expected) + + @pytest.mark.parametrize('idx', [ + pd.to_timedelta(range(1, 2), unit='s'), + pd.to_timedelta(range(2, -1, -1), unit='s') + ]) + def test_intersect_self(self, idx): + # GH 17391 + result = idx.intersection(idx) + tm.assert_index_equal(idx, result)