Skip to content

Commit 29c820f

Browse files
authored
BUG: DTI/TDI intersection result names (#33904)
1 parent 81093ba commit 29c820f

File tree

5 files changed

+35
-16
lines changed

5 files changed

+35
-16
lines changed

doc/source/whatsnew/v1.1.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,7 @@ Datetimelike
557557
- Bug in :meth:`DatetimeIndex.intersection` losing ``freq`` and timezone in some cases (:issue:`33604`)
558558
- Bug in :class:`DatetimeIndex` addition and subtraction with some types of :class:`DateOffset` objects incorrectly retaining an invalid ``freq`` attribute (:issue:`33779`)
559559
- Bug in :class:`DatetimeIndex` where setting the ``freq`` attribute on an index could silently change the ``freq`` attribute on another index viewing the same data (:issue:`33552`)
560+
- Bug in :meth:`DatetimeIndex.intersection` and :meth:`TimedeltaIndex.intersection` with results not having the correct ``name`` attribute (:issue:`33904`)
560561

561562
Timedelta
562563
^^^^^^^^^

pandas/core/indexes/datetimelike.py

+30-12
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import numpy as np
88

9-
from pandas._libs import NaT, iNaT, join as libjoin, lib
9+
from pandas._libs import NaT, Timedelta, iNaT, join as libjoin, lib
1010
from pandas._libs.tslibs import timezones
1111
from pandas._typing import Label
1212
from pandas.compat.numpy import function as nv
@@ -41,7 +41,7 @@
4141
from pandas.core.sorting import ensure_key_mapped
4242
from pandas.core.tools.timedeltas import to_timedelta
4343

44-
from pandas.tseries.frequencies import DateOffset
44+
from pandas.tseries.offsets import DateOffset, Tick
4545

4646
_index_doc_kwargs = dict(ibase._index_doc_kwargs)
4747

@@ -641,6 +641,7 @@ def intersection(self, other, sort=False):
641641
"""
642642
self._validate_sort_keyword(sort)
643643
self._assert_can_do_setop(other)
644+
res_name = get_op_result_name(self, other)
644645

645646
if self.equals(other):
646647
return self._get_reconciled_name_object(other)
@@ -654,12 +655,18 @@ def intersection(self, other, sort=False):
654655
result = Index.intersection(self, other, sort=sort)
655656
if isinstance(result, type(self)):
656657
if result.freq is None:
658+
# TODO: no tests rely on this; needed?
657659
result = result._with_freq("infer")
660+
assert result.name == res_name
658661
return result
659662

660663
elif not self._can_fast_intersect(other):
661664
result = Index.intersection(self, other, sort=sort)
662-
result = result._with_freq("infer")
665+
assert result.name == res_name
666+
# We need to invalidate the freq because Index.intersection
667+
# uses _shallow_copy on a view of self._data, which will preserve
668+
# self.freq if we're not careful.
669+
result = result._with_freq(None)._with_freq("infer")
663670
return result
664671

665672
# to make our life easier, "sort" the two ranges
@@ -674,27 +681,34 @@ def intersection(self, other, sort=False):
674681
start = right[0]
675682

676683
if end < start:
677-
return type(self)(data=[], dtype=self.dtype, freq=self.freq)
684+
return type(self)(data=[], dtype=self.dtype, freq=self.freq, name=res_name)
678685
else:
679686
lslice = slice(*left.slice_locs(start, end))
680687
left_chunk = left._values[lslice]
681-
return self._shallow_copy(left_chunk)
688+
return type(self)._simple_new(left_chunk, name=res_name)
682689

683690
def _can_fast_intersect(self: _T, other: _T) -> bool:
684691
if self.freq is None:
685692
return False
686693

687-
if other.freq != self.freq:
694+
elif other.freq != self.freq:
688695
return False
689696

690-
if not self.is_monotonic_increasing:
697+
elif not self.is_monotonic_increasing:
691698
# Because freq is not None, we must then be monotonic decreasing
692699
return False
693700

694-
if not self.freq.is_anchored():
695-
# If freq is not anchored, then despite having matching freqs,
696-
# we might not "line up"
697-
return False
701+
elif self.freq.is_anchored():
702+
# this along with matching freqs ensure that we "line up",
703+
# so intersection will preserve freq
704+
return True
705+
706+
elif isinstance(self.freq, Tick):
707+
# We "line up" if and only if the difference between two of our points
708+
# is a multiple of our freq
709+
diff = self[0] - other[0]
710+
remainder = diff % self.freq.delta
711+
return remainder == Timedelta(0)
698712

699713
return True
700714

@@ -749,6 +763,7 @@ def _fast_union(self, other, sort=None):
749763
right_chunk = right._values[:loc]
750764
dates = concat_compat((left._values, right_chunk))
751765
# With sort being False, we can't infer that result.freq == self.freq
766+
# TODO: no tests rely on the _with_freq("infer"); needed?
752767
result = self._shallow_copy(dates)._with_freq("infer")
753768
return result
754769
else:
@@ -781,9 +796,12 @@ def _union(self, other, sort):
781796

782797
if this._can_fast_union(other):
783798
result = this._fast_union(other, sort=sort)
784-
if result.freq is None:
799+
if sort is None:
785800
# In the case where sort is None, _can_fast_union
786801
# implies that result.freq should match self.freq
802+
assert result.freq == self.freq, (result.freq, self.freq)
803+
elif result.freq is None:
804+
# TODO: no tests rely on this; needed?
787805
result = result._with_freq("infer")
788806
return result
789807
else:

pandas/tests/indexes/datetimes/test_setops.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ def test_intersection(self, tz, sort):
222222
expected3 = date_range("6/1/2000", "6/20/2000", freq="D", name=None)
223223

224224
rng4 = date_range("7/1/2000", "7/31/2000", freq="D", name="idx")
225-
expected4 = DatetimeIndex([], name="idx")
225+
expected4 = DatetimeIndex([], freq="D", name="idx")
226226

227227
for (rng, expected) in [
228228
(rng2, expected2),
@@ -264,7 +264,7 @@ def test_intersection(self, tz, sort):
264264
if sort is None:
265265
expected = expected.sort_values()
266266
tm.assert_index_equal(result, expected)
267-
assert result.freq is None
267+
assert result.freq == expected.freq
268268

269269
# parametrize over both anchored and non-anchored freqs, as they
270270
# have different code paths

pandas/tests/indexes/timedeltas/test_setops.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ def test_zero_length_input_index(self, sort):
156156
# if no overlap exists return empty index
157157
(
158158
timedelta_range("1 day", periods=10, freq="h", name="idx")[5:],
159-
TimedeltaIndex([], name="idx"),
159+
TimedeltaIndex([], freq="h", name="idx"),
160160
),
161161
],
162162
)

pandas/tests/indexing/test_partial.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ def test_partial_setting(self):
128128
df.at[dates[-1] + dates.freq, "A"] = 7
129129
tm.assert_frame_equal(df, expected)
130130

131-
exp_other = DataFrame({0: 7}, index=[dates[-1] + dates.freq])
131+
exp_other = DataFrame({0: 7}, index=dates[-1:] + dates.freq)
132132
expected = pd.concat([df_orig, exp_other], axis=1)
133133

134134
df = df_orig.copy()

0 commit comments

Comments
 (0)