-
-
Notifications
You must be signed in to change notification settings - Fork 18.4k
[REF] Move comparison methods to EAMixins, share code #21872
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
Changes from 11 commits
1d6e970
ea608fc
0f39561
fbf1e6e
e7b7e90
5b0aa46
0b88a5c
af07649
e05c7e4
6da6da3
5d142c9
0af9876
1044d23
89e92b8
8ecc86d
1bcdb8f
935ac7b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1860,13 +1860,33 @@ cdef int64_t _ordinal_from_fields(year, month, quarter, day, | |
hour, minute, second, freq): | ||
base, mult = get_freq_code(freq) | ||
if quarter is not None: | ||
year, month = _quarter_to_myear(year, quarter, freq) | ||
year, month = quarter_to_myear(year, quarter, freq) | ||
|
||
return period_ordinal(year, month, day, hour, | ||
minute, second, 0, 0, base) | ||
|
||
|
||
def _quarter_to_myear(year, quarter, freq): | ||
def quarter_to_myear(year, quarter, freq): | ||
""" | ||
A quarterly frequency defines a "year" which may not coincide with | ||
the calendar-year. Find the calendar-year and calendar-month associated | ||
with the given year and quarter under the `freq`-derived calendar. | ||
|
||
Parameters | ||
---------- | ||
year : int | ||
quarter : int | ||
freq : DateOffset | ||
|
||
Returns | ||
------- | ||
year : int | ||
month : int | ||
|
||
See Also | ||
-------- | ||
Period.qyear | ||
""" | ||
if quarter is not None: | ||
if quarter <= 0 or quarter > 4: | ||
raise ValueError('Quarter must be 1 <= q <= 4') | ||
|
@@ -1877,6 +1897,8 @@ def _quarter_to_myear(year, quarter, freq): | |
year -= 1 | ||
|
||
return year, month | ||
# FIXME: if quarter is None then `month` won't be defined here. | ||
# just disallow quarter == None? | ||
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. cc @jreback |
||
|
||
|
||
def _validate_end_alias(how): | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,19 +10,53 @@ | |
DIFFERENT_FREQ_INDEX, IncompatibleFrequency) | ||
|
||
from pandas.errors import NullFrequencyError, PerformanceWarning | ||
from pandas import compat | ||
|
||
from pandas.tseries import frequencies | ||
from pandas.tseries.offsets import Tick | ||
|
||
from pandas.core.dtypes.common import ( | ||
needs_i8_conversion, | ||
is_list_like, | ||
is_bool_dtype, | ||
is_period_dtype, | ||
is_timedelta64_dtype, | ||
is_object_dtype) | ||
from pandas.core.dtypes.generic import ABCSeries, ABCDataFrame, ABCIndexClass | ||
|
||
import pandas.core.common as com | ||
from pandas.core.algorithms import checked_add_with_arr | ||
|
||
|
||
def _make_comparison_op(op, cls): | ||
# TODO: share code with indexes.base version? Main difference is that | ||
# the block for MultiIndex was removed here. | ||
def cmp_method(self, other): | ||
if isinstance(other, ABCDataFrame): | ||
return NotImplemented | ||
|
||
if isinstance(other, (np.ndarray, ABCIndexClass, ABCSeries)): | ||
if other.ndim > 0 and len(self) != len(other): | ||
raise ValueError('Lengths must match to compare') | ||
|
||
if needs_i8_conversion(self) and needs_i8_conversion(other): | ||
# we may need to directly compare underlying | ||
# representations | ||
return self._evaluate_compare(other, op) | ||
|
||
# numpy will show a DeprecationWarning on invalid elementwise | ||
# comparisons, this will raise in the future | ||
with warnings.catch_warnings(record=True): | ||
with np.errstate(all='ignore'): | ||
result = op(self.values, np.asarray(other)) | ||
|
||
return result | ||
|
||
name = '__{name}__'.format(name=op.__name__) | ||
# TODO: docstring? | ||
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. Yes, if you can. |
||
return compat.set_function_name(cmp_method, name, cls) | ||
|
||
|
||
class AttributesMixin(object): | ||
|
||
@property | ||
|
@@ -435,3 +469,68 @@ def _addsub_offset_array(self, other, op): | |
if not is_period_dtype(self): | ||
kwargs['freq'] = 'infer' | ||
return type(self)(res_values, **kwargs) | ||
|
||
# -------------------------------------------------------------- | ||
# Comparison Methods | ||
|
||
def _evaluate_compare(self, other, op): | ||
""" | ||
We have been called because a comparison between | ||
8 aware arrays. numpy >= 1.11 will | ||
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. "8 aware arrays" ? 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. This is the existing docstring in indexes.datetimelike. I'm open to suggestions. |
||
now warn about NaT comparisons | ||
""" | ||
# Called by comparison methods when comparing datetimelike | ||
# with datetimelike | ||
|
||
if not isinstance(other, type(self)): | ||
# coerce to a similar object | ||
if not is_list_like(other): | ||
# scalar | ||
other = [other] | ||
elif lib.is_scalar(lib.item_from_zerodim(other)): | ||
# ndarray scalar | ||
other = [other.item()] | ||
other = type(self)(other) | ||
|
||
# compare | ||
result = op(self.asi8, other.asi8) | ||
|
||
# technically we could support bool dtyped Index | ||
# for now just return the indexing array directly | ||
mask = (self._isnan) | (other._isnan) | ||
|
||
filler = iNaT | ||
if is_bool_dtype(result): | ||
filler = False | ||
|
||
result[mask] = filler | ||
return result | ||
|
||
# TODO: get this from ExtensionOpsMixin | ||
@classmethod | ||
def _add_comparison_methods(cls): | ||
""" add in comparison methods """ | ||
# DatetimeArray and TimedeltaArray comparison methods will | ||
# call these as their super(...) methods | ||
cls.__eq__ = _make_comparison_op(operator.eq, cls) | ||
cls.__ne__ = _make_comparison_op(operator.ne, cls) | ||
cls.__lt__ = _make_comparison_op(operator.lt, cls) | ||
cls.__gt__ = _make_comparison_op(operator.gt, cls) | ||
cls.__le__ = _make_comparison_op(operator.le, cls) | ||
cls.__ge__ = _make_comparison_op(operator.ge, cls) | ||
|
||
|
||
DatetimeLikeArrayMixin._add_comparison_methods() | ||
|
||
|
||
# ------------------------------------------------------------------- | ||
# Shared Constructor Helpers | ||
|
||
def validate_periods(periods): | ||
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. docstring |
||
if periods is not None: | ||
if lib.is_float(periods): | ||
periods = int(periods) | ||
elif not lib.is_integer(periods): | ||
raise TypeError('periods must be a number, got {periods}' | ||
.format(periods=periods)) | ||
return periods |
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.
ideally type these, where is quarter passed as None?
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.
It is used once in period.pyx and once in arrays.period, always with quarter non-None. I'll make that required.