-
-
Notifications
You must be signed in to change notification settings - Fork 18.4k
REF: move range-generation functions to EA mixin classes #22016
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
Changes from 1 commit
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
c2d70c8
move range-generation functions
jbrockmendel 5b0ad23
flake8 fixup
jbrockmendel aa36838
Merge branch 'master' of https://github.com/pandas-dev/pandas into no…
jbrockmendel 8f45573
docstring, check extension_dtype instead of categorical_dtype
jbrockmendel 2293a72
Merge branch 'master' of https://github.com/pandas-dev/pandas into no…
jbrockmendel 140255d
Merge branch 'master' of https://github.com/pandas-dev/pandas into no…
jbrockmendel 9e79704
dummy commit to force CI
jbrockmendel e33ff43
Merge branch 'master' of https://github.com/pandas-dev/pandas into no…
jbrockmendel File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
# -*- coding: utf-8 -*- | ||
from datetime import datetime, timedelta | ||
import operator | ||
import warnings | ||
|
||
|
@@ -8,7 +9,7 @@ | |
from pandas._libs.tslibs import timezones | ||
from pandas._libs.tslibs.timedeltas import delta_to_nanoseconds, Timedelta | ||
from pandas._libs.tslibs.period import ( | ||
DIFFERENT_FREQ_INDEX, IncompatibleFrequency) | ||
Period, DIFFERENT_FREQ_INDEX, IncompatibleFrequency) | ||
|
||
from pandas.errors import NullFrequencyError, PerformanceWarning | ||
from pandas import compat | ||
|
@@ -19,6 +20,13 @@ | |
from pandas.core.dtypes.common import ( | ||
needs_i8_conversion, | ||
is_list_like, | ||
is_offsetlike, | ||
is_datetime64_dtype, | ||
is_datetime64_any_dtype, | ||
is_datetime64tz_dtype, | ||
is_categorical_dtype, | ||
is_float_dtype, | ||
is_integer_dtype, | ||
is_bool_dtype, | ||
is_period_dtype, | ||
is_timedelta64_dtype, | ||
|
@@ -100,7 +108,7 @@ class DatetimeLikeArrayMixin(ExtensionOpsMixin, AttributesMixin): | |
_freq | ||
|
||
and that the inheriting class has methods: | ||
_validate_frequency | ||
_generate_range | ||
""" | ||
|
||
@property | ||
|
@@ -132,6 +140,14 @@ def asi8(self): | |
# ------------------------------------------------------------------ | ||
# Array-like Methods | ||
|
||
@property | ||
def shape(self): | ||
return (len(self),) | ||
|
||
@property | ||
def size(self): | ||
return np.prod(self.shape) | ||
|
||
def __len__(self): | ||
return len(self._data) | ||
|
||
|
@@ -296,6 +312,34 @@ def resolution(self): | |
""" | ||
return frequencies.Resolution.get_str(self._resolution) | ||
|
||
@classmethod | ||
def _validate_frequency(cls, index, freq, **kwargs): | ||
""" | ||
Validate that a frequency is compatible with the values of a given | ||
DatetimeIndex or TimedeltaIndex | ||
|
||
Parameters | ||
---------- | ||
index : DatetimeIndex or TimedeltaIndex | ||
The index on which to determine if the given frequency is valid | ||
freq : DateOffset | ||
The frequency to validate | ||
""" | ||
if is_period_dtype(cls): | ||
# Frequency validation is not meaningful for Period Array/Index | ||
return | ||
|
||
inferred = index.inferred_freq | ||
if index.size == 0 or inferred == freq.freqstr: | ||
return None | ||
|
||
on_freq = cls._generate_range(start=index[0], end=None, | ||
periods=len(index), freq=freq, **kwargs) | ||
if not np.array_equal(index.asi8, on_freq.asi8): | ||
raise ValueError('Inferred frequency {infer} from passed values ' | ||
'does not conform to passed frequency {passed}' | ||
.format(infer=inferred, passed=freq.freqstr)) | ||
|
||
# ------------------------------------------------------------------ | ||
# Arithmetic Methods | ||
|
||
|
@@ -477,6 +521,188 @@ def _addsub_offset_array(self, other, op): | |
kwargs['freq'] = 'infer' | ||
return type(self)(res_values, **kwargs) | ||
|
||
def shift(self, n, freq=None): | ||
""" | ||
Specialized shift which produces a Datetime/Timedelta Array/Index | ||
|
||
Parameters | ||
---------- | ||
n : int | ||
Periods to shift by | ||
freq : DateOffset or timedelta-like, optional | ||
|
||
Returns | ||
------- | ||
shifted : same type as self | ||
""" | ||
if freq is not None and freq != self.freq: | ||
if isinstance(freq, compat.string_types): | ||
freq = frequencies.to_offset(freq) | ||
offset = n * freq | ||
result = self + offset | ||
|
||
if hasattr(self, 'tz'): | ||
result._tz = self.tz | ||
|
||
return result | ||
|
||
if n == 0: | ||
# immutable so OK | ||
return self | ||
|
||
if self.freq is None: | ||
raise NullFrequencyError("Cannot shift with no freq") | ||
|
||
start = self[0] + n * self.freq | ||
end = self[-1] + n * self.freq | ||
attribs = self._get_attributes_dict() | ||
return self._generate_range(start=start, end=end, periods=None, | ||
**attribs) | ||
|
||
@classmethod | ||
def _add_datetimelike_methods(cls): | ||
""" | ||
add in the datetimelike methods (as we may have to override the | ||
superclass) | ||
""" | ||
|
||
def __add__(self, other): | ||
other = lib.item_from_zerodim(other) | ||
if isinstance(other, (ABCSeries, ABCDataFrame)): | ||
return NotImplemented | ||
|
||
# scalar others | ||
elif other is NaT: | ||
result = self._add_nat() | ||
elif isinstance(other, (Tick, timedelta, np.timedelta64)): | ||
result = self._add_delta(other) | ||
elif isinstance(other, DateOffset): | ||
# specifically _not_ a Tick | ||
result = self._add_offset(other) | ||
elif isinstance(other, (datetime, np.datetime64)): | ||
result = self._add_datelike(other) | ||
elif lib.is_integer(other): | ||
# This check must come after the check for np.timedelta64 | ||
# as is_integer returns True for these | ||
result = self.shift(other) | ||
|
||
# array-like others | ||
elif is_timedelta64_dtype(other): | ||
# TimedeltaIndex, ndarray[timedelta64] | ||
result = self._add_delta(other) | ||
elif is_offsetlike(other): | ||
# Array/Index of DateOffset objects | ||
result = self._addsub_offset_array(other, operator.add) | ||
elif is_datetime64_dtype(other) or is_datetime64tz_dtype(other): | ||
# DatetimeIndex, ndarray[datetime64] | ||
return self._add_datelike(other) | ||
elif is_integer_dtype(other): | ||
result = self._addsub_int_array(other, operator.add) | ||
elif is_float_dtype(other) or is_period_dtype(other): | ||
# Explicitly catch invalid dtypes | ||
raise TypeError("cannot add {dtype}-dtype to {cls}" | ||
.format(dtype=other.dtype, | ||
cls=type(self).__name__)) | ||
elif is_categorical_dtype(other): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. better is is_extension_array_dtype |
||
# Categorical op will raise; defer explicitly | ||
return NotImplemented | ||
else: # pragma: no cover | ||
return NotImplemented | ||
|
||
return result | ||
|
||
cls.__add__ = __add__ | ||
|
||
def __radd__(self, other): | ||
# alias for __add__ | ||
return self.__add__(other) | ||
cls.__radd__ = __radd__ | ||
|
||
def __sub__(self, other): | ||
other = lib.item_from_zerodim(other) | ||
if isinstance(other, (ABCSeries, ABCDataFrame)): | ||
return NotImplemented | ||
|
||
# scalar others | ||
elif other is NaT: | ||
result = self._sub_nat() | ||
elif isinstance(other, (Tick, timedelta, np.timedelta64)): | ||
result = self._add_delta(-other) | ||
elif isinstance(other, DateOffset): | ||
# specifically _not_ a Tick | ||
result = self._add_offset(-other) | ||
elif isinstance(other, (datetime, np.datetime64)): | ||
result = self._sub_datelike(other) | ||
elif lib.is_integer(other): | ||
# This check must come after the check for np.timedelta64 | ||
# as is_integer returns True for these | ||
result = self.shift(-other) | ||
elif isinstance(other, Period): | ||
result = self._sub_period(other) | ||
|
||
# array-like others | ||
elif is_timedelta64_dtype(other): | ||
# TimedeltaIndex, ndarray[timedelta64] | ||
result = self._add_delta(-other) | ||
elif is_offsetlike(other): | ||
# Array/Index of DateOffset objects | ||
result = self._addsub_offset_array(other, operator.sub) | ||
elif is_datetime64_dtype(other) or is_datetime64tz_dtype(other): | ||
# DatetimeIndex, ndarray[datetime64] | ||
result = self._sub_datelike(other) | ||
elif is_period_dtype(other): | ||
# PeriodIndex | ||
result = self._sub_period_array(other) | ||
elif is_integer_dtype(other): | ||
result = self._addsub_int_array(other, operator.sub) | ||
elif isinstance(other, ABCIndexClass): | ||
raise TypeError("cannot subtract {cls} and {typ}" | ||
.format(cls=type(self).__name__, | ||
typ=type(other).__name__)) | ||
elif is_float_dtype(other): | ||
# Explicitly catch invalid dtypes | ||
raise TypeError("cannot subtract {dtype}-dtype from {cls}" | ||
.format(dtype=other.dtype, | ||
cls=type(self).__name__)) | ||
elif is_categorical_dtype(other): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same |
||
# Categorical op will raise; defer explicitly | ||
return NotImplemented | ||
else: # pragma: no cover | ||
return NotImplemented | ||
|
||
return result | ||
|
||
cls.__sub__ = __sub__ | ||
|
||
def __rsub__(self, other): | ||
if is_datetime64_dtype(other) and is_timedelta64_dtype(self): | ||
# ndarray[datetime64] cannot be subtracted from self, so | ||
# we need to wrap in DatetimeArray/Index and flip the operation | ||
if not isinstance(other, DatetimeLikeArrayMixin): | ||
# Avoid down-casting DatetimeIndex | ||
from pandas.core.arrays import DatetimeArrayMixin | ||
other = DatetimeArrayMixin(other) | ||
return other - self | ||
elif (is_datetime64_any_dtype(self) and hasattr(other, 'dtype') and | ||
not is_datetime64_any_dtype(other)): | ||
# GH#19959 datetime - datetime is well-defined as timedelta, | ||
# but any other type - datetime is not well-defined. | ||
raise TypeError("cannot subtract {cls} from {typ}" | ||
.format(cls=type(self).__name__, | ||
typ=type(other).__name__)) | ||
return -(self - other) | ||
cls.__rsub__ = __rsub__ | ||
|
||
def __iadd__(self, other): | ||
# alias for __add__ | ||
return self.__add__(other) | ||
cls.__iadd__ = __iadd__ | ||
|
||
def __isub__(self, other): | ||
# alias for __sub__ | ||
return self.__sub__(other) | ||
cls.__isub__ = __isub__ | ||
|
||
# -------------------------------------------------------------- | ||
# Comparison Methods | ||
|
||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
be explicit about the return