Skip to content

Commit 24a50fc

Browse files
jbrockmendeljreback
authored andcommitted
POC: _eadata (#24394)
1 parent 73801ab commit 24a50fc

File tree

5 files changed

+85
-99
lines changed

5 files changed

+85
-99
lines changed

pandas/core/indexes/datetimelike.py

+54-10
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ class DatetimeIndexOpsMixin(DatetimeLikeArrayMixin):
3939

4040
# override DatetimeLikeArrayMixin method
4141
copy = Index.copy
42-
unique = Index.unique
4342

4443
# DatetimeLikeArrayMixin assumes subclasses are mutable, so these are
4544
# properties there. They can be made into cache_readonly for Index
@@ -51,6 +50,30 @@ class DatetimeIndexOpsMixin(DatetimeLikeArrayMixin):
5150
_resolution = cache_readonly(DatetimeLikeArrayMixin._resolution.fget)
5251
resolution = cache_readonly(DatetimeLikeArrayMixin.resolution.fget)
5352

53+
def unique(self, level=None):
54+
if level is not None:
55+
self._validate_index_level(level)
56+
57+
result = self._eadata.unique()
58+
59+
# Note: if `self` is already unique, then self.unique() should share
60+
# a `freq` with self. If not already unique, then self.freq must be
61+
# None, so again sharing freq is correct.
62+
return self._shallow_copy(result._data)
63+
64+
@classmethod
65+
def _create_comparison_method(cls, op):
66+
"""
67+
Create a comparison method that dispatches to ``cls.values``.
68+
"""
69+
def wrapper(self, other):
70+
result = op(self._eadata, maybe_unwrap_index(other))
71+
return result
72+
73+
wrapper.__doc__ = op.__doc__
74+
wrapper.__name__ = '__{}__'.format(op.__name__)
75+
return wrapper
76+
5477
# A few methods that are shared
5578
_maybe_mask_results = DatetimeLikeArrayMixin._maybe_mask_results
5679

@@ -106,7 +129,7 @@ def wrapper(left, right):
106129

107130
@Appender(DatetimeLikeArrayMixin._evaluate_compare.__doc__)
108131
def _evaluate_compare(self, other, op):
109-
result = DatetimeLikeArrayMixin._evaluate_compare(self, other, op)
132+
result = self._eadata._evaluate_compare(other, op)
110133
if is_bool_dtype(result):
111134
return result
112135
try:
@@ -406,7 +429,7 @@ def _add_datetimelike_methods(cls):
406429

407430
def __add__(self, other):
408431
# dispatch to ExtensionArray implementation
409-
result = super(cls, self).__add__(other)
432+
result = self._eadata.__add__(maybe_unwrap_index(other))
410433
return wrap_arithmetic_op(self, other, result)
411434

412435
cls.__add__ = __add__
@@ -418,13 +441,13 @@ def __radd__(self, other):
418441

419442
def __sub__(self, other):
420443
# dispatch to ExtensionArray implementation
421-
result = super(cls, self).__sub__(other)
444+
result = self._eadata.__sub__(maybe_unwrap_index(other))
422445
return wrap_arithmetic_op(self, other, result)
423446

424447
cls.__sub__ = __sub__
425448

426449
def __rsub__(self, other):
427-
result = super(cls, self).__rsub__(other)
450+
result = self._eadata.__rsub__(maybe_unwrap_index(other))
428451
return wrap_arithmetic_op(self, other, result)
429452

430453
cls.__rsub__ = __rsub__
@@ -548,9 +571,8 @@ def astype(self, dtype, copy=True):
548571

549572
@Appender(DatetimeLikeArrayMixin._time_shift.__doc__)
550573
def _time_shift(self, periods, freq=None):
551-
result = DatetimeLikeArrayMixin._time_shift(self, periods, freq=freq)
552-
result.name = self.name
553-
return result
574+
result = self._eadata._time_shift(periods, freq=freq)
575+
return type(self)(result, name=self.name)
554576

555577

556578
def wrap_arithmetic_op(self, other, result):
@@ -589,7 +611,7 @@ def wrap_array_method(method, pin_name=False):
589611
method
590612
"""
591613
def index_method(self, *args, **kwargs):
592-
result = method(self, *args, **kwargs)
614+
result = method(self._eadata, *args, **kwargs)
593615

594616
# Index.__new__ will choose the appropriate subclass to return
595617
result = Index(result)
@@ -618,7 +640,7 @@ def wrap_field_accessor(prop):
618640
fget = prop.fget
619641

620642
def f(self):
621-
result = fget(self)
643+
result = fget(self._eadata)
622644
if is_bool_dtype(result):
623645
# return numpy array b/c there is no BoolIndex
624646
return result
@@ -629,6 +651,28 @@ def f(self):
629651
return property(f)
630652

631653

654+
def maybe_unwrap_index(obj):
655+
"""
656+
If operating against another Index object, we need to unwrap the underlying
657+
data before deferring to the DatetimeArray/TimedeltaArray/PeriodArray
658+
implementation, otherwise we will incorrectly return NotImplemented.
659+
660+
Parameters
661+
----------
662+
obj : object
663+
664+
Returns
665+
-------
666+
unwrapped object
667+
"""
668+
if isinstance(obj, ABCIndexClass):
669+
if isinstance(obj, DatetimeIndexOpsMixin):
670+
# i.e. PeriodIndex/DatetimeIndex/TimedeltaIndex
671+
return obj._eadata
672+
return obj._data
673+
return obj
674+
675+
632676
class DatetimelikeDelegateMixin(PandasDelegate):
633677
"""
634678
Delegation mechanism, specific for Datetime, Timedelta, and Period types.

pandas/core/indexes/datetimes.py

+5-9
Original file line numberDiff line numberDiff line change
@@ -713,15 +713,6 @@ def snap(self, freq='S'):
713713
return DatetimeIndex._simple_new(snapped, freq=freq)
714714
# TODO: what about self.name? tz? if so, use shallow_copy?
715715

716-
def unique(self, level=None):
717-
if level is not None:
718-
self._validate_index_level(level)
719-
720-
# TODO(DatetimeArray): change dispatch once inheritance is removed
721-
# call DatetimeArray method
722-
result = DatetimeArray.unique(self)
723-
return self._shallow_copy(result._data)
724-
725716
def join(self, other, how='left', level=None, return_indexers=False,
726717
sort=False):
727718
"""
@@ -1089,6 +1080,11 @@ def slice_indexer(self, start=None, end=None, step=None, kind=None):
10891080
# --------------------------------------------------------------------
10901081
# Wrapping DatetimeArray
10911082

1083+
@property
1084+
def _eadata(self):
1085+
return DatetimeArray._simple_new(self._data,
1086+
tz=self.tz, freq=self.freq)
1087+
10921088
# Compat for frequency inference, see GH#23789
10931089
_is_monotonic_increasing = Index.is_monotonic_increasing
10941090
_is_monotonic_decreasing = Index.is_monotonic_decreasing

pandas/core/indexes/period.py

+5-47
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
import pandas.core.indexes.base as ibase
2727
from pandas.core.indexes.base import _index_shared_docs, ensure_index
2828
from pandas.core.indexes.datetimelike import (
29-
DatetimeIndexOpsMixin, DatetimelikeDelegateMixin, wrap_arithmetic_op)
29+
DatetimeIndexOpsMixin, DatetimelikeDelegateMixin)
3030
from pandas.core.indexes.datetimes import DatetimeIndex, Index, Int64Index
3131
from pandas.core.missing import isna
3232
from pandas.core.ops import get_op_result_name
@@ -247,6 +247,10 @@ def _simple_new(cls, values, name=None, freq=None, **kwargs):
247247
# ------------------------------------------------------------------------
248248
# Data
249249

250+
@property
251+
def _eadata(self):
252+
return self._data
253+
250254
@property
251255
def _ndarray_values(self):
252256
return self._data._ndarray_values
@@ -878,52 +882,6 @@ def __setstate__(self, state):
878882

879883
_unpickle_compat = __setstate__
880884

881-
@classmethod
882-
def _add_datetimelike_methods(cls):
883-
"""
884-
add in the datetimelike methods (as we may have to override the
885-
superclass)
886-
"""
887-
# TODO(DatetimeArray): move this up to DatetimeArrayMixin
888-
889-
def __add__(self, other):
890-
# dispatch to ExtensionArray implementation
891-
result = self._data.__add__(other)
892-
return wrap_arithmetic_op(self, other, result)
893-
894-
cls.__add__ = __add__
895-
896-
def __radd__(self, other):
897-
# alias for __add__
898-
return self.__add__(other)
899-
cls.__radd__ = __radd__
900-
901-
def __sub__(self, other):
902-
# dispatch to ExtensionArray implementation
903-
result = self._data.__sub__(other)
904-
return wrap_arithmetic_op(self, other, result)
905-
906-
cls.__sub__ = __sub__
907-
908-
def __rsub__(self, other):
909-
result = self._data.__rsub__(other)
910-
return wrap_arithmetic_op(self, other, result)
911-
912-
cls.__rsub__ = __rsub__
913-
914-
@classmethod
915-
def _create_comparison_method(cls, op):
916-
"""
917-
Create a comparison method that dispatches to ``cls.values``.
918-
"""
919-
# TODO(DatetimeArray): move to base class.
920-
def wrapper(self, other):
921-
return op(self._data, other)
922-
923-
wrapper.__doc__ = op.__doc__
924-
wrapper.__name__ = '__{}__'.format(op.__name__)
925-
return wrapper
926-
927885
def view(self, dtype=None, type=None):
928886
# TODO(DatetimeArray): remove
929887
if dtype is None or dtype is __builtins__['type'](self):

pandas/core/indexes/timedeltas.py

+12-27
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222
import pandas.core.common as com
2323
from pandas.core.indexes.base import Index, _index_shared_docs
2424
from pandas.core.indexes.datetimelike import (
25-
DatetimeIndexOpsMixin, wrap_arithmetic_op, wrap_array_method,
26-
wrap_field_accessor)
25+
DatetimeIndexOpsMixin, maybe_unwrap_index, wrap_arithmetic_op,
26+
wrap_array_method, wrap_field_accessor)
2727
from pandas.core.indexes.numeric import Int64Index
2828
from pandas.core.ops import get_op_result_name
2929
from pandas.core.tools.timedeltas import _coerce_scalar_to_timedelta_type
@@ -36,11 +36,7 @@ def _make_wrapped_arith_op(opname):
3636
meth = getattr(TimedeltaArray, opname)
3737

3838
def method(self, other):
39-
oth = other
40-
if isinstance(other, Index):
41-
oth = other._data
42-
43-
result = meth(self, oth)
39+
result = meth(self._eadata, maybe_unwrap_index(other))
4440
return wrap_arithmetic_op(self, other, result)
4541

4642
method.__name__ = opname
@@ -237,6 +233,10 @@ def _format_native_types(self, na_rep=u'NaT', date_format=None, **kwargs):
237233
# -------------------------------------------------------------------
238234
# Wrapping TimedeltaArray
239235

236+
@property
237+
def _eadata(self):
238+
return TimedeltaArray._simple_new(self._data, freq=self.freq)
239+
240240
__mul__ = _make_wrapped_arith_op("__mul__")
241241
__rmul__ = _make_wrapped_arith_op("__rmul__")
242242
__floordiv__ = _make_wrapped_arith_op("__floordiv__")
@@ -245,6 +245,11 @@ def _format_native_types(self, na_rep=u'NaT', date_format=None, **kwargs):
245245
__rmod__ = _make_wrapped_arith_op("__rmod__")
246246
__divmod__ = _make_wrapped_arith_op("__divmod__")
247247
__rdivmod__ = _make_wrapped_arith_op("__rdivmod__")
248+
__truediv__ = _make_wrapped_arith_op("__truediv__")
249+
__rtruediv__ = _make_wrapped_arith_op("__rtruediv__")
250+
if compat.PY2:
251+
__div__ = __truediv__
252+
__rdiv__ = __rtruediv__
248253

249254
days = wrap_field_accessor(TimedeltaArray.days)
250255
seconds = wrap_field_accessor(TimedeltaArray.seconds)
@@ -253,26 +258,6 @@ def _format_native_types(self, na_rep=u'NaT', date_format=None, **kwargs):
253258

254259
total_seconds = wrap_array_method(TimedeltaArray.total_seconds, True)
255260

256-
def __truediv__(self, other):
257-
oth = other
258-
if isinstance(other, Index):
259-
# TimedeltaArray defers, so we need to unwrap
260-
oth = other._values
261-
result = TimedeltaArray.__truediv__(self, oth)
262-
return wrap_arithmetic_op(self, other, result)
263-
264-
def __rtruediv__(self, other):
265-
oth = other
266-
if isinstance(other, Index):
267-
# TimedeltaArray defers, so we need to unwrap
268-
oth = other._values
269-
result = TimedeltaArray.__rtruediv__(self, oth)
270-
return wrap_arithmetic_op(self, other, result)
271-
272-
if compat.PY2:
273-
__div__ = __truediv__
274-
__rdiv__ = __rtruediv__
275-
276261
# Compat for frequency inference, see GH#23789
277262
_is_monotonic_increasing = Index.is_monotonic_increasing
278263
_is_monotonic_decreasing = Index.is_monotonic_decreasing

pandas/tests/arithmetic/test_datetime64.py

+9-6
Original file line numberDiff line numberDiff line change
@@ -1591,7 +1591,8 @@ def check(get_ser, test_ser):
15911591
# with 'operate' (from core/ops.py) for the ops that are not
15921592
# defined
15931593
op = getattr(get_ser, op_str, None)
1594-
with pytest.raises(TypeError, match='operate|[cC]annot'):
1594+
with pytest.raises(TypeError,
1595+
match='operate|[cC]annot|unsupported operand'):
15951596
op(test_ser)
15961597

15971598
# ## timedelta64 ###
@@ -1973,15 +1974,15 @@ def test_dti_sub_tdi(self, tz_naive_fixture):
19731974
result = dti - tdi
19741975
tm.assert_index_equal(result, expected)
19751976

1976-
msg = 'cannot subtract .*TimedeltaIndex'
1977+
msg = 'cannot subtract .*TimedeltaArrayMixin'
19771978
with pytest.raises(TypeError, match=msg):
19781979
tdi - dti
19791980

19801981
# sub with timedelta64 array
19811982
result = dti - tdi.values
19821983
tm.assert_index_equal(result, expected)
19831984

1984-
msg = 'cannot subtract DatetimeIndex from'
1985+
msg = 'cannot subtract DatetimeArrayMixin from'
19851986
with pytest.raises(TypeError, match=msg):
19861987
tdi.values - dti
19871988

@@ -1997,7 +1998,7 @@ def test_dti_isub_tdi(self, tz_naive_fixture):
19971998
result -= tdi
19981999
tm.assert_index_equal(result, expected)
19992000

2000-
msg = 'cannot subtract .*TimedeltaIndex'
2001+
msg = 'cannot subtract .*TimedeltaArrayMixin'
20012002
with pytest.raises(TypeError, match=msg):
20022003
tdi -= dti
20032004

@@ -2008,7 +2009,7 @@ def test_dti_isub_tdi(self, tz_naive_fixture):
20082009

20092010
msg = '|'.join(['cannot perform __neg__ with this index type:',
20102011
'ufunc subtract cannot use operands with types',
2011-
'cannot subtract DatetimeIndex from'])
2012+
'cannot subtract DatetimeArrayMixin from'])
20122013
with pytest.raises(TypeError, match=msg):
20132014
tdi.values -= dti
20142015

@@ -2028,7 +2029,9 @@ def test_dti_isub_tdi(self, tz_naive_fixture):
20282029
def test_add_datetimelike_and_dti(self, addend, tz):
20292030
# GH#9631
20302031
dti = DatetimeIndex(['2011-01-01', '2011-01-02']).tz_localize(tz)
2031-
msg = 'cannot add DatetimeIndex and {0}'.format(type(addend).__name__)
2032+
msg = ('cannot add DatetimeArrayMixin and {0}'
2033+
.format(type(addend).__name__)).replace('DatetimeIndex',
2034+
'DatetimeArrayMixin')
20322035
with pytest.raises(TypeError, match=msg):
20332036
dti + addend
20342037
with pytest.raises(TypeError, match=msg):

0 commit comments

Comments
 (0)