Skip to content

Implement Delegate parts of #24024 #24463

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

Merged
merged 2 commits into from
Dec 28, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions pandas/core/indexes/accessors.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@

from pandas.core.accessor import PandasDelegate, delegate_names
from pandas.core.algorithms import take_1d
from pandas.core.arrays import (
DatetimeArrayMixin as DatetimeArray, PeriodArray,
TimedeltaArrayMixin as TimedeltaArray)
from pandas.core.base import NoNewAttributesMixin, PandasObject
from pandas.core.indexes.datetimes import DatetimeIndex
from pandas.core.indexes.period import PeriodArray
from pandas.core.indexes.timedeltas import TimedeltaIndex


Expand Down Expand Up @@ -106,11 +108,11 @@ def _delegate_method(self, name, *args, **kwargs):
return result


@delegate_names(delegate=DatetimeIndex,
accessors=DatetimeIndex._datetimelike_ops,
@delegate_names(delegate=DatetimeArray,
accessors=DatetimeArray._datetimelike_ops,
typ="property")
@delegate_names(delegate=DatetimeIndex,
accessors=DatetimeIndex._datetimelike_methods,
@delegate_names(delegate=DatetimeArray,
accessors=DatetimeArray._datetimelike_methods,
typ="method")
class DatetimeProperties(Properties):
"""
Expand Down Expand Up @@ -177,11 +179,11 @@ def freq(self):
return self._get_values().inferred_freq


@delegate_names(delegate=TimedeltaIndex,
accessors=TimedeltaIndex._datetimelike_ops,
@delegate_names(delegate=TimedeltaArray,
accessors=TimedeltaArray._datetimelike_ops,
typ="property")
@delegate_names(delegate=TimedeltaIndex,
accessors=TimedeltaIndex._datetimelike_methods,
@delegate_names(delegate=TimedeltaArray,
accessors=TimedeltaArray._datetimelike_methods,
typ="method")
class TimedeltaProperties(Properties):
"""
Expand Down
63 changes: 3 additions & 60 deletions pandas/core/indexes/datetimelike.py
Original file line number Diff line number Diff line change
Expand Up @@ -594,63 +594,6 @@ def wrap_arithmetic_op(self, other, result):
return result


def wrap_array_method(method, pin_name=False):
"""
Wrap a DatetimeArray/TimedeltaArray/PeriodArray method so that the
returned object is an Index subclass instead of ndarray or ExtensionArray
subclass.

Parameters
----------
method : method of Datetime/Timedelta/Period Array class
pin_name : bool
Whether to set name=self.name on the output Index

Returns
-------
method
"""
def index_method(self, *args, **kwargs):
result = method(self._eadata, *args, **kwargs)

# Index.__new__ will choose the appropriate subclass to return
result = Index(result)
if pin_name:
result.name = self.name
return result

index_method.__name__ = method.__name__
index_method.__doc__ = method.__doc__
return index_method


def wrap_field_accessor(prop):
"""
Wrap a DatetimeArray/TimedeltaArray/PeriodArray array-returning property
to return an Index subclass instead of ndarray or ExtensionArray subclass.

Parameters
----------
prop : property

Returns
-------
new_prop : property
"""
fget = prop.fget

def f(self):
result = fget(self._eadata)
if is_bool_dtype(result):
# return numpy array b/c there is no BoolIndex
return result
return Index(result, name=self.name)

f.__name__ = fget.__name__
f.__doc__ = fget.__doc__
return property(f)


def maybe_unwrap_index(obj):
"""
If operating against another Index object, we need to unwrap the underlying
Expand Down Expand Up @@ -703,16 +646,16 @@ def _delegate_class(self):
raise AbstractMethodError

def _delegate_property_get(self, name, *args, **kwargs):
result = getattr(self._data, name)
result = getattr(self._eadata, 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)
setattr(self._eadata, name, value)

def _delegate_method(self, name, *args, **kwargs):
result = operator.methodcaller(name, *args, **kwargs)(self._data)
result = operator.methodcaller(name, *args, **kwargs)(self._eadata)
if name not in self._raw_methods:
result = Index(result, name=self.name)
return result
90 changes: 50 additions & 40 deletions pandas/core/indexes/datetimes.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@
import pandas.core.dtypes.concat as _concat
from pandas.core.dtypes.missing import isna

from pandas.core.accessor import delegate_names
from pandas.core.arrays.datetimes import (
DatetimeArrayMixin as DatetimeArray, _to_m8)
from pandas.core.base import _shared_docs
import pandas.core.common as com
from pandas.core.indexes.base import Index, _index_shared_docs
from pandas.core.indexes.datetimelike import (
DatetimeIndexOpsMixin, wrap_array_method, wrap_field_accessor)
DatetimeIndexOpsMixin, DatetimelikeDelegateMixin)
from pandas.core.indexes.numeric import Int64Index
from pandas.core.ops import get_op_result_name
import pandas.core.tools.datetimes as tools
Expand Down Expand Up @@ -61,7 +62,54 @@ def _new_DatetimeIndex(cls, d):
return result


class DatetimeIndex(DatetimeArray, DatetimeIndexOpsMixin, Int64Index):
class DatetimeDelegateMixin(DatetimelikeDelegateMixin):
# Most attrs are dispatched via datetimelike_{ops,methods}
# Some are "raw" methods, the result is not not re-boxed in an Index
# We also have a few "extra" attrs, which may or may not be raw,
# which we we dont' want to expose in the .dt accessor.
_extra_methods = [
'to_period',
'to_perioddelta',
'to_julian_date',
]
_extra_raw_methods = [
'to_pydatetime',
'_local_timestamps',
'_has_same_tz',
]
_extra_raw_properties = [
'_box_func',
'tz', 'tzinfo',
]
_delegated_properties = (
DatetimeArray._datetimelike_ops + _extra_raw_properties
)
_delegated_methods = (
DatetimeArray._datetimelike_methods + _extra_methods +
_extra_raw_methods
)
_raw_properties = {
'date',
'time',
'timetz',
} | set(DatetimeArray._bool_ops) | set(_extra_raw_properties)
_raw_methods = set(_extra_raw_methods)
_delegate_class = DatetimeArray


@delegate_names(DatetimeArray, ["to_period", "tz_localize", "tz_convert",
"day_name", "month_name"],
typ="method", overwrite=True)
@delegate_names(DatetimeArray,
DatetimeArray._field_ops, typ="property", overwrite=True)
@delegate_names(DatetimeArray,
DatetimeDelegateMixin._delegated_properties,
typ="property")
@delegate_names(DatetimeArray,
DatetimeDelegateMixin._delegated_methods,
typ="method", overwrite=False)
class DatetimeIndex(DatetimeArray, DatetimeIndexOpsMixin, Int64Index,
DatetimeDelegateMixin):
"""
Immutable ndarray of datetime64 data, represented internally as int64, and
which can be boxed to Timestamp objects that are subclasses of datetime and
Expand Down Expand Up @@ -1094,44 +1142,6 @@ def _eadata(self):
is_normalized = cache_readonly(DatetimeArray.is_normalized.fget)
_resolution = cache_readonly(DatetimeArray._resolution.fget)

year = wrap_field_accessor(DatetimeArray.year)
month = wrap_field_accessor(DatetimeArray.month)
day = wrap_field_accessor(DatetimeArray.day)
hour = wrap_field_accessor(DatetimeArray.hour)
minute = wrap_field_accessor(DatetimeArray.minute)
second = wrap_field_accessor(DatetimeArray.second)
microsecond = wrap_field_accessor(DatetimeArray.microsecond)
nanosecond = wrap_field_accessor(DatetimeArray.nanosecond)
weekofyear = wrap_field_accessor(DatetimeArray.weekofyear)
week = weekofyear
dayofweek = wrap_field_accessor(DatetimeArray.dayofweek)
weekday = dayofweek

weekday_name = wrap_field_accessor(DatetimeArray.weekday_name)

dayofyear = wrap_field_accessor(DatetimeArray.dayofyear)
quarter = wrap_field_accessor(DatetimeArray.quarter)
days_in_month = wrap_field_accessor(DatetimeArray.days_in_month)
daysinmonth = days_in_month
is_month_start = wrap_field_accessor(DatetimeArray.is_month_start)
is_month_end = wrap_field_accessor(DatetimeArray.is_month_end)
is_quarter_start = wrap_field_accessor(DatetimeArray.is_quarter_start)
is_quarter_end = wrap_field_accessor(DatetimeArray.is_quarter_end)
is_year_start = wrap_field_accessor(DatetimeArray.is_year_start)
is_year_end = wrap_field_accessor(DatetimeArray.is_year_end)
is_leap_year = wrap_field_accessor(DatetimeArray.is_leap_year)

tz_localize = wrap_array_method(DatetimeArray.tz_localize, True)
tz_convert = wrap_array_method(DatetimeArray.tz_convert, True)
to_perioddelta = wrap_array_method(DatetimeArray.to_perioddelta,
False)
to_period = wrap_array_method(DatetimeArray.to_period, True)
normalize = wrap_array_method(DatetimeArray.normalize, True)
to_julian_date = wrap_array_method(DatetimeArray.to_julian_date,
False)
month_name = wrap_array_method(DatetimeArray.month_name, True)
day_name = wrap_array_method(DatetimeArray.day_name, True)

# --------------------------------------------------------------------

@Substitution(klass='DatetimeIndex')
Expand Down
46 changes: 36 additions & 10 deletions pandas/core/indexes/timedeltas.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,16 @@
import pandas.core.dtypes.concat as _concat
from pandas.core.dtypes.missing import isna

from pandas.core.accessor import delegate_names
from pandas.core.arrays import datetimelike as dtl
from pandas.core.arrays.timedeltas import (
TimedeltaArrayMixin as TimedeltaArray, _is_convertible_to_td, _to_m8)
from pandas.core.base import _shared_docs
import pandas.core.common as com
from pandas.core.indexes.base import Index, _index_shared_docs
from pandas.core.indexes.datetimelike import (
DatetimeIndexOpsMixin, maybe_unwrap_index, wrap_arithmetic_op,
wrap_array_method, wrap_field_accessor)
DatetimeIndexOpsMixin, DatetimelikeDelegateMixin, maybe_unwrap_index,
wrap_arithmetic_op)
from pandas.core.indexes.numeric import Int64Index
from pandas.core.ops import get_op_result_name
from pandas.core.tools.timedeltas import _coerce_scalar_to_timedelta_type
Expand All @@ -43,8 +44,40 @@ def method(self, other):
return method


class TimedeltaDelegateMixin(DatetimelikeDelegateMixin):
# Most attrs are dispatched via datetimelike_{ops,methods}
# Some are "raw" methods, the result is not not re-boxed in an Index
# We also have a few "extra" attrs, which may or may not be raw,
# which we we dont' want to expose in the .dt accessor.
_delegate_class = TimedeltaArray
_delegated_properties = (TimedeltaArray._datetimelike_ops + [
'components',
])
_delegated_methods = TimedeltaArray._datetimelike_methods + [
'_box_values',
]
_raw_properties = {
'components',
}
_raw_methods = {
'to_pytimedelta',
}


@delegate_names(TimedeltaArray,
["to_pytimedelta", "total_seconds"],
typ="method", overwrite=True)
@delegate_names(TimedeltaArray,
["days", "seconds", "microseconds", "nanoseconds"],
typ="property", overwrite=True)
@delegate_names(TimedeltaArray,
TimedeltaDelegateMixin._delegated_properties,
typ="property")
@delegate_names(TimedeltaArray,
TimedeltaDelegateMixin._delegated_methods,
typ="method", overwrite=False)
class TimedeltaIndex(TimedeltaArray, DatetimeIndexOpsMixin,
dtl.TimelikeOps, Int64Index):
dtl.TimelikeOps, Int64Index, TimedeltaDelegateMixin):
"""
Immutable ndarray of timedelta64 data, represented internally as int64, and
which can be boxed to timedelta objects
Expand Down Expand Up @@ -251,13 +284,6 @@ def _eadata(self):
__div__ = __truediv__
__rdiv__ = __rtruediv__

days = wrap_field_accessor(TimedeltaArray.days)
seconds = wrap_field_accessor(TimedeltaArray.seconds)
microseconds = wrap_field_accessor(TimedeltaArray.microseconds)
nanoseconds = wrap_field_accessor(TimedeltaArray.nanoseconds)

total_seconds = wrap_array_method(TimedeltaArray.total_seconds, True)

# Compat for frequency inference, see GH#23789
_is_monotonic_increasing = Index.is_monotonic_increasing
_is_monotonic_decreasing = Index.is_monotonic_decreasing
Expand Down