Skip to content

Commit

Permalink
BUG: DTI/TDI intersection result names (#33904)
Browse files Browse the repository at this point in the history
  • Loading branch information
jbrockmendel committed May 1, 2020
1 parent 81093ba commit 29c820f
Show file tree
Hide file tree
Showing 5 changed files with 35 additions and 16 deletions.
1 change: 1 addition & 0 deletions doc/source/whatsnew/v1.1.0.rst
Expand Up @@ -557,6 +557,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
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
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
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
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

0 comments on commit 29c820f

Please sign in to comment.