Skip to content

BUG: TimedeltaIndex[:] losing freq #33834

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 6 commits into from
Apr 27, 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
88 changes: 4 additions & 84 deletions pandas/core/indexes/datetimelike.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""
Base and utility classes for tseries type pandas objects.
"""
from datetime import datetime, timedelta
from datetime import datetime
from typing import Any, List, Optional, Union, cast

import numpy as np
Expand All @@ -16,18 +16,14 @@
from pandas.core.dtypes.common import (
ensure_int64,
is_bool_dtype,
is_datetime64_any_dtype,
is_dtype_equal,
is_integer,
is_list_like,
is_object_dtype,
is_period_dtype,
is_scalar,
is_timedelta64_dtype,
)
from pandas.core.dtypes.concat import concat_compat
from pandas.core.dtypes.generic import ABCIndex, ABCIndexClass, ABCSeries
from pandas.core.dtypes.missing import isna

from pandas.core import algorithms
from pandas.core.arrays import DatetimeArray, PeriodArray, TimedeltaArray
Expand All @@ -46,7 +42,6 @@
from pandas.core.tools.timedeltas import to_timedelta

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

_index_doc_kwargs = dict(ibase._index_doc_kwargs)

Expand Down Expand Up @@ -77,33 +72,13 @@ def wrapper(left, right):
return wrapper


def _make_wrapped_arith_op_with_freq(opname: str):
"""
Dispatch the operation to the underlying ExtensionArray, and infer
the appropriate frequency for the result.
"""
meth = make_wrapped_arith_op(opname)

def wrapped(self, other):
result = meth(self, other)
if result is NotImplemented:
return NotImplemented

new_freq = self._get_addsub_freq(other, result)
result._freq = new_freq
return result

wrapped.__name__ = opname
return wrapped


@inherit_names(
["inferred_freq", "_isnan", "_resolution", "resolution"],
DatetimeLikeArrayMixin,
cache=True,
)
@inherit_names(
["mean", "asi8", "_box_func"], DatetimeLikeArrayMixin,
["mean", "asi8", "freq", "freqstr", "_box_func"], DatetimeLikeArrayMixin,
)
class DatetimeIndexOpsMixin(ExtensionIndex):
"""
Expand Down Expand Up @@ -437,44 +412,8 @@ def _partial_date_slice(
# --------------------------------------------------------------------
# Arithmetic Methods

def _get_addsub_freq(self, other, result) -> Optional[DateOffset]:
"""
Find the freq we expect the result of an addition/subtraction operation
to have.
"""
if is_period_dtype(self.dtype):
if is_period_dtype(result.dtype):
# Only used for ops that stay PeriodDtype
return self.freq
return None
elif self.freq is None:
return None
elif lib.is_scalar(other) and isna(other):
return None

elif isinstance(other, (Tick, timedelta, np.timedelta64)):
new_freq = None
if isinstance(self.freq, Tick):
new_freq = self.freq
return new_freq

elif isinstance(other, DateOffset):
# otherwise just DatetimeArray
return None # TODO: Should we infer if it matches self.freq * n?
elif isinstance(other, (datetime, np.datetime64)):
return self.freq

elif is_timedelta64_dtype(other):
return None # TODO: shouldnt we be able to do self.freq + other.freq?
elif is_object_dtype(other):
return None # TODO: is this quite right? sometimes we unpack singletons
elif is_datetime64_any_dtype(other):
return None # TODO: shouldnt we be able to do self.freq + other.freq?
else:
raise NotImplementedError

__add__ = _make_wrapped_arith_op_with_freq("__add__")
__sub__ = _make_wrapped_arith_op_with_freq("__sub__")
__add__ = make_wrapped_arith_op("__add__")
__sub__ = make_wrapped_arith_op("__sub__")
__radd__ = make_wrapped_arith_op("__radd__")
__rsub__ = make_wrapped_arith_op("__rsub__")
__pow__ = make_wrapped_arith_op("__pow__")
Expand Down Expand Up @@ -643,25 +582,6 @@ class DatetimeTimedeltaMixin(DatetimeIndexOpsMixin, Int64Index):
_is_monotonic_increasing = Index.is_monotonic_increasing
_is_monotonic_decreasing = Index.is_monotonic_decreasing
_is_unique = Index.is_unique
_freq = lib.no_default

@property
def freq(self):
"""
In limited circumstances, our freq may differ from that of our _data.
"""
if self._freq is not lib.no_default:
return self._freq
return self._data.freq

@property
def freqstr(self):
"""
Return the frequency object as a string if its set, otherwise None.
"""
if self.freq is None:
return None
return self.freq.freqstr

def _with_freq(self, freq):
arr = self._data._with_freq(freq)
Expand Down
2 changes: 1 addition & 1 deletion pandas/core/indexes/period.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def _new_PeriodIndex(cls, **d):
PeriodArray,
wrap=True,
)
@inherit_names(["is_leap_year", "freq", "freqstr", "_format_native_types"], PeriodArray)
@inherit_names(["is_leap_year", "_format_native_types"], PeriodArray)
class PeriodIndex(DatetimeIndexOpsMixin, Int64Index):
"""
Immutable ndarray holding ordinal values indicating regular periods in time.
Expand Down
7 changes: 7 additions & 0 deletions pandas/tests/indexes/datetimelike.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,10 @@ def test_map_dictlike(self, mapper):
expected = pd.Index([np.nan] * len(index))
result = index.map(mapper([], []))
tm.assert_index_equal(result, expected)

def test_getitem_preserves_freq(self):
index = self.create_index()
assert index.freq is not None

result = index[:]
assert result.freq == index.freq