From 923f96d306254d65ffb82ca2e78626e32c8db5d5 Mon Sep 17 00:00:00 2001 From: Florian Roessler Date: Sun, 3 Feb 2019 15:57:00 +0000 Subject: [PATCH 01/10] Moving intersection method from DatetimeIndex and Timedeltaindex to datetimelike. Point PeriodIndex intersection to Index.intersection --- pandas/core/indexes/datetimelike.py | 78 +++++++++++++++++++++++++++++ pandas/core/indexes/datetimes.py | 73 --------------------------- pandas/core/indexes/period.py | 3 ++ pandas/core/indexes/timedeltas.py | 46 ----------------- 4 files changed, 81 insertions(+), 119 deletions(-) diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index aa7332472fc07..426820ad6d130 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -25,6 +25,7 @@ import pandas.core.indexes.base as ibase from pandas.core.indexes.base import Index, _index_shared_docs from pandas.core.tools.timedeltas import to_timedelta +from pandas.tseries.frequencies import to_offset import pandas.io.formats.printing as printing @@ -530,6 +531,83 @@ def isin(self, values): return algorithms.isin(self.asi8, values.asi8) + def intersection(self, other, sort=False): + """ + Specialized intersection for DatetimeIndex and TimedeltaIndex objects. + May be much faster than Index.intersection + + Parameters + ---------- + other : DatetimeIndex or TimedeltaIndex or array-like + sort : False or None, default False + Sort the resulting index if possible. + + .. versionadded:: 0.24.0 + + .. versionchanged:: 0.24.1 + + Changed the default to ``False`` to match the behaviour + from before 0.24.0. + + Returns + ------- + y : Index or DatetimeIndex or TimedeltaIndex + """ + self._validate_sort_keyword(sort) + self._assert_can_do_setop(other) + + if self.equals(other): + return self._get_reconciled_name_object(other) + + if not isinstance(other, type(self)): + try: + other = self(other) + except (TypeError, ValueError): + pass + result = Index.intersection(self, other, sort=sort) + if isinstance(result, type(self)): + if result.freq is None: + result.freq = to_offset(result.inferred_freq) + return result + + elif (other.freq is None or self.freq is None or + other.freq != self.freq or + not other.freq.isAnchored() or + (not self.is_monotonic or not other.is_monotonic)): + result = Index.intersection(self, other, sort=sort) + # Invalidate the freq of `result`, which may not be correct at + # this point, depending on the values. + result.freq = None + if hasattr(self, 'tz'): + result = self._shallow_copy(result._values, name=result.name, + tz=result.tz, freq=None) + else: + result = self._shallow_copy(result._values, name=result.name, + freq=None) + if result.freq is None: + result.freq = to_offset(result.inferred_freq) + return result + + if len(self) == 0: + 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 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) + @Appender(_index_shared_docs['repeat'] % _index_doc_kwargs) def repeat(self, repeats, axis=None): nv.validate_repeat(tuple(), dict(axis=axis)) diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index 9c46860eb49d6..d73bb4260ee70 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -594,79 +594,6 @@ def _wrap_setop_result(self, other, result): name = get_op_result_name(self, other) return self._shallow_copy(result, name=name, freq=None, tz=self.tz) - def intersection(self, other, sort=False): - """ - Specialized intersection for DatetimeIndex objects. May be much faster - than Index.intersection - - Parameters - ---------- - other : DatetimeIndex or array-like - sort : False or None, default False - Sort the resulting index if possible. - - .. versionadded:: 0.24.0 - - .. versionchanged:: 0.24.1 - - Changed the default to ``False`` to match the behaviour - from before 0.24.0. - - Returns - ------- - y : Index or DatetimeIndex - """ - self._validate_sort_keyword(sort) - self._assert_can_do_setop(other) - - if self.equals(other): - return self._get_reconciled_name_object(other) - - if not isinstance(other, DatetimeIndex): - try: - other = DatetimeIndex(other) - except (TypeError, ValueError): - pass - result = Index.intersection(self, other, sort=sort) - if isinstance(result, DatetimeIndex): - if result.freq is None: - result.freq = to_offset(result.inferred_freq) - return result - - elif (other.freq is None or self.freq is None or - other.freq != self.freq or - not other.freq.isAnchored() or - (not self.is_monotonic or not other.is_monotonic)): - result = Index.intersection(self, other, sort=sort) - # Invalidate the freq of `result`, which may not be correct at - # this point, depending on the values. - result.freq = None - result = self._shallow_copy(result._values, name=result.name, - tz=result.tz, freq=None) - if result.freq is None: - result.freq = to_offset(result.inferred_freq) - return result - - if len(self) == 0: - 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 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) - # -------------------------------------------------------------------- def _get_time_micros(self): diff --git a/pandas/core/indexes/period.py b/pandas/core/indexes/period.py index a4bd7f9017eb4..b3d7e07857838 100644 --- a/pandas/core/indexes/period.py +++ b/pandas/core/indexes/period.py @@ -802,6 +802,9 @@ def join(self, other, how='left', level=None, return_indexers=False, return self._apply_meta(result), lidx, ridx return self._apply_meta(result) + def intersection(self, other, sort=False): + return Index.intersection(self, other, sort=sort) + def _assert_can_do_setop(self, other): super(PeriodIndex, self)._assert_can_do_setop(other) diff --git a/pandas/core/indexes/timedeltas.py b/pandas/core/indexes/timedeltas.py index cbe5ae198838f..de63fdda895c7 100644 --- a/pandas/core/indexes/timedeltas.py +++ b/pandas/core/indexes/timedeltas.py @@ -439,52 +439,6 @@ def _fast_union(self, other): else: return left - def intersection(self, other): - """ - Specialized intersection for TimedeltaIndex objects. May be much faster - than Index.intersection - - Parameters - ---------- - other : TimedeltaIndex or array-like - - Returns - ------- - y : Index or TimedeltaIndex - """ - self._assert_can_do_setop(other) - - if self.equals(other): - return self._get_reconciled_name_object(other) - - if not isinstance(other, TimedeltaIndex): - try: - other = TimedeltaIndex(other) - except (TypeError, ValueError): - pass - result = Index.intersection(self, other) - return result - - if len(self) == 0: - 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 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) - def _maybe_promote(self, other): if other.inferred_type == 'timedelta': other = TimedeltaIndex(other) From 7088684307f9c6e16887edab08dc755602c68477 Mon Sep 17 00:00:00 2001 From: Florian Roessler Date: Sun, 3 Feb 2019 16:48:17 +0000 Subject: [PATCH 02/10] add tests for timedeltaindex --- .../tests/indexes/timedeltas/test_setops.py | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/pandas/tests/indexes/timedeltas/test_setops.py b/pandas/tests/indexes/timedeltas/test_setops.py index f7c3f764df0a0..2a5a77f0dc578 100644 --- a/pandas/tests/indexes/timedeltas/test_setops.py +++ b/pandas/tests/indexes/timedeltas/test_setops.py @@ -3,6 +3,7 @@ import pandas as pd from pandas import Int64Index, TimedeltaIndex, timedelta_range import pandas.util.testing as tm +import pytest class TestTimedeltaIndex(object): @@ -73,3 +74,80 @@ 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("sort", [None, False]) + def test_intersection_equal(self, sort): + # for equal indicies intersection should return the original index + first = timedelta_range('1 day', periods=4, freq='h') + second = timedelta_range('1 day', periods=4, freq='h') + intersect = first.intersection(second, sort=sort) + if sort is None: + tm.assert_index_equal(intersect, second.sort_values()) + assert tm.equalContents(intersect, second) + + # Corner cases + inter = first.intersection(first, sort=sort) + assert inter is first + + @pytest.mark.parametrize("sort", [None, False]) + def test_intersection_zero_length(self, sort): + index_1 = timedelta_range('1 day', periods=4, freq='h') + index_2 = timedelta_range('1 day', periods=0, freq='h') + inter = index_1.intersection(index_2, sort=sort) + tm.assert_index_equal(index_2, inter) + inter_2 = index_2.intersection(index_1, sort=sort) + tm.assert_index_equal(index_2, inter_2) + + @pytest.mark.parametrize("sort", [None, False]) + def test_intersection(self, sort): + # GH 4690 (with tz) + base = timedelta_range('1 day', periods=4, freq='h', name='idx') + + # if target has the same name, it is preserved + rng2 = timedelta_range('1 day', periods=5, freq='h', name='idx') + expected2 = timedelta_range('1 day', periods=4, freq='h', name='idx') + + # if target name is different, it will be reset + rng3 = timedelta_range('1 day', periods=5, freq='h', name='other') + expected3 = timedelta_range('1 day', periods=4, freq='h', name=None) + + rng4 = timedelta_range('1 day', periods=10, freq='h', name='idx')[5:] + expected4 = TimedeltaIndex([], name='idx') + + for (rng, expected) in [(rng2, expected2), (rng3, expected3), + (rng4, expected4)]: + result = base.intersection(rng) + tm.assert_index_equal(result, expected) + assert result.name == expected.name + assert result.freq == expected.freq + + @pytest.mark.parametrize("sort", [None, False]) + def intersection_non_monotonic(self, sort): + # non-monotonic + base = TimedeltaIndex(['1 hour', '2 hour', + '4 hour', '3 hour'], + name='idx') + + rng2 = TimedeltaIndex(['5 hour', '2 hour', + '4 hour', '9 hour'], + name='idx') + expected2 = TimedeltaIndex(['2 hour', '4 hour'], + name='idx') + + rng3 = TimedeltaIndex(['2 hour', '5 hour', + '5 hour', '1 hour'], + name='other') + expected3 = TimedeltaIndex(['1 hour', '2 hour'], + name=None) + + rng4 = base[::-1] + expected4 = base + + for (rng, expected) in [(rng2, expected2), (rng3, expected3), + (rng4, expected4)]: + result = base.intersection(rng, sort=sort) + if sort is None: + expected = expected.sort_values() + tm.assert_index_equal(result, expected) + assert result.name == expected.name + assert result.freq is None From b9d869b3d46272e7c5c5df654a985f32eb2c04da Mon Sep 17 00:00:00 2001 From: Florian Roessler Date: Sun, 3 Feb 2019 16:57:04 +0000 Subject: [PATCH 03/10] add whatsnew --- doc/source/whatsnew/v0.25.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst index 09626be713c4f..d409ddfcffa9d 100644 --- a/doc/source/whatsnew/v0.25.0.rst +++ b/doc/source/whatsnew/v0.25.0.rst @@ -20,7 +20,7 @@ Other Enhancements ^^^^^^^^^^^^^^^^^^ - :meth:`Timestamp.replace` now supports the ``fold`` argument to disambiguate DST transition times (:issue:`25017`) -- +- :meth:`DatetimeIndex.intersection` and :meth:`TimedeltaIndex.intersection` have been moved to :meth:`Datetimelike.intersection` and tests were added for :meth:`TimedeltaIndex.intersection` (:issue:`24966`). - .. _whatsnew_0250.api_breaking: From 0540312140a8aa4e2d0e5bbcbe9908b1d300627d Mon Sep 17 00:00:00 2001 From: Florian Roessler Date: Sun, 3 Feb 2019 17:39:48 +0000 Subject: [PATCH 04/10] fixing isort issue --- pandas/core/indexes/datetimelike.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index 426820ad6d130..c51611de89307 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -25,9 +25,9 @@ import pandas.core.indexes.base as ibase from pandas.core.indexes.base import Index, _index_shared_docs from pandas.core.tools.timedeltas import to_timedelta -from pandas.tseries.frequencies import to_offset import pandas.io.formats.printing as printing +from pandas.tseries.frequencies import to_offset _index_doc_kwargs = dict(ibase._index_doc_kwargs) From dd9eb56479341f7730008ee970ad5544b52be479 Mon Sep 17 00:00:00 2001 From: Florian Roessler Date: Sun, 3 Feb 2019 23:24:22 +0000 Subject: [PATCH 05/10] isort issue --- pandas/tests/indexes/timedeltas/test_setops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/indexes/timedeltas/test_setops.py b/pandas/tests/indexes/timedeltas/test_setops.py index 2a5a77f0dc578..fd8e68a2fa92b 100644 --- a/pandas/tests/indexes/timedeltas/test_setops.py +++ b/pandas/tests/indexes/timedeltas/test_setops.py @@ -1,9 +1,9 @@ import numpy as np +import pytest import pandas as pd from pandas import Int64Index, TimedeltaIndex, timedelta_range import pandas.util.testing as tm -import pytest class TestTimedeltaIndex(object): From c532694d400dd696557cac65d00d14f1e5ef4899 Mon Sep 17 00:00:00 2001 From: Florian Roessler Date: Wed, 6 Feb 2019 23:48:54 +0000 Subject: [PATCH 06/10] update whatsnew --- doc/source/whatsnew/v0.25.0.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst index d409ddfcffa9d..b96fa5bf360a2 100644 --- a/doc/source/whatsnew/v0.25.0.rst +++ b/doc/source/whatsnew/v0.25.0.rst @@ -20,7 +20,7 @@ Other Enhancements ^^^^^^^^^^^^^^^^^^ - :meth:`Timestamp.replace` now supports the ``fold`` argument to disambiguate DST transition times (:issue:`25017`) -- :meth:`DatetimeIndex.intersection` and :meth:`TimedeltaIndex.intersection` have been moved to :meth:`Datetimelike.intersection` and tests were added for :meth:`TimedeltaIndex.intersection` (:issue:`24966`). +- :meth:`TimedeltaIndex.intersection` now also supports the ``sort`` keyword. - .. _whatsnew_0250.api_breaking: @@ -88,7 +88,7 @@ Datetimelike Timedelta ^^^^^^^^^ -- +- Bug in :func:`TimedeltaIndex.intersection` where for non-monotonic indicies in some cases an empty Index was returned when in fact an intersection existed. - - From a77b7edd4bb48031af547039e9f05da24388d57d Mon Sep 17 00:00:00 2001 From: Florian Roessler Date: Wed, 6 Feb 2019 23:49:16 +0000 Subject: [PATCH 07/10] update docstrings --- pandas/core/indexes/datetimelike.py | 23 ++--------------------- pandas/core/indexes/datetimes.py | 24 ++++++++++++++++++++++++ pandas/core/indexes/period.py | 1 + pandas/core/indexes/timedeltas.py | 28 ++++++++++++++++++++++++++++ 4 files changed, 55 insertions(+), 21 deletions(-) diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index c51611de89307..82c4c618e4ff0 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -532,27 +532,6 @@ def isin(self, values): return algorithms.isin(self.asi8, values.asi8) def intersection(self, other, sort=False): - """ - Specialized intersection for DatetimeIndex and TimedeltaIndex objects. - May be much faster than Index.intersection - - Parameters - ---------- - other : DatetimeIndex or TimedeltaIndex or array-like - sort : False or None, default False - Sort the resulting index if possible. - - .. versionadded:: 0.24.0 - - .. versionchanged:: 0.24.1 - - Changed the default to ``False`` to match the behaviour - from before 0.24.0. - - Returns - ------- - y : Index or DatetimeIndex or TimedeltaIndex - """ self._validate_sort_keyword(sort) self._assert_can_do_setop(other) @@ -575,6 +554,7 @@ def intersection(self, other, sort=False): not other.freq.isAnchored() or (not self.is_monotonic or not other.is_monotonic)): result = Index.intersection(self, other, sort=sort) + # Invalidate the freq of `result`, which may not be correct at # this point, depending on the values. result.freq = None @@ -592,6 +572,7 @@ def intersection(self, other, sort=False): 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 diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index d73bb4260ee70..1469cb3518296 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -590,6 +590,30 @@ def _fast_union(self, other): else: return left + def intersection(self, other, sort=False): + """ + Specialized intersection for DatetimeIndex objects. + May be much faster than Index.intersection + + Parameters + ---------- + other : DatetimeIndex or array-like + sort : False or None, default False + Sort the resulting index if possible. + + .. versionadded:: 0.24.0 + + .. versionchanged:: 0.24.1 + + Changed the default to ``False`` to match the behaviour + from before 0.24.0. + + Returns + ------- + y : Index or DatetimeIndex or TimedeltaIndex + """ + return super().intersection(other, sort=sort) + def _wrap_setop_result(self, other, result): name = get_op_result_name(self, other) return self._shallow_copy(result, name=name, freq=None, tz=self.tz) diff --git a/pandas/core/indexes/period.py b/pandas/core/indexes/period.py index b3d7e07857838..0ddf7ec106698 100644 --- a/pandas/core/indexes/period.py +++ b/pandas/core/indexes/period.py @@ -802,6 +802,7 @@ def join(self, other, how='left', level=None, return_indexers=False, return self._apply_meta(result), lidx, ridx return self._apply_meta(result) + @Appender(Index.intersection.__doc__) def intersection(self, other, sort=False): return Index.intersection(self, other, sort=sort) diff --git a/pandas/core/indexes/timedeltas.py b/pandas/core/indexes/timedeltas.py index de63fdda895c7..b0197b9464c98 100644 --- a/pandas/core/indexes/timedeltas.py +++ b/pandas/core/indexes/timedeltas.py @@ -378,6 +378,34 @@ def join(self, other, how='left', level=None, return_indexers=False, return_indexers=return_indexers, sort=sort) + def intersection(self, other, sort=False): + """ + Specialized intersection for TimedeltaIndex objects. + May be much faster than Index.intersection + + Parameters + ---------- + other : TimedeltaIndex or array-like + sort : False or None, default False + Sort the resulting index if possible. + + .. versionadded:: 0.24.0 + + .. versionchanged:: 0.24.1 + + Changed the default to ``False`` to match the behaviour + from before 0.24.0. + + .. versionchanged:: 0.25.0 + + The `sort` keyword has been added to TimedeltaIndex as well. + + Returns + ------- + y : Index or DatetimeIndex or TimedeltaIndex + """ + return super().intersection(other, sort=sort) + def _wrap_joined_index(self, joined, other): name = get_op_result_name(self, other) if (isinstance(other, TimedeltaIndex) and self.freq == other.freq and From 651422faf72552d73fcce0110ddebd0e30b50970 Mon Sep 17 00:00:00 2001 From: Florian Roessler Date: Wed, 6 Feb 2019 23:49:39 +0000 Subject: [PATCH 08/10] parametrize tests --- .../tests/indexes/timedeltas/test_setops.py | 115 ++++++++++-------- 1 file changed, 63 insertions(+), 52 deletions(-) diff --git a/pandas/tests/indexes/timedeltas/test_setops.py b/pandas/tests/indexes/timedeltas/test_setops.py index fd8e68a2fa92b..897d12d2b33b4 100644 --- a/pandas/tests/indexes/timedeltas/test_setops.py +++ b/pandas/tests/indexes/timedeltas/test_setops.py @@ -4,6 +4,7 @@ import pandas as pd from pandas import Int64Index, TimedeltaIndex, timedelta_range import pandas.util.testing as tm +from pandas.tseries.offsets import Hour class TestTimedeltaIndex(object): @@ -89,65 +90,75 @@ def test_intersection_equal(self, sort): inter = first.intersection(first, sort=sort) assert inter is first + @pytest.mark.parametrize("period_1, period_2", [(0, 4), (4, 0)]) @pytest.mark.parametrize("sort", [None, False]) - def test_intersection_zero_length(self, sort): - index_1 = timedelta_range('1 day', periods=4, freq='h') - index_2 = timedelta_range('1 day', periods=0, freq='h') + def test_intersection_zero_length(self, period_1, period_2, sort): + index_1 = timedelta_range('1 day', periods=period_1, freq='h') + index_2 = timedelta_range('1 day', periods=period_2, freq='h') inter = index_1.intersection(index_2, sort=sort) - tm.assert_index_equal(index_2, inter) - inter_2 = index_2.intersection(index_1, sort=sort) - tm.assert_index_equal(index_2, inter_2) - + tm.assert_index_equal(timedelta_range('1 day', periods=0, freq='h'), + inter) + + @pytest.mark.parametrize("rng, expected", + # if target has the same name, it is preserved + [(timedelta_range('1 day', periods=5, + freq='h', name='idx'), + timedelta_range('1 day', periods=4, + freq='h', name='idx')), + # if target name is different, it will be reset + (timedelta_range('1 day', periods=5, + freq='h', name='other'), + timedelta_range('1 day', periods=4, + freq='h', name=None)), + # if no overlap exists return empty index + (timedelta_range('1 day', periods=10, + freq='h', name='idx')[5:], + TimedeltaIndex([], name='idx')) + ]) @pytest.mark.parametrize("sort", [None, False]) - def test_intersection(self, sort): + def test_intersection(self, rng, expected, sort): # GH 4690 (with tz) base = timedelta_range('1 day', periods=4, freq='h', name='idx') - - # if target has the same name, it is preserved - rng2 = timedelta_range('1 day', periods=5, freq='h', name='idx') - expected2 = timedelta_range('1 day', periods=4, freq='h', name='idx') - - # if target name is different, it will be reset - rng3 = timedelta_range('1 day', periods=5, freq='h', name='other') - expected3 = timedelta_range('1 day', periods=4, freq='h', name=None) - - rng4 = timedelta_range('1 day', periods=10, freq='h', name='idx')[5:] - expected4 = TimedeltaIndex([], name='idx') - - for (rng, expected) in [(rng2, expected2), (rng3, expected3), - (rng4, expected4)]: - result = base.intersection(rng) - tm.assert_index_equal(result, expected) - assert result.name == expected.name - assert result.freq == expected.freq - + result = base.intersection(rng, sort=sort) + if sort is None: + expected = expected.sort_values() + tm.assert_index_equal(result, expected) + assert result.name == expected.name + assert result.freq == expected.freq + + @pytest.mark.parametrize("rng, expected", + # part intersection works + [(TimedeltaIndex(['5 hour', '2 hour', + '4 hour', '9 hour'], + name='idx'), + TimedeltaIndex(['2 hour', '4 hour'], + name='idx')), + # reordered part intersection + (TimedeltaIndex(['2 hour', '5 hour', + '5 hour', '1 hour'], + name='other'), + TimedeltaIndex(['1 hour', '2 hour'], + name=None)), + # reveresed index + (TimedeltaIndex(['1 hour', '2 hour', + '4 hour', '3 hour'], + name='idx')[::-1], + TimedeltaIndex(['1 hour', '2 hour', + '4 hour', '3 hour'], + name='idx'))]) @pytest.mark.parametrize("sort", [None, False]) - def intersection_non_monotonic(self, sort): + def test_intersection_non_monotonic(self, rng, expected, sort): # non-monotonic - base = TimedeltaIndex(['1 hour', '2 hour', - '4 hour', '3 hour'], + base = TimedeltaIndex(['1 hour', '2 hour', '4 hour', '3 hour'], name='idx') + result = base.intersection(rng, sort=sort) + if sort is None: + expected = expected.sort_values() + tm.assert_index_equal(result, expected) + assert result.name == expected.name - rng2 = TimedeltaIndex(['5 hour', '2 hour', - '4 hour', '9 hour'], - name='idx') - expected2 = TimedeltaIndex(['2 hour', '4 hour'], - name='idx') - - rng3 = TimedeltaIndex(['2 hour', '5 hour', - '5 hour', '1 hour'], - name='other') - expected3 = TimedeltaIndex(['1 hour', '2 hour'], - name=None) - - rng4 = base[::-1] - expected4 = base - - for (rng, expected) in [(rng2, expected2), (rng3, expected3), - (rng4, expected4)]: - result = base.intersection(rng, sort=sort) - if sort is None: - expected = expected.sort_values() - tm.assert_index_equal(result, expected) - assert result.name == expected.name + # if reveresed order, frequency is still the same + if all(base == rng[::-1]) and sort is None: + assert isinstance(result.freq, Hour) + else: assert result.freq is None From b51e578006b1b918aa49512f415d6e0beda8838e Mon Sep 17 00:00:00 2001 From: Florian Roessler Date: Thu, 7 Feb 2019 08:55:42 +0000 Subject: [PATCH 09/10] add compatibility of super with python2 --- pandas/core/indexes/datetimes.py | 2 +- pandas/core/indexes/timedeltas.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index 1469cb3518296..6bfe657c1b74a 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -612,7 +612,7 @@ def intersection(self, other, sort=False): ------- y : Index or DatetimeIndex or TimedeltaIndex """ - return super().intersection(other, sort=sort) + return super(DatetimeIndex, self).intersection(other, sort=sort) def _wrap_setop_result(self, other, result): name = get_op_result_name(self, other) diff --git a/pandas/core/indexes/timedeltas.py b/pandas/core/indexes/timedeltas.py index b0197b9464c98..365a0a4a48f3d 100644 --- a/pandas/core/indexes/timedeltas.py +++ b/pandas/core/indexes/timedeltas.py @@ -404,7 +404,7 @@ def intersection(self, other, sort=False): ------- y : Index or DatetimeIndex or TimedeltaIndex """ - return super().intersection(other, sort=sort) + return super(TimedeltaIndex, self).intersection(other, sort=sort) def _wrap_joined_index(self, joined, other): name = get_op_result_name(self, other) From 6c8a57d280b1d418a12e8f19992339522b749f8c Mon Sep 17 00:00:00 2001 From: Florian Roessler Date: Fri, 8 Feb 2019 09:22:00 +0000 Subject: [PATCH 10/10] isort test_setops --- pandas/tests/indexes/timedeltas/test_setops.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pandas/tests/indexes/timedeltas/test_setops.py b/pandas/tests/indexes/timedeltas/test_setops.py index 897d12d2b33b4..28fb3479a2347 100644 --- a/pandas/tests/indexes/timedeltas/test_setops.py +++ b/pandas/tests/indexes/timedeltas/test_setops.py @@ -4,6 +4,7 @@ import pandas as pd from pandas import Int64Index, TimedeltaIndex, timedelta_range import pandas.util.testing as tm + from pandas.tseries.offsets import Hour