diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index 0dedd8fe1cf4b..db1369fe41911 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -2,6 +2,7 @@ """ Base and utility classes for tseries type pandas objects. """ +import operator import warnings import numpy as np @@ -19,6 +20,7 @@ from pandas.core.dtypes.generic import ABCIndex, ABCIndexClass, ABCSeries from pandas.core import algorithms, ops +from pandas.core.accessor import PandasDelegate from pandas.core.arrays.datetimelike import ( DatetimeLikeArrayMixin, _ensure_datetimelike_to_i8) import pandas.core.indexes.base as ibase @@ -637,3 +639,48 @@ def f(self): f.__name__ = fget.__name__ f.__doc__ = fget.__doc__ return property(f) + + +class DatetimelikeDelegateMixin(PandasDelegate): + """ + Delegation mechanism, specific for Datetime, Timedelta, and Period types. + + Functionality is delegated from the Index class to an Array class. A + few things can be customized + + * _delegate_class : type + The class being delegated to. + * _delegated_methods, delegated_properties : List + The list of property / method names being delagated. + * raw_methods : Set + The set of methods whose results should should *not* be + boxed in an index, after being returned from the array + * raw_properties : Set + The set of properties whose results should should *not* be + boxed in an index, after being returned from the array + """ + # raw_methods : dispatch methods that shouldn't be boxed in an Index + _raw_methods = set() + # raw_properties : dispatch properties that shouldn't be boxed in an Index + _raw_properties = set() + name = None + _data = None + + @property + def _delegate_class(self): + raise AbstractMethodError + + def _delegate_property_get(self, name, *args, **kwargs): + result = getattr(self._data, name) + if name not in self._raw_properties: + result = Index(result, name=self.name) + return result + + def _delegate_property_set(self, name, value, *args, **kwargs): + setattr(self._data, name, value) + + def _delegate_method(self, name, *args, **kwargs): + result = operator.methodcaller(name, *args, **kwargs)(self._data) + if name not in self._raw_methods: + result = Index(result, name=self.name) + return result diff --git a/pandas/core/indexes/period.py b/pandas/core/indexes/period.py index 3cdefb02ef8b3..71f55f9021eac 100644 --- a/pandas/core/indexes/period.py +++ b/pandas/core/indexes/period.py @@ -1,6 +1,5 @@ # pylint: disable=E1101,E1103,W0232 from datetime import datetime, timedelta -import operator import warnings import numpy as np @@ -18,15 +17,16 @@ from pandas import compat from pandas.core import common as com -from pandas.core.accessor import PandasDelegate, delegate_names +from pandas.core.accessor import delegate_names from pandas.core.algorithms import unique1d import pandas.core.arrays.datetimelike as dtl +from pandas.core.arrays.datetimelike import DatelikeOps from pandas.core.arrays.period import PeriodArray, period_array from pandas.core.base import _shared_docs import pandas.core.indexes.base as ibase from pandas.core.indexes.base import _index_shared_docs, ensure_index from pandas.core.indexes.datetimelike import ( - DatetimeIndexOpsMixin, wrap_arithmetic_op) + DatetimeIndexOpsMixin, DatetimelikeDelegateMixin, wrap_arithmetic_op) from pandas.core.indexes.datetimes import DatetimeIndex, Index, Int64Index from pandas.core.missing import isna from pandas.core.ops import get_op_result_name @@ -54,37 +54,26 @@ def _new_PeriodIndex(cls, **d): return cls(values, **d) -class PeriodDelegateMixin(PandasDelegate): +class PeriodDelegateMixin(DatetimelikeDelegateMixin): """ Delegate from PeriodIndex to PeriodArray. """ - def _delegate_property_get(self, name, *args, **kwargs): - result = getattr(self._data, name) - box_ops = ( - set(PeriodArray._datetimelike_ops) - set(PeriodArray._bool_ops) - ) - if name in box_ops: - result = Index(result, name=self.name) - return result - - def _delegate_property_set(self, name, value, *args, **kwargs): - setattr(self._data, name, value) - - def _delegate_method(self, name, *args, **kwargs): - result = operator.methodcaller(name, *args, **kwargs)(self._data) - return Index(result, name=self.name) + _delegate_class = PeriodArray + _delegated_properties = PeriodArray._datetimelike_ops + _delegated_methods = ( + set(PeriodArray._datetimelike_methods) | {'_addsub_int_array'} + ) + _raw_properties = {'is_leap_year'} @delegate_names(PeriodArray, - PeriodArray._datetimelike_ops + ['size', 'asi8', 'shape'], + PeriodDelegateMixin._delegated_properties, typ='property') @delegate_names(PeriodArray, - [x for x in PeriodArray._datetimelike_methods - if x not in {"asfreq", "to_timestamp"}], - typ="method", - overwrite=True) -class PeriodIndex(DatetimeIndexOpsMixin, - Int64Index, PeriodDelegateMixin): + PeriodDelegateMixin._delegated_methods, + typ="method") +class PeriodIndex(DatelikeOps, DatetimeIndexOpsMixin, Int64Index, + PeriodDelegateMixin): """ Immutable ndarray holding ordinal values indicating regular periods in time such as particular years, quarters, months, etc. @@ -349,21 +338,6 @@ def _maybe_box_as_values(self, values, **attribs): freq = attribs['freq'] return PeriodArray(values, freq=freq) - # ------------------------------------------------------------------------ - # Dispatch and maybe box. Not done in delegate_names because we box - # different from those (which use Index). - - def asfreq(self, freq=None, how='E'): - result = self._data.asfreq(freq=freq, how=how) - return self._simple_new(result, name=self.name) - - def to_timestamp(self, freq=None, how='start'): - from pandas import DatetimeIndex - result = self._data.to_timestamp(freq=freq, how=how) - return DatetimeIndex._simple_new(result.asi8, - name=self.name, - freq=result.freq) - def _maybe_convert_timedelta(self, other): """ Convert timedelta-like input to an integer multiple of self.freq