From 7cd2679982138e1e7f9f1a759827647e6e521d43 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Mon, 2 Jul 2018 16:58:47 -0700 Subject: [PATCH] ENH: implement DatetimeLikeArray (#19902) --- pandas/core/arrays/__init__.py | 3 + pandas/core/arrays/datetimelike.py | 169 ++++++++++++++++++++++++++++ pandas/core/arrays/datetimes.py | 110 ++++++++++++++++++ pandas/core/arrays/period.py | 28 +++++ pandas/core/arrays/timedelta.py | 17 +++ pandas/core/indexes/datetimelike.py | 129 ++------------------- pandas/core/indexes/datetimes.py | 86 +------------- pandas/core/indexes/period.py | 22 +--- pandas/core/indexes/timedeltas.py | 12 +- 9 files changed, 347 insertions(+), 229 deletions(-) create mode 100644 pandas/core/arrays/datetimelike.py create mode 100644 pandas/core/arrays/datetimes.py create mode 100644 pandas/core/arrays/period.py create mode 100644 pandas/core/arrays/timedelta.py diff --git a/pandas/core/arrays/__init__.py b/pandas/core/arrays/__init__.py index f57348116c195..1b8a43d4293a5 100644 --- a/pandas/core/arrays/__init__.py +++ b/pandas/core/arrays/__init__.py @@ -1,3 +1,6 @@ from .base import (ExtensionArray, # noqa ExtensionScalarOpsMixin) from .categorical import Categorical # noqa +from .datetimes import DatetimeArrayMixin # noqa +from .period import PeriodArrayMixin # noqa +from .timedelta import TimedeltaArrayMixin # noqa diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py new file mode 100644 index 0000000000000..180417ce5e49c --- /dev/null +++ b/pandas/core/arrays/datetimelike.py @@ -0,0 +1,169 @@ +# -*- coding: utf-8 -*- + +import numpy as np + +from pandas._libs import iNaT +from pandas._libs.tslibs.timedeltas import delta_to_nanoseconds + +from pandas.tseries import frequencies + +import pandas.core.common as com +from pandas.core.algorithms import checked_add_with_arr + + +class DatetimeLikeArrayMixin(object): + """ + Shared Base/Mixin class for DatetimeArray, TimedeltaArray, PeriodArray + + Assumes that __new__/__init__ defines: + _data + _freq + + and that the inheriting class has methods: + _validate_frequency + """ + + @property + def _box_func(self): + """ + box function to get object from internal representation + """ + raise com.AbstractMethodError(self) + + def __iter__(self): + return (self._box_func(v) for v in self.asi8) + + @property + def values(self): + """ return the underlying data as an ndarray """ + return self._data.view(np.ndarray) + + @property + def asi8(self): + # do not cache or you'll create a memory leak + return self.values.view('i8') + + # ------------------------------------------------------------------ + # Null Handling + + @property # NB: override with cache_readonly in immutable subclasses + def _isnan(self): + """ return if each value is nan""" + return (self.asi8 == iNaT) + + @property # NB: override with cache_readonly in immutable subclasses + def hasnans(self): + """ return if I have any nans; enables various perf speedups """ + return self._isnan.any() + + def _maybe_mask_results(self, result, fill_value=None, convert=None): + """ + Parameters + ---------- + result : a ndarray + convert : string/dtype or None + + Returns + ------- + result : ndarray with values replace by the fill_value + + mask the result if needed, convert to the provided dtype if its not + None + + This is an internal routine + """ + + if self.hasnans: + if convert: + result = result.astype(convert) + if fill_value is None: + fill_value = np.nan + result[self._isnan] = fill_value + return result + + # ------------------------------------------------------------------ + # Frequency Properties/Methods + + @property + def freq(self): + """Return the frequency object if it is set, otherwise None""" + return self._freq + + @freq.setter + def freq(self, value): + if value is not None: + value = frequencies.to_offset(value) + self._validate_frequency(self, value) + + self._freq = value + + @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 + + @property # NB: override with cache_readonly in immutable subclasses + def inferred_freq(self): + """ + Tryies to return a string representing a frequency guess, + generated by infer_freq. Returns None if it can't autodetect the + frequency. + """ + try: + return frequencies.infer_freq(self) + except ValueError: + return None + + # ------------------------------------------------------------------ + # Arithmetic Methods + + def _add_datelike(self, other): + raise TypeError("cannot add {cls} and {typ}" + .format(cls=type(self).__name__, + typ=type(other).__name__)) + + def _sub_datelike(self, other): + raise com.AbstractMethodError(self) + + def _sub_period(self, other): + return NotImplemented + + def _add_offset(self, offset): + raise com.AbstractMethodError(self) + + def _add_delta(self, other): + return NotImplemented + + def _add_delta_td(self, other): + """ + Add a delta of a timedeltalike + return the i8 result view + """ + inc = delta_to_nanoseconds(other) + new_values = checked_add_with_arr(self.asi8, inc, + arr_mask=self._isnan).view('i8') + if self.hasnans: + new_values[self._isnan] = iNaT + return new_values.view('i8') + + def _add_delta_tdi(self, other): + """ + Add a delta of a TimedeltaIndex + return the i8 result view + """ + if not len(self) == len(other): + raise ValueError("cannot add indices of unequal length") + + self_i8 = self.asi8 + other_i8 = other.asi8 + new_values = checked_add_with_arr(self_i8, other_i8, + arr_mask=self._isnan, + b_mask=other._isnan) + if self.hasnans or other.hasnans: + mask = (self._isnan) | (other._isnan) + new_values[mask] = iNaT + return new_values.view('i8') diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py new file mode 100644 index 0000000000000..fb51f3324c5ea --- /dev/null +++ b/pandas/core/arrays/datetimes.py @@ -0,0 +1,110 @@ +# -*- coding: utf-8 -*- +import warnings + +import numpy as np + +from pandas._libs.tslib import Timestamp, NaT, iNaT +from pandas._libs.tslibs import timezones + +from pandas.util._decorators import cache_readonly + +from pandas.core.dtypes.common import _NS_DTYPE, is_datetime64tz_dtype +from pandas.core.dtypes.dtypes import DatetimeTZDtype + +from .datetimelike import DatetimeLikeArrayMixin + + +class DatetimeArrayMixin(DatetimeLikeArrayMixin): + """ + Assumes that subclass __new__/__init__ defines: + tz + _freq + _data + """ + + # ----------------------------------------------------------------- + # Descriptive Properties + + @property + def _box_func(self): + return lambda x: Timestamp(x, freq=self.freq, tz=self.tz) + + @cache_readonly + def dtype(self): + if self.tz is None: + return _NS_DTYPE + return DatetimeTZDtype('ns', self.tz) + + @property + def tzinfo(self): + """ + Alias for tz attribute + """ + return self.tz + + @property # NB: override with cache_readonly in immutable subclasses + def _timezone(self): + """ Comparable timezone both for pytz / dateutil""" + return timezones.get_timezone(self.tzinfo) + + @property + def offset(self): + """get/set the frequency of the instance""" + msg = ('DatetimeIndex.offset has been deprecated and will be removed ' + 'in a future version; use DatetimeIndex.freq instead.') + warnings.warn(msg, FutureWarning, stacklevel=2) + return self.freq + + @offset.setter + def offset(self, value): + """get/set the frequency of the instance""" + msg = ('DatetimeIndex.offset has been deprecated and will be removed ' + 'in a future version; use DatetimeIndex.freq instead.') + warnings.warn(msg, FutureWarning, stacklevel=2) + self.freq = value + + # ----------------------------------------------------------------- + # Comparison Methods + + def _has_same_tz(self, other): + zzone = self._timezone + + # vzone sholdn't be None if value is non-datetime like + if isinstance(other, np.datetime64): + # convert to Timestamp as np.datetime64 doesn't have tz attr + other = Timestamp(other) + vzone = timezones.get_timezone(getattr(other, 'tzinfo', '__no_tz__')) + return zzone == vzone + + def _assert_tzawareness_compat(self, other): + # adapted from _Timestamp._assert_tzawareness_compat + other_tz = getattr(other, 'tzinfo', None) + if is_datetime64tz_dtype(other): + # Get tzinfo from Series dtype + other_tz = other.dtype.tz + if other is NaT: + # pd.NaT quacks both aware and naive + pass + elif self.tz is None: + if other_tz is not None: + raise TypeError('Cannot compare tz-naive and tz-aware ' + 'datetime-like objects.') + elif other_tz is None: + raise TypeError('Cannot compare tz-naive and tz-aware ' + 'datetime-like objects') + + # ----------------------------------------------------------------- + # Arithmetic Methods + + def _sub_datelike_dti(self, other): + """subtraction of two DatetimeIndexes""" + if not len(self) == len(other): + raise ValueError("cannot add indices of unequal length") + + self_i8 = self.asi8 + other_i8 = other.asi8 + new_values = self_i8 - other_i8 + if self.hasnans or other.hasnans: + mask = (self._isnan) | (other._isnan) + new_values[mask] = iNaT + return new_values.view('timedelta64[ns]') diff --git a/pandas/core/arrays/period.py b/pandas/core/arrays/period.py new file mode 100644 index 0000000000000..1158bae748f0a --- /dev/null +++ b/pandas/core/arrays/period.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- + +from pandas._libs.tslibs.period import Period + +from pandas.util._decorators import cache_readonly + +from pandas.core.dtypes.dtypes import PeriodDtype + +from .datetimelike import DatetimeLikeArrayMixin + + +class PeriodArrayMixin(DatetimeLikeArrayMixin): + @property + def _box_func(self): + return lambda x: Period._from_ordinal(ordinal=x, freq=self.freq) + + @cache_readonly + def dtype(self): + return PeriodDtype.construct_from_string(self.freq) + + @property + def _ndarray_values(self): + # Ordinals + return self._data + + @property + def asi8(self): + return self._ndarray_values.view('i8') diff --git a/pandas/core/arrays/timedelta.py b/pandas/core/arrays/timedelta.py new file mode 100644 index 0000000000000..487858c49b66a --- /dev/null +++ b/pandas/core/arrays/timedelta.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- + +from pandas._libs.tslib import Timedelta + +from pandas.core.dtypes.common import _TD_DTYPE + +from .datetimelike import DatetimeLikeArrayMixin + + +class TimedeltaArrayMixin(DatetimeLikeArrayMixin): + @property + def _box_func(self): + return lambda x: Timedelta(x, unit='ns') + + @property + def dtype(self): + return _TD_DTYPE diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index 5afa99d7b6fe8..328c51aae1807 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -15,7 +15,6 @@ from pandas._libs import lib, iNaT, NaT, Timedelta from pandas._libs.tslibs.period import (Period, IncompatibleFrequency, _DIFFERENT_FREQ_INDEX) -from pandas._libs.tslibs.timedeltas import delta_to_nanoseconds from pandas._libs.tslibs.timestamps import round_ns from pandas.core.dtypes.common import ( @@ -46,6 +45,7 @@ from pandas.errors import NullFrequencyError, PerformanceWarning import pandas.io.formats.printing as printing +from pandas.core.arrays.datetimelike import DatetimeLikeArrayMixin from pandas.core.indexes.base import Index, _index_shared_docs from pandas.util._decorators import Appender, cache_readonly import pandas.core.dtypes.concat as _concat @@ -231,23 +231,17 @@ def _validate_frequency(cls, index, freq, **kwargs): 'conform to passed frequency {passed}') raise ValueError(msg.format(infer=inferred, passed=freq.freqstr)) - @property - def freq(self): - """Return the frequency object if it is set, otherwise None""" - return self._freq - - @freq.setter - def freq(self, value): - if value is not None: - value = frequencies.to_offset(value) - self._validate_frequency(self, value) - - self._freq = value - -class DatetimeIndexOpsMixin(object): +class DatetimeIndexOpsMixin(DatetimeLikeArrayMixin): """ common ops mixin to support a unified interface datetimelike Index """ + # DatetimeLikeArrayMixin assumes subclasses are mutable, so these are + # properties there. They can be made into cache_readonly for Index + # subclasses bc they are immutable + inferred_freq = cache_readonly(DatetimeLikeArrayMixin.inferred_freq.fget) + _isnan = cache_readonly(DatetimeLikeArrayMixin._isnan.fget) + hasnans = cache_readonly(DatetimeLikeArrayMixin.hasnans.fget) + def equals(self, other): """ Determines if two Index objects contain the same elements. @@ -276,9 +270,6 @@ def equals(self, other): return np.array_equal(self.asi8, other.asi8) - def __iter__(self): - return (self._box_func(v) for v in self.asi8) - @staticmethod def _join_i8_wrapper(joinf, dtype, with_indexers=True): """ create the join wrapper methods """ @@ -354,13 +345,6 @@ def _ensure_localized(self, result): result = result.tz_localize(self.tz) return result - @property - def _box_func(self): - """ - box function to get object from internal representation - """ - raise com.AbstractMethodError(self) - def _box_values(self, values): """ apply box func to passed values @@ -437,27 +421,6 @@ def __getitem__(self, key): return self._simple_new(result, **attribs) - @property - def freqstr(self): - """ - Return the frequency object as a string if it is set, otherwise None - """ - if self.freq is None: - return None - return self.freq.freqstr - - @cache_readonly - def inferred_freq(self): - """ - Tries to return a string representing a frequency guess, - generated by infer_freq. Returns None if it can't autodetect the - frequency. - """ - try: - return frequencies.infer_freq(self) - except ValueError: - return None - def _nat_new(self, box=True): """ Return Index or ndarray filled with NaT which has the same @@ -546,11 +509,6 @@ def take(self, indices, axis=0, allow_fill=True, _na_value = NaT """The expected NA value to use with this index.""" - @cache_readonly - def _isnan(self): - """ return if each value is nan""" - return (self.asi8 == iNaT) - @property def asobject(self): """Return object Index which contains boxed values. @@ -571,31 +529,6 @@ def _convert_tolerance(self, tolerance, target): 'target index size') return tolerance - def _maybe_mask_results(self, result, fill_value=None, convert=None): - """ - Parameters - ---------- - result : a ndarray - convert : string/dtype or None - - Returns - ------- - result : ndarray with values replace by the fill_value - - mask the result if needed, convert to the provided dtype if its not - None - - This is an internal routine - """ - - if self.hasnans: - if convert: - result = result.astype(convert) - if fill_value is None: - fill_value = np.nan - result[self._isnan] = fill_value - return result - def tolist(self): """ return a list of the underlying data @@ -752,14 +685,6 @@ def _convert_scalar_indexer(self, key, kind=None): return (super(DatetimeIndexOpsMixin, self) ._convert_scalar_indexer(key, kind=kind)) - def _add_datelike(self, other): - raise TypeError("cannot add {cls} and {typ}" - .format(cls=type(self).__name__, - typ=type(other).__name__)) - - def _sub_datelike(self, other): - raise com.AbstractMethodError(self) - def _add_nat(self): """Add pd.NaT to self""" if is_period_dtype(self): @@ -1048,42 +973,6 @@ def __isub__(self, other): return self.__sub__(other) cls.__isub__ = __isub__ - def _add_delta(self, other): - return NotImplemented - - def _add_delta_td(self, other): - """ - Add a delta of a timedeltalike - return the i8 result view - """ - - inc = delta_to_nanoseconds(other) - new_values = checked_add_with_arr(self.asi8, inc, - arr_mask=self._isnan).view('i8') - if self.hasnans: - new_values[self._isnan] = iNaT - return new_values.view('i8') - - def _add_delta_tdi(self, other): - """ - Add a delta of a TimedeltaIndex - return the i8 result view - """ - - # delta operation - if not len(self) == len(other): - raise ValueError("cannot add indices of unequal length") - - self_i8 = self.asi8 - other_i8 = other.asi8 - new_values = checked_add_with_arr(self_i8, other_i8, - arr_mask=self._isnan, - b_mask=other._isnan) - if self.hasnans or other.hasnans: - mask = (self._isnan) | (other._isnan) - new_values[mask] = iNaT - return new_values.view('i8') - def isin(self, values): """ Compute boolean array of whether each index value is found in the diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index 9515d41080f87..7475998909ec2 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -13,7 +13,7 @@ _INT64_DTYPE, _NS_DTYPE, is_object_dtype, - is_datetime64_dtype, is_datetime64tz_dtype, + is_datetime64_dtype, is_datetimetz, is_dtype_equal, is_timedelta64_dtype, @@ -35,6 +35,7 @@ import pandas.core.dtypes.concat as _concat from pandas.errors import PerformanceWarning from pandas.core.algorithms import checked_add_with_arr +from pandas.core.arrays.datetimes import DatetimeArrayMixin from pandas.core.indexes.base import Index, _index_shared_docs from pandas.core.indexes.numeric import Int64Index, Float64Index @@ -173,8 +174,8 @@ def _new_DatetimeIndex(cls, d): return result -class DatetimeIndex(DatelikeOps, TimelikeOps, DatetimeIndexOpsMixin, - Int64Index): +class DatetimeIndex(DatetimeArrayMixin, DatelikeOps, TimelikeOps, + DatetimeIndexOpsMixin, Int64Index): """ Immutable ndarray of datetime64 data, represented internally as int64, and which can be boxed to Timestamp objects that are subclasses of datetime and @@ -329,6 +330,7 @@ def _add_comparison_methods(cls): _is_numeric_dtype = False _infer_as_myclass = True + _timezone = cache_readonly(DatetimeArrayMixin._timezone.fget) def __new__(cls, data=None, freq=None, start=None, end=None, periods=None, tz=None, @@ -591,10 +593,6 @@ def _generate(cls, start, end, periods, name, freq, index = cls._simple_new(index, name=name, freq=freq, tz=tz) return index - @property - def _box_func(self): - return lambda x: Timestamp(x, freq=self.freq, tz=self.tz) - def _convert_for_op(self, value): """ Convert value to be insertable to ndarray """ if self._has_same_tz(value): @@ -645,23 +643,6 @@ def _simple_new(cls, values, name=None, freq=None, tz=None, result._reset_identity() return result - def _assert_tzawareness_compat(self, other): - # adapted from _Timestamp._assert_tzawareness_compat - other_tz = getattr(other, 'tzinfo', None) - if is_datetime64tz_dtype(other): - # Get tzinfo from Series dtype - other_tz = other.dtype.tz - if other is libts.NaT: - # pd.NaT quacks both aware and naive - pass - elif self.tz is None: - if other_tz is not None: - raise TypeError('Cannot compare tz-naive and tz-aware ' - 'datetime-like objects.') - elif other_tz is None: - raise TypeError('Cannot compare tz-naive and tz-aware ' - 'datetime-like objects') - @property def _values(self): # tz-naive -> ndarray @@ -682,13 +663,6 @@ def tz(self, value): raise AttributeError("Cannot directly set timezone. Use tz_localize() " "or tz_convert() as appropriate") - @property - def tzinfo(self): - """ - Alias for tz attribute - """ - return self.tz - @property def size(self): # TODO: Remove this when we have a DatetimeTZArray @@ -710,21 +684,6 @@ def nbytes(self): # for TZ-aware return self._ndarray_values.nbytes - @cache_readonly - def _timezone(self): - """ Comparable timezone both for pytz / dateutil""" - return timezones.get_timezone(self.tzinfo) - - def _has_same_tz(self, other): - zzone = self._timezone - - # vzone sholdn't be None if value is non-datetime like - if isinstance(other, np.datetime64): - # convert to Timestamp as np.datetime64 doesn't have tz attr - other = Timestamp(other) - vzone = timezones.get_timezone(getattr(other, 'tzinfo', '__no_tz__')) - return zzone == vzone - @classmethod def _cached_range(cls, start=None, end=None, periods=None, freq=None, name=None): @@ -880,19 +839,6 @@ def _sub_datelike(self, other): typ=type(other).__name__)) return result.view('timedelta64[ns]') - def _sub_datelike_dti(self, other): - """subtraction of two DatetimeIndexes""" - if not len(self) == len(other): - raise ValueError("cannot add indices of unequal length") - - self_i8 = self.asi8 - other_i8 = other.asi8 - new_values = self_i8 - other_i8 - if self.hasnans or other.hasnans: - mask = (self._isnan) | (other._isnan) - new_values[mask] = libts.iNaT - return new_values.view('timedelta64[ns]') - def _maybe_update_attributes(self, attrs): """ Update Index attributes (e.g. freq) depending on op """ freq = attrs.get('freq', None) @@ -1720,22 +1666,6 @@ def slice_indexer(self, start=None, end=None, step=None, kind=None): else: raise - @property - def offset(self): - """get/set the frequency of the Index""" - msg = ('DatetimeIndex.offset has been deprecated and will be removed ' - 'in a future version; use DatetimeIndex.freq instead.') - warnings.warn(msg, FutureWarning, stacklevel=2) - return self.freq - - @offset.setter - def offset(self, value): - """get/set the frequency of the Index""" - msg = ('DatetimeIndex.offset has been deprecated and will be removed ' - 'in a future version; use DatetimeIndex.freq instead.') - warnings.warn(msg, FutureWarning, stacklevel=2) - self.freq = value - year = _field_accessor('year', 'Y', "The year of the datetime") month = _field_accessor('month', 'M', "The month as January=1, December=12") @@ -2114,12 +2044,6 @@ def inferred_type(self): # sure we can't have ambiguous indexing return 'datetime64' - @cache_readonly - def dtype(self): - if self.tz is None: - return _NS_DTYPE - return DatetimeTZDtype('ns', self.tz) - @property def is_all_dates(self): return True diff --git a/pandas/core/indexes/period.py b/pandas/core/indexes/period.py index d4d35d48743bd..1257266025c03 100644 --- a/pandas/core/indexes/period.py +++ b/pandas/core/indexes/period.py @@ -16,7 +16,6 @@ is_bool_dtype, pandas_dtype, _ensure_object) -from pandas.core.dtypes.dtypes import PeriodDtype from pandas.core.dtypes.generic import ABCSeries import pandas.tseries.frequencies as frequencies @@ -36,6 +35,7 @@ from pandas._libs.tslibs import resolution, period from pandas._libs.tslibs.timedeltas import delta_to_nanoseconds +from pandas.core.arrays.period import PeriodArrayMixin from pandas.core.base import _shared_docs from pandas.core.indexes.base import _index_shared_docs, _ensure_index @@ -122,7 +122,8 @@ def _new_PeriodIndex(cls, **d): return cls._from_ordinals(values=values, **d) -class PeriodIndex(DatelikeOps, DatetimeIndexOpsMixin, Int64Index): +class PeriodIndex(PeriodArrayMixin, DatelikeOps, DatetimeIndexOpsMixin, + Int64Index): """ Immutable ndarray holding ordinal values indicating regular periods in time such as particular years, quarters, months, etc. @@ -410,10 +411,6 @@ def __contains__(self, key): contains = __contains__ - @property - def asi8(self): - return self._ndarray_values.view('i8') - @cache_readonly def _int64index(self): return Int64Index(self.asi8, name=self.name, fastpath=True) @@ -422,11 +419,6 @@ def _int64index(self): def values(self): return self.astype(object).values - @property - def _ndarray_values(self): - # Ordinals - return self._data - def __array__(self, dtype=None): if is_integer_dtype(dtype): return self.asi8 @@ -467,10 +459,6 @@ def __array_wrap__(self, result, context=None): # cannot pass _simple_new as it is return self._shallow_copy(result, freq=self.freq, name=self.name) - @property - def _box_func(self): - return lambda x: Period._from_ordinal(ordinal=x, freq=self.freq) - def _to_embed(self, keep_tz=False, dtype=None): """ return an array repr of this object, potentially casting to object @@ -795,10 +783,6 @@ def shift(self, n): values[self._isnan] = tslib.iNaT return self._shallow_copy(values=values) - @cache_readonly - def dtype(self): - return PeriodDtype.construct_from_string(self.freq) - @property def inferred_type(self): # b/c data is represented as ints make sure we can't have ambiguous diff --git a/pandas/core/indexes/timedeltas.py b/pandas/core/indexes/timedeltas.py index e90e1264638b0..8b62d9aa631e9 100644 --- a/pandas/core/indexes/timedeltas.py +++ b/pandas/core/indexes/timedeltas.py @@ -17,6 +17,7 @@ from pandas.core.dtypes.missing import isna from pandas.core.dtypes.generic import ABCSeries +from pandas.core.arrays.timedelta import TimedeltaArrayMixin from pandas.core.indexes.base import Index from pandas.core.indexes.numeric import Int64Index import pandas.compat as compat @@ -96,7 +97,8 @@ def wrapper(self, other): return compat.set_function_name(wrapper, opname, cls) -class TimedeltaIndex(DatetimeIndexOpsMixin, TimelikeOps, Int64Index): +class TimedeltaIndex(TimedeltaArrayMixin, DatetimeIndexOpsMixin, + TimelikeOps, Int64Index): """ Immutable ndarray of timedelta64 data, represented internally as int64, and which can be boxed to timedelta objects @@ -311,10 +313,6 @@ def _generate(cls, start, end, periods, name, freq, closed=None): return index - @property - def _box_func(self): - return lambda x: Timedelta(x, unit='ns') - @classmethod def _simple_new(cls, values, name=None, freq=None, **kwargs): values = np.array(values, copy=False) @@ -906,10 +904,6 @@ def is_type_compatible(self, typ): def inferred_type(self): return 'timedelta64' - @property - def dtype(self): - return _TD_DTYPE - @property def is_all_dates(self): return True