From 1d6e97043086167de012ef1f0bb96c0318863f54 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Wed, 11 Jul 2018 17:50:35 -0700 Subject: [PATCH 01/16] rename timedelta-->timedeltas to match indexes naming --- pandas/core/arrays/{timedelta.py => timedeltas.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pandas/core/arrays/{timedelta.py => timedeltas.py} (100%) diff --git a/pandas/core/arrays/timedelta.py b/pandas/core/arrays/timedeltas.py similarity index 100% rename from pandas/core/arrays/timedelta.py rename to pandas/core/arrays/timedeltas.py From ea608fc0c7d668d9d91cadbea4b1078e9d3958fe Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Wed, 11 Jul 2018 17:50:56 -0700 Subject: [PATCH 02/16] update imports --- pandas/core/arrays/__init__.py | 2 +- pandas/core/indexes/timedeltas.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/core/arrays/__init__.py b/pandas/core/arrays/__init__.py index 1b8a43d4293a5..6ccbb872bf50e 100644 --- a/pandas/core/arrays/__init__.py +++ b/pandas/core/arrays/__init__.py @@ -3,4 +3,4 @@ from .categorical import Categorical # noqa from .datetimes import DatetimeArrayMixin # noqa from .period import PeriodArrayMixin # noqa -from .timedelta import TimedeltaArrayMixin # noqa +from .timedeltas import TimedeltaArrayMixin # noqa diff --git a/pandas/core/indexes/timedeltas.py b/pandas/core/indexes/timedeltas.py index eb1171c45b1e5..013ee173e3f5f 100644 --- a/pandas/core/indexes/timedeltas.py +++ b/pandas/core/indexes/timedeltas.py @@ -15,7 +15,7 @@ from pandas.core.dtypes.missing import isna from pandas.core.dtypes.generic import ABCSeries -from pandas.core.arrays.timedelta import ( +from pandas.core.arrays.timedeltas import ( TimedeltaArrayMixin, _is_convertible_to_td) from pandas.core.indexes.base import Index from pandas.core.indexes.numeric import Int64Index From 0f39561daf43c3e99543545b57297e457a8f8362 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Wed, 11 Jul 2018 18:13:02 -0700 Subject: [PATCH 03/16] remove usage of np 1.7 deprecated API --- pandas/_libs/src/numpy_helper.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/_libs/src/numpy_helper.h b/pandas/_libs/src/numpy_helper.h index 5cfa51dc8a0be..3573a561945d2 100644 --- a/pandas/_libs/src/numpy_helper.h +++ b/pandas/_libs/src/numpy_helper.h @@ -50,7 +50,7 @@ PANDAS_INLINE PyObject* char_to_string(const char* data) { void set_array_not_contiguous(PyArrayObject* ao) { - ao->flags &= ~(NPY_C_CONTIGUOUS | NPY_F_CONTIGUOUS); + ao->flags &= ~(NPY_ARRAY_C_CONTIGUOUS | NPY_ARRAY_F_CONTIGUOUS); } #endif // PANDAS__LIBS_SRC_NUMPY_HELPER_H_ From fbf1e6e7686dc73af60ba91972ebc165db469a2f Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Wed, 11 Jul 2018 18:58:14 -0700 Subject: [PATCH 04/16] de-privatize _quarter_to_myear --- pandas/_libs/tslibs/period.pyx | 26 ++++++++++++++++++++++++-- pandas/core/arrays/period.py | 4 ++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/pandas/_libs/tslibs/period.pyx b/pandas/_libs/tslibs/period.pyx index 1796a764ae326..45dcb1664fef2 100644 --- a/pandas/_libs/tslibs/period.pyx +++ b/pandas/_libs/tslibs/period.pyx @@ -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? def _validate_end_alias(how): diff --git a/pandas/core/arrays/period.py b/pandas/core/arrays/period.py index 000775361061e..ef86c3205fd3c 100644 --- a/pandas/core/arrays/period.py +++ b/pandas/core/arrays/period.py @@ -8,7 +8,7 @@ from pandas._libs.tslib import NaT, iNaT from pandas._libs.tslibs.period import ( Period, IncompatibleFrequency, DIFFERENT_FREQ_INDEX, - get_period_field_arr, period_asfreq_arr, _quarter_to_myear) + get_period_field_arr, period_asfreq_arr) from pandas._libs.tslibs import period as libperiod from pandas._libs.tslibs.timedeltas import delta_to_nanoseconds from pandas._libs.tslibs.fields import isleapyear_arr @@ -466,7 +466,7 @@ def _range_from_fields(year=None, month=None, quarter=None, day=None, year, quarter = _make_field_arrays(year, quarter) for y, q in compat.zip(year, quarter): - y, m = _quarter_to_myear(y, q, freq) + y, m = libperiod.quarter_to_myear(y, q, freq) val = libperiod.period_ordinal(y, m, 1, 1, 1, 1, 0, 0, base) ordinals.append(val) else: From e7b7e900fc89a0070b9585fafcdef52ae4a5d8ee Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Wed, 11 Jul 2018 19:00:03 -0700 Subject: [PATCH 05/16] requested comment --- pandas/core/dtypes/inference.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pandas/core/dtypes/inference.py b/pandas/core/dtypes/inference.py index a0456630c9a0f..ed416c3ef857d 100644 --- a/pandas/core/dtypes/inference.py +++ b/pandas/core/dtypes/inference.py @@ -285,7 +285,9 @@ def is_list_like(obj): """ return (isinstance(obj, Iterable) and + # we do not count strings/unicode/bytes as list-like not isinstance(obj, string_and_binary_types) and + # exclude zero-dimensional numpy arrays, effectively scalars not (isinstance(obj, np.ndarray) and obj.ndim == 0)) From 5b0aa467ef80fc0b8f0400374ce0bcccabc5cfd9 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Wed, 11 Jul 2018 19:00:26 -0700 Subject: [PATCH 06/16] stop making copies now that DateOffsets are immutable --- pandas/tseries/frequencies.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/tseries/frequencies.py b/pandas/tseries/frequencies.py index d4ad2e4eeb2e6..387a70fe37253 100644 --- a/pandas/tseries/frequencies.py +++ b/pandas/tseries/frequencies.py @@ -208,8 +208,8 @@ def get_offset(name): raise ValueError(libfreqs.INVALID_FREQ_ERR_MSG.format(name)) # cache _offset_map[name] = offset - # do not return cache because it's mutable - return _offset_map[name].copy() + + return _offset_map[name] getOffset = get_offset From 0b88a5c864aeff85fe416656df9dc4538655527a Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Wed, 11 Jul 2018 19:44:59 -0700 Subject: [PATCH 07/16] implement comparison methods on EAMixins --- pandas/core/arrays/datetimelike.py | 86 ++++++++++++++++++++++++ pandas/core/arrays/datetimes.py | 84 ++++++++++++++++++++++- pandas/core/arrays/timedeltas.py | 70 ++++++++++++++++++- pandas/core/indexes/base.py | 8 --- pandas/core/indexes/datetimelike.py | 27 +------- pandas/core/indexes/datetimes.py | 58 +--------------- pandas/core/indexes/timedeltas.py | 47 +------------ pandas/tests/arrays/test_datetimelike.py | 2 +- 8 files changed, 247 insertions(+), 135 deletions(-) diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index 65f34b847f8d0..0f134b5b75d55 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -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? + return compat.set_function_name(cmp_method, name, cls) + + class AttributesMixin(object): @property @@ -435,3 +469,55 @@ 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 + 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() diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index d7dfa73c53d8d..b80e0d36a844e 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -13,15 +13,20 @@ from pandas.util._decorators import cache_readonly from pandas.errors import PerformanceWarning +from pandas import compat from pandas.core.dtypes.common import ( _NS_DTYPE, + is_datetimelike, is_datetime64tz_dtype, is_datetime64_dtype, is_timedelta64_dtype, _ensure_int64) from pandas.core.dtypes.dtypes import DatetimeTZDtype +from pandas.core.dtypes.missing import isna +from pandas.core.dtypes.generic import ABCIndexClass, ABCSeries +import pandas.core.common as com from pandas.core.algorithms import checked_add_with_arr from pandas.tseries.frequencies import to_offset, DateOffset @@ -30,6 +35,17 @@ from .datetimelike import DatetimeLikeArrayMixin +def _to_m8(key, tz=None): + """ + Timestamp-like => dt64 + """ + if not isinstance(key, Timestamp): + # this also converts strings + key = Timestamp(key, tz=tz) + + return np.int64(conversion.pydt_to_i8(key)).view(_NS_DTYPE) + + def _field_accessor(name, field, docstring=None): def f(self): values = self.asi8 @@ -68,6 +84,57 @@ def f(self): return property(f) +def _dt_array_cmp(opname, cls): + """ + Wrap comparison operations to convert datetime-like to datetime64 + """ + nat_result = True if opname == '__ne__' else False + + def wrapper(self, other): + meth = getattr(DatetimeLikeArrayMixin, opname) + + if isinstance(other, (datetime, np.datetime64, compat.string_types)): + if isinstance(other, datetime): + # GH#18435 strings get a pass from tzawareness compat + self._assert_tzawareness_compat(other) + + other = _to_m8(other, tz=self.tz) + result = meth(self, other) + if isna(other): + result.fill(nat_result) + else: + if isinstance(other, list): + other = type(self)(other) + elif not isinstance(other, (np.ndarray, ABCIndexClass, ABCSeries)): + # Following Timestamp convention, __eq__ is all-False + # and __ne__ is all True, others raise TypeError. + if opname == '__eq__': + return np.zeros(shape=self.shape, dtype=bool) + elif opname == '__ne__': + return np.ones(shape=self.shape, dtype=bool) + raise TypeError('%s type object %s' % + (type(other), str(other))) + + if is_datetimelike(other): + self._assert_tzawareness_compat(other) + + result = meth(self, np.asarray(other)) + result = com._values_from_object(result) + + # Make sure to pass an array to result[...]; indexing with + # Series breaks with older version of numpy + o_mask = np.array(isna(other)) + if o_mask.any(): + result[o_mask] = nat_result + + if self.hasnans: + result[self._isnan] = nat_result + + return result + + return compat.set_function_name(wrapper, opname, cls) + + class DatetimeArrayMixin(DatetimeLikeArrayMixin): """ Assumes that subclass __new__/__init__ defines: @@ -222,6 +289,18 @@ def __iter__(self): # ----------------------------------------------------------------- # Comparison Methods + @classmethod + def _add_comparison_methods(cls): + """add in comparison methods""" + cls.__eq__ = _dt_array_cmp('__eq__', cls) + cls.__ne__ = _dt_array_cmp('__ne__', cls) + cls.__lt__ = _dt_array_cmp('__lt__', cls) + cls.__gt__ = _dt_array_cmp('__gt__', cls) + cls.__le__ = _dt_array_cmp('__le__', cls) + cls.__ge__ = _dt_array_cmp('__ge__', cls) + # TODO: Some classes pass __eq__ while others pass operator.eq; + # standardize this. + def _has_same_tz(self, other): zzone = self._timezone @@ -335,7 +414,7 @@ def _add_delta(self, delta): The result's name is set outside of _add_delta by the calling method (__add__ or __sub__) """ - from pandas.core.arrays.timedelta import TimedeltaArrayMixin + from pandas.core.arrays.timedeltas import TimedeltaArrayMixin if isinstance(delta, (Tick, timedelta, np.timedelta64)): new_values = self._add_delta_td(delta) @@ -1021,3 +1100,6 @@ def to_julian_date(self): self.microsecond / 3600.0 / 1e+6 + self.nanosecond / 3600.0 / 1e+9 ) / 24.0) + + +DatetimeArrayMixin._add_comparison_methods() diff --git a/pandas/core/arrays/timedeltas.py b/pandas/core/arrays/timedeltas.py index dbd481aae4f37..e22103be9039a 100644 --- a/pandas/core/arrays/timedeltas.py +++ b/pandas/core/arrays/timedeltas.py @@ -11,7 +11,7 @@ from pandas import compat from pandas.core.dtypes.common import ( - _TD_DTYPE, _ensure_int64, is_timedelta64_dtype) + _TD_DTYPE, _ensure_int64, is_timedelta64_dtype, is_list_like) from pandas.core.dtypes.generic import ABCSeries from pandas.core.dtypes.missing import isna @@ -23,6 +23,18 @@ from .datetimelike import DatetimeLikeArrayMixin +def _to_m8(key): + """ + Timedelta-like => dt64 + """ + if not isinstance(key, Timedelta): + # this also converts strings + key = Timedelta(key) + + # return an type that can be compared + return np.int64(key.value).view(_TD_DTYPE) + + def _is_convertible_to_td(key): return isinstance(key, (Tick, timedelta, np.timedelta64, compat.string_types)) @@ -42,6 +54,46 @@ def f(self): return property(f) +def _td_array_cmp(opname, cls): + """ + Wrap comparison operations to convert timedelta-like to timedelta64 + """ + nat_result = True if opname == '__ne__' else False + + def wrapper(self, other): + msg = "cannot compare a {cls} with type {typ}" + meth = getattr(DatetimeLikeArrayMixin, opname) + if _is_convertible_to_td(other) or other is NaT: + try: + other = _to_m8(other) + except ValueError: + # failed to parse as timedelta + raise TypeError(msg.format(cls=type(self).__name__, + typ=type(other).__name__)) + result = meth(self, other) + if isna(other): + result.fill(nat_result) + + elif not is_list_like(other): + raise TypeError(msg.format(cls=type(self).__name__, + typ=type(other).__name__)) + else: + other = type(self)(other).values + result = meth(self, other) + result = com._values_from_object(result) + + o_mask = np.array(isna(other)) + if o_mask.any(): + result[o_mask] = nat_result + + if self.hasnans: + result[self._isnan] = nat_result + + return result + + return compat.set_function_name(wrapper, opname, cls) + + class TimedeltaArrayMixin(DatetimeLikeArrayMixin): @property def _box_func(self): @@ -219,6 +271,19 @@ def _evaluate_with_timedelta_like(self, other, op): return NotImplemented + # ---------------------------------------------------------------- + # Comparison Methods + + @classmethod + def _add_comparison_methods(cls): + """add in comparison methods""" + cls.__eq__ = _td_array_cmp('__eq__', cls) + cls.__ne__ = _td_array_cmp('__ne__', cls) + cls.__lt__ = _td_array_cmp('__lt__', cls) + cls.__gt__ = _td_array_cmp('__gt__', cls) + cls.__le__ = _td_array_cmp('__le__', cls) + cls.__ge__ = _td_array_cmp('__ge__', cls) + # ---------------------------------------------------------------- # Conversion Methods - Vectorized analogues of Timedelta methods @@ -332,6 +397,9 @@ def f(x): return result +TimedeltaArrayMixin._add_comparison_methods() + + # --------------------------------------------------------------------- # Constructor Helpers diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 78fa6f8217157..efaaf827c1bc0 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -87,11 +87,6 @@ def cmp_method(self, other): if other.ndim > 0 and len(self) != len(other): raise ValueError('Lengths must match to compare') - # we may need to directly compare underlying - # representations - if needs_i8_conversion(self) and needs_i8_conversion(other): - return self._evaluate_compare(other, op) - from .multi import MultiIndex if is_object_dtype(self) and not isinstance(self, MultiIndex): # don't pass MultiIndex @@ -4628,9 +4623,6 @@ def _evaluate_with_timedelta_like(self, other, op): def _evaluate_with_datetime_like(self, other, op): raise TypeError("can only perform ops with datetime like values") - def _evaluate_compare(self, other, op): - raise com.AbstractMethodError(self) - @classmethod def _add_comparison_methods(cls): """ add in comparison methods """ diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index 37e20496aafce..c033be16a6176 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -290,34 +290,11 @@ def wrapper(left, right): return wrapper + @Appender(DatetimeLikeArrayMixin._evaluate_compare.__doc__) def _evaluate_compare(self, other, op): - """ - We have been called because a comparison between - 8 aware arrays. numpy >= 1.11 will - now warn about NaT comparisons - """ - - # coerce to a similar object - if not isinstance(other, type(self)): - if not is_list_like(other): - # scalar - other = [other] - elif 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) + result = DatetimeLikeArrayMixin._evaluate_compare(self, other, op) if is_bool_dtype(result): - result[mask] = False return result - - result[mask] = iNaT try: return Index(result) except TypeError: diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index 4931610e652b6..3307659130518 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -18,7 +18,7 @@ is_integer, is_float, is_integer_dtype, - is_datetime64_ns_dtype, is_datetimelike, + is_datetime64_ns_dtype, is_period_dtype, is_bool_dtype, is_string_like, @@ -31,7 +31,7 @@ from pandas.core.dtypes.missing import isna import pandas.core.dtypes.concat as _concat -from pandas.core.arrays.datetimes import DatetimeArrayMixin +from pandas.core.arrays.datetimes import DatetimeArrayMixin, _to_m8 from pandas.core.indexes.base import Index, _index_shared_docs from pandas.core.indexes.numeric import Int64Index, Float64Index @@ -87,49 +87,8 @@ def _dt_index_cmp(opname, cls): """ Wrap comparison operations to convert datetime-like to datetime64 """ - nat_result = True if opname == '__ne__' else False - def wrapper(self, other): - func = getattr(super(DatetimeIndex, self), opname) - - if isinstance(other, (datetime, np.datetime64, compat.string_types)): - if isinstance(other, datetime): - # GH#18435 strings get a pass from tzawareness compat - self._assert_tzawareness_compat(other) - - other = _to_m8(other, tz=self.tz) - result = func(other) - if isna(other): - result.fill(nat_result) - else: - if isinstance(other, list): - other = DatetimeIndex(other) - elif not isinstance(other, (np.ndarray, Index, ABCSeries)): - # Following Timestamp convention, __eq__ is all-False - # and __ne__ is all True, others raise TypeError. - if opname == '__eq__': - return np.zeros(shape=self.shape, dtype=bool) - elif opname == '__ne__': - return np.ones(shape=self.shape, dtype=bool) - raise TypeError('%s type object %s' % - (type(other), str(other))) - - if is_datetimelike(other): - self._assert_tzawareness_compat(other) - - result = func(np.asarray(other)) - result = com._values_from_object(result) - - # Make sure to pass an array to result[...]; indexing with - # Series breaks with older version of numpy - o_mask = np.array(isna(other)) - if o_mask.any(): - result[o_mask] = nat_result - - if self.hasnans: - result[self._isnan] = nat_result - - # support of bool dtype indexers + result = getattr(DatetimeArrayMixin, opname)(self, other) if is_bool_dtype(result): return result return Index(result) @@ -2088,17 +2047,6 @@ def cdate_range(start=None, end=None, periods=None, freq='C', tz=None, closed=closed, **kwargs) -def _to_m8(key, tz=None): - """ - Timestamp-like => dt64 - """ - if not isinstance(key, Timestamp): - # this also converts strings - key = Timestamp(key, tz=tz) - - return np.int64(conversion.pydt_to_i8(key)).view(_NS_DTYPE) - - _CACHE_START = Timestamp(datetime(1950, 1, 1)) _CACHE_END = Timestamp(datetime(2030, 1, 1)) diff --git a/pandas/core/indexes/timedeltas.py b/pandas/core/indexes/timedeltas.py index 013ee173e3f5f..87fbfecfc0447 100644 --- a/pandas/core/indexes/timedeltas.py +++ b/pandas/core/indexes/timedeltas.py @@ -16,7 +16,7 @@ from pandas.core.dtypes.generic import ABCSeries from pandas.core.arrays.timedeltas import ( - TimedeltaArrayMixin, _is_convertible_to_td) + TimedeltaArrayMixin, _is_convertible_to_td, _to_m8) from pandas.core.indexes.base import Index from pandas.core.indexes.numeric import Int64Index import pandas.compat as compat @@ -53,39 +53,10 @@ def _td_index_cmp(opname, cls): """ Wrap comparison operations to convert timedelta-like to timedelta64 """ - nat_result = True if opname == '__ne__' else False - def wrapper(self, other): - msg = "cannot compare a {cls} with type {typ}" - func = getattr(super(TimedeltaIndex, self), opname) - if _is_convertible_to_td(other) or other is NaT: - try: - other = _to_m8(other) - except ValueError: - # failed to parse as timedelta - raise TypeError(msg.format(cls=type(self).__name__, - typ=type(other).__name__)) - result = func(other) - if isna(other): - result.fill(nat_result) - - elif not is_list_like(other): - raise TypeError(msg.format(cls=type(self).__name__, - typ=type(other).__name__)) - else: - other = TimedeltaIndex(other).values - result = func(other) - result = com._values_from_object(result) - - o_mask = np.array(isna(other)) - if o_mask.any(): - result[o_mask] = nat_result - - if self.hasnans: - result[self._isnan] = nat_result - - # support of bool dtype indexers + result = getattr(TimedeltaArrayMixin, opname)(self, other) if is_bool_dtype(result): + # support of bool dtype indexers return result return Index(result) @@ -797,18 +768,6 @@ def _is_convertible_to_index(other): return False -def _to_m8(key): - """ - Timedelta-like => dt64 - """ - if not isinstance(key, Timedelta): - # this also converts strings - key = Timedelta(key) - - # return an type that can be compared - return np.int64(key.value).view(_TD_DTYPE) - - def timedelta_range(start=None, end=None, periods=None, freq=None, name=None, closed=None): """ diff --git a/pandas/tests/arrays/test_datetimelike.py b/pandas/tests/arrays/test_datetimelike.py index d116b3bcff86a..69e802fbaa3f0 100644 --- a/pandas/tests/arrays/test_datetimelike.py +++ b/pandas/tests/arrays/test_datetimelike.py @@ -4,7 +4,7 @@ import pandas as pd from pandas.core.arrays.datetimes import DatetimeArrayMixin -from pandas.core.arrays.timedelta import TimedeltaArrayMixin +from pandas.core.arrays.timedeltas import TimedeltaArrayMixin from pandas.core.arrays.period import PeriodArrayMixin From af0764959b00090a872604f2c7f46f0db219b680 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Wed, 11 Jul 2018 19:58:03 -0700 Subject: [PATCH 08/16] share code between TDI and DTI constructors --- pandas/core/arrays/datetimelike.py | 12 ++++++++++++ pandas/core/arrays/timedeltas.py | 8 ++------ pandas/core/indexes/datetimes.py | 8 ++------ pandas/core/indexes/timedeltas.py | 26 ++++++++++---------------- 4 files changed, 26 insertions(+), 28 deletions(-) diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index 0f134b5b75d55..1029667eecfcc 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -521,3 +521,15 @@ def _add_comparison_methods(cls): DatetimeLikeArrayMixin._add_comparison_methods() + +# ------------------------------------------------------------------- +# Shared Constructor Helpers + +def validate_periods(periods): + 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 diff --git a/pandas/core/arrays/timedeltas.py b/pandas/core/arrays/timedeltas.py index e22103be9039a..cf389f54382d4 100644 --- a/pandas/core/arrays/timedeltas.py +++ b/pandas/core/arrays/timedeltas.py @@ -21,6 +21,7 @@ from pandas.tseries.frequencies import to_offset from .datetimelike import DatetimeLikeArrayMixin +from . import datetimelike as dtl def _to_m8(key): @@ -130,12 +131,7 @@ def __new__(cls, values, freq=None, start=None, end=None, periods=None, freq != 'infer'): freq = to_offset(freq) - 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)) + periods = dtl.validate_periods(periods) if values is None: if freq is None and com._any_none(periods, start, end): diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index 3307659130518..88b73aa408081 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -32,6 +32,7 @@ import pandas.core.dtypes.concat as _concat from pandas.core.arrays.datetimes import DatetimeArrayMixin, _to_m8 +from pandas.core.arrays import datetimelike as dtl from pandas.core.indexes.base import Index, _index_shared_docs from pandas.core.indexes.numeric import Int64Index, Float64Index @@ -298,12 +299,7 @@ def __new__(cls, data=None, freq_infer = True freq = None - if periods is not None: - if is_float(periods): - periods = int(periods) - elif not is_integer(periods): - msg = 'periods must be a number, got {periods}' - raise TypeError(msg.format(periods=periods)) + periods = dtl.validate_periods(periods) # if dtype has an embedded tz, capture it if dtype is not None: diff --git a/pandas/core/indexes/timedeltas.py b/pandas/core/indexes/timedeltas.py index 87fbfecfc0447..2c36b3f18041d 100644 --- a/pandas/core/indexes/timedeltas.py +++ b/pandas/core/indexes/timedeltas.py @@ -17,6 +17,8 @@ from pandas.core.arrays.timedeltas import ( TimedeltaArrayMixin, _is_convertible_to_td, _to_m8) +from pandas.core.arrays import datetimelike as dtl + from pandas.core.indexes.base import Index from pandas.core.indexes.numeric import Int64Index import pandas.compat as compat @@ -189,12 +191,7 @@ def __new__(cls, data=None, unit=None, freq=None, start=None, end=None, freq_infer = True freq = None - if periods is not None: - if is_float(periods): - periods = int(periods) - elif not is_integer(periods): - msg = 'periods must be a number, got {periods}' - raise TypeError(msg.format(periods=periods)) + periods = dtl.validate_periods(periods) if data is None: if freq is None and com._any_none(periods, start, end): @@ -219,22 +216,19 @@ def __new__(cls, data=None, unit=None, freq=None, start=None, end=None, elif copy: data = np.array(data, copy=True) + subarr = cls._simple_new(data, name=name, freq=freq) # check that we are matching freqs - if verify_integrity and len(data) > 0: + if verify_integrity and len(subarr) > 0: if freq is not None and not freq_infer: - index = cls._simple_new(data, name=name) - cls._validate_frequency(index, freq) - index.freq = freq - return index + cls._validate_frequency(subarr, freq) if freq_infer: - index = cls._simple_new(data, name=name) - inferred = index.inferred_freq + inferred = subarr.inferred_freq if inferred: - index.freq = to_offset(inferred) - return index + subarr.freq = to_offset(inferred) + return subarr - return cls._simple_new(data, name=name, freq=freq) + return subarr @classmethod def _generate(cls, start, end, periods, name, freq, closed=None): From e05c7e4b26ddf6716ad0b1cfab647f8c629b69e1 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Wed, 11 Jul 2018 20:09:55 -0700 Subject: [PATCH 09/16] make generate_range more class-agnostic, remove unused imports --- pandas/core/arrays/datetimelike.py | 1 + pandas/core/arrays/timedeltas.py | 2 +- pandas/core/indexes/base.py | 1 - pandas/core/indexes/datetimes.py | 12 +++++++----- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index 1029667eecfcc..7b28fb6404812 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -522,6 +522,7 @@ def _add_comparison_methods(cls): DatetimeLikeArrayMixin._add_comparison_methods() + # ------------------------------------------------------------------- # Shared Constructor Helpers diff --git a/pandas/core/arrays/timedeltas.py b/pandas/core/arrays/timedeltas.py index cf389f54382d4..3c1cbe77fcd65 100644 --- a/pandas/core/arrays/timedeltas.py +++ b/pandas/core/arrays/timedeltas.py @@ -3,7 +3,7 @@ import numpy as np -from pandas._libs import tslibs, lib +from pandas._libs import tslibs from pandas._libs.tslibs import Timedelta, NaT from pandas._libs.tslibs.fields import get_timedelta_field from pandas._libs.tslibs.timedeltas import array_to_timedelta64 diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index efaaf827c1bc0..419e543ae8044 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -45,7 +45,6 @@ is_datetime64tz_dtype, is_timedelta64_dtype, is_hashable, - needs_i8_conversion, is_iterator, is_list_like, is_scalar) diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index 88b73aa408081..4fa90db26f0fb 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -476,7 +476,7 @@ def _generate(cls, start, end, periods, name, freq, index = cls._cached_range(start, end, periods=periods, freq=freq, name=name) else: - index = _generate_regular_range(start, end, periods, freq) + index = _generate_regular_range(cls, start, end, periods, freq) else: @@ -500,14 +500,15 @@ def _generate(cls, start, end, periods, name, freq, index = cls._cached_range(start, end, periods=periods, freq=freq, name=name) else: - index = _generate_regular_range(start, end, periods, freq) + index = _generate_regular_range(cls, start, end, + periods, freq) if tz is not None and getattr(index, 'tz', None) is None: arr = conversion.tz_localize_to_utc(_ensure_int64(index), tz, ambiguous=ambiguous) - index = DatetimeIndex(arr) + index = cls(arr) # index is localized datetime64 array -> have to convert # start/end as well to compare @@ -1719,7 +1720,7 @@ def to_julian_date(self): DatetimeIndex._add_datetimelike_methods() -def _generate_regular_range(start, end, periods, freq): +def _generate_regular_range(cls, start, end, periods, freq): if isinstance(freq, Tick): stride = freq.nanos if periods is None: @@ -1743,7 +1744,8 @@ def _generate_regular_range(start, end, periods, freq): "if a 'period' is given.") data = np.arange(b, e, stride, dtype=np.int64) - data = DatetimeIndex._simple_new(data.view(_NS_DTYPE), None, tz=tz) + # TODO: Do we need to use _simple_new here? just return data.view? + data = cls._simple_new(data.view(_NS_DTYPE), None, tz=tz) else: if isinstance(start, Timestamp): start = start.to_pydatetime() From 6da6da385804140c43705a9f9075a82e75125bdd Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Wed, 11 Jul 2018 20:12:16 -0700 Subject: [PATCH 10/16] rename _generate --> _generate_range as requested --- pandas/core/arrays/timedeltas.py | 6 +++--- pandas/core/indexes/datetimes.py | 10 +++++----- pandas/core/indexes/timedeltas.py | 13 +++++++------ 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/pandas/core/arrays/timedeltas.py b/pandas/core/arrays/timedeltas.py index 3c1cbe77fcd65..2deb618e1447b 100644 --- a/pandas/core/arrays/timedeltas.py +++ b/pandas/core/arrays/timedeltas.py @@ -138,8 +138,8 @@ def __new__(cls, values, freq=None, start=None, end=None, periods=None, raise ValueError('Must provide freq argument if no data is ' 'supplied') else: - return cls._generate(start, end, periods, freq, - closed=closed) + return cls._generate_range(start, end, periods, freq, + closed=closed) result = cls._simple_new(values, freq=freq) if freq == 'infer': @@ -150,7 +150,7 @@ def __new__(cls, values, freq=None, start=None, end=None, periods=None, return result @classmethod - def _generate(cls, start, end, periods, freq, closed=None, **kwargs): + def _generate_range(cls, start, end, periods, freq, closed=None, **kwargs): # **kwargs are for compat with TimedeltaIndex, which includes `name` if com._count_not_none(start, end, periods, freq) != 3: raise ValueError('Of the four parameters: start, end, periods, ' diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index 4fa90db26f0fb..4732178d552be 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -319,9 +319,9 @@ def __new__(cls, data=None, msg = 'Must provide freq argument if no data is supplied' raise ValueError(msg) else: - return cls._generate(start, end, periods, name, freq, tz=tz, - normalize=normalize, closed=closed, - ambiguous=ambiguous) + return cls._generate_range(start, end, periods, name, freq, + tz=tz, normalize=normalize, + closed=closed, ambiguous=ambiguous) if not isinstance(data, (np.ndarray, Index, ABCSeries)): if is_scalar(data): @@ -393,8 +393,8 @@ def __new__(cls, data=None, return subarr._deepcopy_if_needed(ref_to_data, copy) @classmethod - def _generate(cls, start, end, periods, name, freq, - tz=None, normalize=False, ambiguous='raise', closed=None): + def _generate_range(cls, start, end, periods, name, freq, tz=None, + normalize=False, ambiguous='raise', closed=None): if com._count_not_none(start, end, periods, freq) != 3: raise ValueError('Of the four parameters: start, end, periods, ' 'and freq, exactly three must be specified') diff --git a/pandas/core/indexes/timedeltas.py b/pandas/core/indexes/timedeltas.py index 2c36b3f18041d..1ed6145f01a44 100644 --- a/pandas/core/indexes/timedeltas.py +++ b/pandas/core/indexes/timedeltas.py @@ -198,8 +198,8 @@ def __new__(cls, data=None, unit=None, freq=None, start=None, end=None, msg = 'Must provide freq argument if no data is supplied' raise ValueError(msg) else: - return cls._generate(start, end, periods, name, freq, - closed=closed) + return cls._generate_range(start, end, periods, name, freq, + closed=closed) if unit is not None: data = to_timedelta(data, unit=unit, box=False) @@ -231,12 +231,13 @@ def __new__(cls, data=None, unit=None, freq=None, start=None, end=None, return subarr @classmethod - def _generate(cls, start, end, periods, name, freq, closed=None): + def _generate_range(cls, start, end, periods, name, freq, closed=None): # TimedeltaArray gets `name` via **kwargs, so we need to explicitly # override it if name is passed as a positional argument - return super(TimedeltaIndex, cls)._generate(start, end, - periods, freq, - name=name, closed=closed) + return super(TimedeltaIndex, cls)._generate_range(start, end, + periods, freq, + name=name, + closed=closed) @classmethod def _simple_new(cls, values, name=None, freq=None, **kwargs): From 5d142c908e9ec14a65b4438a453280b7d5318748 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Wed, 11 Jul 2018 21:28:14 -0700 Subject: [PATCH 11/16] update name --- pandas/core/indexes/datetimelike.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index c033be16a6176..3f0bdf18f7230 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -223,7 +223,7 @@ def _validate_frequency(cls, index, freq, **kwargs): if index.empty or inferred == freq.freqstr: return None - on_freq = cls._generate( + on_freq = cls._generate_range( index[0], None, len(index), None, freq, **kwargs) if not np.array_equal(index.asi8, on_freq.asi8): msg = ('Inferred frequency {infer} from passed values does not ' From 0af98760ea5a5e9c5e9d2edd5fe0dd5ba8f266f9 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Thu, 12 Jul 2018 08:59:41 -0700 Subject: [PATCH 12/16] docstring for validate_periods --- pandas/core/arrays/datetimelike.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index 7b28fb6404812..ec430e4bf17b1 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -527,6 +527,23 @@ def _add_comparison_methods(cls): # Shared Constructor Helpers def validate_periods(periods): + """ + If a `periods` argument is passed to the Datetime/Timedelta Array/Index + constructor, cast it to an integer. + + Parameters + ---------- + periods : None, float, int + + Returns + ------- + periods : None or int + + Raises + ------ + TypeError + if periods is None, float, or int + """ if periods is not None: if lib.is_float(periods): periods = int(periods) From 1044d23c3d475c452293464fa31981dc27b68c7e Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Thu, 12 Jul 2018 09:03:38 -0700 Subject: [PATCH 13/16] make imports absolute --- pandas/core/arrays/datetimes.py | 2 +- pandas/core/arrays/period.py | 2 +- pandas/core/arrays/timedeltas.py | 7 +++---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index b80e0d36a844e..41225a9d0caab 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -32,7 +32,7 @@ from pandas.tseries.frequencies import to_offset, DateOffset from pandas.tseries.offsets import Tick -from .datetimelike import DatetimeLikeArrayMixin +from pandas.core.arrays.datetimelike import DatetimeLikeArrayMixin def _to_m8(key, tz=None): diff --git a/pandas/core/arrays/period.py b/pandas/core/arrays/period.py index ef86c3205fd3c..66b1fb8db25c0 100644 --- a/pandas/core/arrays/period.py +++ b/pandas/core/arrays/period.py @@ -26,7 +26,7 @@ from pandas.tseries import frequencies from pandas.tseries.offsets import Tick, DateOffset -from .datetimelike import DatetimeLikeArrayMixin +from pandas.core.arrays.datetimelike import DatetimeLikeArrayMixin def _field_accessor(name, alias, docstring=None): diff --git a/pandas/core/arrays/timedeltas.py b/pandas/core/arrays/timedeltas.py index 2deb618e1447b..a2bcd503fc308 100644 --- a/pandas/core/arrays/timedeltas.py +++ b/pandas/core/arrays/timedeltas.py @@ -20,8 +20,7 @@ from pandas.tseries.offsets import Tick, DateOffset from pandas.tseries.frequencies import to_offset -from .datetimelike import DatetimeLikeArrayMixin -from . import datetimelike as dtl +import pandas.core.arrays.datetimelike as dtl def _to_m8(key): @@ -63,7 +62,7 @@ def _td_array_cmp(opname, cls): def wrapper(self, other): msg = "cannot compare a {cls} with type {typ}" - meth = getattr(DatetimeLikeArrayMixin, opname) + meth = getattr(dtl.DatetimeLikeArrayMixin, opname) if _is_convertible_to_td(other) or other is NaT: try: other = _to_m8(other) @@ -95,7 +94,7 @@ def wrapper(self, other): return compat.set_function_name(wrapper, opname, cls) -class TimedeltaArrayMixin(DatetimeLikeArrayMixin): +class TimedeltaArrayMixin(dtl.DatetimeLikeArrayMixin): @property def _box_func(self): return lambda x: Timedelta(x, unit='ns') From 89e92b8dd73ab188accbb22b30523f2b871ef2c3 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Thu, 12 Jul 2018 09:07:17 -0700 Subject: [PATCH 14/16] make quarter non-None required in quarter_to_myear --- pandas/_libs/tslibs/period.pyx | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/pandas/_libs/tslibs/period.pyx b/pandas/_libs/tslibs/period.pyx index 45dcb1664fef2..ef4a10b8e459d 100644 --- a/pandas/_libs/tslibs/period.pyx +++ b/pandas/_libs/tslibs/period.pyx @@ -1866,7 +1866,7 @@ cdef int64_t _ordinal_from_fields(year, month, quarter, day, minute, second, 0, 0, base) -def quarter_to_myear(year, quarter, freq): +def quarter_to_myear(int year, int quarter, freq): """ A quarterly frequency defines a "year" which may not coincide with the calendar-year. Find the calendar-year and calendar-month associated @@ -1887,18 +1887,15 @@ def quarter_to_myear(year, quarter, freq): -------- Period.qyear """ - if quarter is not None: - if quarter <= 0 or quarter > 4: - raise ValueError('Quarter must be 1 <= q <= 4') + if quarter <= 0 or quarter > 4: + raise ValueError('Quarter must be 1 <= q <= 4') - mnum = MONTH_NUMBERS[get_rule_month(freq)] + 1 - month = (mnum + (quarter - 1) * 3) % 12 + 1 - if month > mnum: - year -= 1 + mnum = MONTH_NUMBERS[get_rule_month(freq)] + 1 + month = (mnum + (quarter - 1) * 3) % 12 + 1 + if month > mnum: + year -= 1 return year, month - # FIXME: if quarter is None then `month` won't be defined here. - # just disallow quarter == None? def _validate_end_alias(how): From 1bcdb8f9b31899d4671b279f93f4ae3562e74a7e Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Thu, 12 Jul 2018 09:26:36 -0700 Subject: [PATCH 15/16] make import relative to fix import error --- pandas/core/arrays/timedeltas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/arrays/timedeltas.py b/pandas/core/arrays/timedeltas.py index a2bcd503fc308..f027b84506164 100644 --- a/pandas/core/arrays/timedeltas.py +++ b/pandas/core/arrays/timedeltas.py @@ -20,7 +20,7 @@ from pandas.tseries.offsets import Tick, DateOffset from pandas.tseries.frequencies import to_offset -import pandas.core.arrays.datetimelike as dtl +from . import datetimelike as dtl def _to_m8(key): From 935ac7b2b48a98ead0e2140ce3a681649184a18a Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Thu, 12 Jul 2018 12:19:16 -0700 Subject: [PATCH 16/16] dummy commit to forice CI --- pandas/core/arrays/datetimes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index 41225a9d0caab..5835090e25de1 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -32,7 +32,7 @@ from pandas.tseries.frequencies import to_offset, DateOffset from pandas.tseries.offsets import Tick -from pandas.core.arrays.datetimelike import DatetimeLikeArrayMixin +from pandas.core.arrays import datetimelike as dtl def _to_m8(key, tz=None): @@ -91,7 +91,7 @@ def _dt_array_cmp(opname, cls): nat_result = True if opname == '__ne__' else False def wrapper(self, other): - meth = getattr(DatetimeLikeArrayMixin, opname) + meth = getattr(dtl.DatetimeLikeArrayMixin, opname) if isinstance(other, (datetime, np.datetime64, compat.string_types)): if isinstance(other, datetime): @@ -135,7 +135,7 @@ def wrapper(self, other): return compat.set_function_name(wrapper, opname, cls) -class DatetimeArrayMixin(DatetimeLikeArrayMixin): +class DatetimeArrayMixin(dtl.DatetimeLikeArrayMixin): """ Assumes that subclass __new__/__init__ defines: tz