From e614a80668d630eb415bed25f861a49fa929d042 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Sat, 1 Dec 2018 14:30:08 -0600 Subject: [PATCH 1/6] REF: Refactor Datetimelike delegation This is a generalization of PeriodIndex's dispatching to PeriodArray, without any actual changes yet. This is split from #24024, where DatetimeIndex and TimedeltaIndex will implement and inherit from delgates similiar to PeriodDelegateMixin. --- pandas/core/indexes/datetimelike.py | 47 +++++++++++++++++++++++++++++ pandas/core/indexes/period.py | 37 +++++++++-------------- 2 files changed, 62 insertions(+), 22 deletions(-) diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index 5e25efe77d8b9..74a798eb26e26 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 @@ -22,6 +23,7 @@ from pandas.core.dtypes.missing import isna from pandas.core import algorithms, ops +from pandas.core.accessor import PandasDelegate from pandas.core.arrays import PeriodArray from pandas.core.arrays.datetimelike import DatetimeLikeArrayMixin import pandas.core.indexes.base as ibase @@ -855,3 +857,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 56df454bddf1c..12c58c59e7395 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,7 +17,7 @@ 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.period import PeriodArray, period_array @@ -26,7 +25,8 @@ import pandas.core.indexes.base as ibase from pandas.core.indexes.base import _index_shared_docs, ensure_index from pandas.core.indexes.datetimelike import ( - DatelikeOps, DatetimeIndexOpsMixin, wrap_arithmetic_op) + DatelikeOps, 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,33 +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 + ['size', 'asi8', 'shape'] + ) + _delegated_methods = ( + set(PeriodArray._datetimelike_methods) - + {'asfreq', 'to_timestamp'} | {'_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"}], + PeriodDelegateMixin._delegated_methods, typ="method", overwrite=True) class PeriodIndex(DatelikeOps, DatetimeIndexOpsMixin, From 649843b5ffea0df8d4faa15eb73e42977ad0fc1b Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Sat, 1 Dec 2018 14:34:18 -0600 Subject: [PATCH 2/6] remove unused delegated properties --- pandas/core/indexes/period.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pandas/core/indexes/period.py b/pandas/core/indexes/period.py index 12c58c59e7395..4e0b3b737b413 100644 --- a/pandas/core/indexes/period.py +++ b/pandas/core/indexes/period.py @@ -59,9 +59,7 @@ class PeriodDelegateMixin(DatetimelikeDelegateMixin): Delegate from PeriodIndex to PeriodArray. """ _delegate_class = PeriodArray - _delegated_properties = ( - PeriodArray._datetimelike_ops + ['size', 'asi8', 'shape'] - ) + _delegated_properties = PeriodArray._datetimelike_ops _delegated_methods = ( set(PeriodArray._datetimelike_methods) - {'asfreq', 'to_timestamp'} | {'_addsub_int_array'} From 96b7d932f6438965ae26730d83f1f9b27c4d5f65 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Sat, 1 Dec 2018 14:36:31 -0600 Subject: [PATCH 3/6] add some delegated methods --- pandas/core/indexes/period.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/pandas/core/indexes/period.py b/pandas/core/indexes/period.py index 4e0b3b737b413..5252895ca2681 100644 --- a/pandas/core/indexes/period.py +++ b/pandas/core/indexes/period.py @@ -61,8 +61,7 @@ class PeriodDelegateMixin(DatetimelikeDelegateMixin): _delegate_class = PeriodArray _delegated_properties = PeriodArray._datetimelike_ops _delegated_methods = ( - set(PeriodArray._datetimelike_methods) - - {'asfreq', 'to_timestamp'} | {'_addsub_int_array'} + set(PeriodArray._datetimelike_methods) | {'_addsub_int_array'} ) _raw_properties = {'is_leap_year'} @@ -72,8 +71,7 @@ class PeriodDelegateMixin(DatetimelikeDelegateMixin): typ='property') @delegate_names(PeriodArray, PeriodDelegateMixin._delegated_methods, - typ="method", - overwrite=True) + typ="method") class PeriodIndex(DatelikeOps, DatetimeIndexOpsMixin, Int64Index, PeriodDelegateMixin): """ @@ -344,9 +342,9 @@ def _maybe_box_as_values(self, values, **attribs): # 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 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 From a8e3e50117e14311f1be1aed3f1a32ea1c1da3e3 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Sat, 1 Dec 2018 14:38:06 -0600 Subject: [PATCH 4/6] remove old implementations --- pandas/core/indexes/period.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/pandas/core/indexes/period.py b/pandas/core/indexes/period.py index 5252895ca2681..e0df8aaf7c875 100644 --- a/pandas/core/indexes/period.py +++ b/pandas/core/indexes/period.py @@ -342,17 +342,6 @@ def _maybe_box_as_values(self, values, **attribs): # 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 From de13b2d8a513d9e5db9f496e80778278af62ac88 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Sat, 1 Dec 2018 14:38:44 -0600 Subject: [PATCH 5/6] remove stale comment --- pandas/core/indexes/period.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pandas/core/indexes/period.py b/pandas/core/indexes/period.py index e0df8aaf7c875..c40023d128481 100644 --- a/pandas/core/indexes/period.py +++ b/pandas/core/indexes/period.py @@ -338,10 +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 _maybe_convert_timedelta(self, other): """ Convert timedelta-like input to an integer multiple of self.freq From 1f3f4c92c5c62eca7970c40b4c785c77b89a178e Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Mon, 3 Dec 2018 06:44:16 -0600 Subject: [PATCH 6/6] fix import / inheritence --- pandas/core/indexes/period.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pandas/core/indexes/period.py b/pandas/core/indexes/period.py index c40023d128481..71f55f9021eac 100644 --- a/pandas/core/indexes/period.py +++ b/pandas/core/indexes/period.py @@ -20,13 +20,13 @@ 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 ( - DatelikeOps, DatetimeIndexOpsMixin, DatetimelikeDelegateMixin, - 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 @@ -72,8 +72,8 @@ class PeriodDelegateMixin(DatetimelikeDelegateMixin): @delegate_names(PeriodArray, PeriodDelegateMixin._delegated_methods, typ="method") -class PeriodIndex(DatelikeOps, DatetimeIndexOpsMixin, - Int64Index, PeriodDelegateMixin): +class PeriodIndex(DatelikeOps, DatetimeIndexOpsMixin, Int64Index, + PeriodDelegateMixin): """ Immutable ndarray holding ordinal values indicating regular periods in time such as particular years, quarters, months, etc.