Skip to content

BUG: DTI/TDI intersection result names #33904

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/source/whatsnew/v1.1.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,7 @@ Datetimelike
- Bug in :meth:`DatetimeIndex.intersection` losing ``freq`` and timezone in some cases (:issue:`33604`)
- Bug in :class:`DatetimeIndex` addition and subtraction with some types of :class:`DateOffset` objects incorrectly retaining an invalid ``freq`` attribute (:issue:`33779`)
- 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`)
- Bug in :meth:`DatetimeIndex.intersection` and :meth:`TimedeltaIndex.intersection` with results not having the correct ``name`` attribute (:issue:`33904`)

Timedelta
^^^^^^^^^
Expand Down
42 changes: 30 additions & 12 deletions pandas/core/indexes/datetimelike.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import numpy as np

from pandas._libs import NaT, iNaT, join as libjoin, lib
from pandas._libs import NaT, Timedelta, iNaT, join as libjoin, lib
from pandas._libs.tslibs import timezones
from pandas._typing import Label
from pandas.compat.numpy import function as nv
Expand Down Expand Up @@ -41,7 +41,7 @@
from pandas.core.sorting import ensure_key_mapped
from pandas.core.tools.timedeltas import to_timedelta

from pandas.tseries.frequencies import DateOffset
from pandas.tseries.offsets import DateOffset, Tick

_index_doc_kwargs = dict(ibase._index_doc_kwargs)

Expand Down Expand Up @@ -641,6 +641,7 @@ def intersection(self, other, sort=False):
"""
self._validate_sort_keyword(sort)
self._assert_can_do_setop(other)
res_name = get_op_result_name(self, other)

if self.equals(other):
return self._get_reconciled_name_object(other)
Expand All @@ -654,12 +655,18 @@ def intersection(self, other, sort=False):
result = Index.intersection(self, other, sort=sort)
if isinstance(result, type(self)):
if result.freq is None:
# TODO: no tests rely on this; needed?
result = result._with_freq("infer")
assert result.name == res_name
return result

elif not self._can_fast_intersect(other):
result = Index.intersection(self, other, sort=sort)
result = result._with_freq("infer")
assert result.name == res_name
# We need to invalidate the freq because Index.intersection
# uses _shallow_copy on a view of self._data, which will preserve
# self.freq if we're not careful.
result = result._with_freq(None)._with_freq("infer")
return result

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

if end < start:
return type(self)(data=[], dtype=self.dtype, freq=self.freq)
return type(self)(data=[], dtype=self.dtype, freq=self.freq, name=res_name)
else:
lslice = slice(*left.slice_locs(start, end))
left_chunk = left._values[lslice]
return self._shallow_copy(left_chunk)
return type(self)._simple_new(left_chunk, name=res_name)

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

if other.freq != self.freq:
elif other.freq != self.freq:
return False

if not self.is_monotonic_increasing:
elif not self.is_monotonic_increasing:
# Because freq is not None, we must then be monotonic decreasing
return False

if not self.freq.is_anchored():
# If freq is not anchored, then despite having matching freqs,
# we might not "line up"
return False
elif self.freq.is_anchored():
# this along with matching freqs ensure that we "line up",
# so intersection will preserve freq
return True

elif isinstance(self.freq, Tick):
# We "line up" if and only if the difference between two of our points
# is a multiple of our freq
diff = self[0] - other[0]
remainder = diff % self.freq.delta
return remainder == Timedelta(0)

return True

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

if this._can_fast_union(other):
result = this._fast_union(other, sort=sort)
if result.freq is None:
if sort is None:
# In the case where sort is None, _can_fast_union
# implies that result.freq should match self.freq
assert result.freq == self.freq, (result.freq, self.freq)
elif result.freq is None:
# TODO: no tests rely on this; needed?
result = result._with_freq("infer")
return result
else:
Expand Down
4 changes: 2 additions & 2 deletions pandas/tests/indexes/datetimes/test_setops.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ def test_intersection(self, tz, sort):
expected3 = date_range("6/1/2000", "6/20/2000", freq="D", name=None)

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

for (rng, expected) in [
(rng2, expected2),
Expand Down Expand Up @@ -264,7 +264,7 @@ def test_intersection(self, tz, sort):
if sort is None:
expected = expected.sort_values()
tm.assert_index_equal(result, expected)
assert result.freq is None
assert result.freq == expected.freq

# parametrize over both anchored and non-anchored freqs, as they
# have different code paths
Expand Down
2 changes: 1 addition & 1 deletion pandas/tests/indexes/timedeltas/test_setops.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ def test_zero_length_input_index(self, sort):
# if no overlap exists return empty index
(
timedelta_range("1 day", periods=10, freq="h", name="idx")[5:],
TimedeltaIndex([], name="idx"),
TimedeltaIndex([], freq="h", name="idx"),
),
],
)
Expand Down
2 changes: 1 addition & 1 deletion pandas/tests/indexing/test_partial.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ def test_partial_setting(self):
df.at[dates[-1] + dates.freq, "A"] = 7
tm.assert_frame_equal(df, expected)

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

df = df_orig.copy()
Expand Down