Skip to content

Commit 46aa691

Browse files
jbrockmendelPingviinituutti
authored andcommitted
REF: Stop mixing DTA/TDA into DTI/TDI (pandas-dev#24476)
* implement _index_data parts of pandas-dev#24024 * implement _eadata, dispatch arithmetic methods to it * dont mix DatetimeLikeArrayMixin into DatetimeIndexOpsMixin * dont inherit TimedeltaIndex from TimedeltaArray * dont inherit from DatetimeArray * use ea_passthrough * remove previously-overriden overridings * stop double-mixing * stop over-writing * handle+test object arrays * Remove unused import * flake8 fixup * edits per comments
1 parent a342bf4 commit 46aa691

File tree

6 files changed

+168
-70
lines changed

6 files changed

+168
-70
lines changed

pandas/core/arrays/datetimes.py

+7-23
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,13 @@ def wrapper(self, other):
131131
return ops.invalid_comparison(self, other, op)
132132

133133
if is_object_dtype(other):
134-
result = op(self.astype('O'), np.array(other))
134+
# We have to use _comp_method_OBJECT_ARRAY instead of numpy
135+
# comparison otherwise it would fail to raise when
136+
# comparing tz-aware and tz-naive
137+
with np.errstate(all='ignore'):
138+
result = ops._comp_method_OBJECT_ARRAY(op,
139+
self.astype(object),
140+
other)
135141
o_mask = isna(other)
136142
elif not (is_datetime64_dtype(other) or
137143
is_datetime64tz_dtype(other)):
@@ -430,28 +436,6 @@ def _timezone(self):
430436
"""
431437
return timezones.get_timezone(self.tzinfo)
432438

433-
@property
434-
def offset(self):
435-
"""
436-
get/set the frequency of the instance
437-
"""
438-
msg = ('{cls}.offset has been deprecated and will be removed '
439-
'in a future version; use {cls}.freq instead.'
440-
.format(cls=type(self).__name__))
441-
warnings.warn(msg, FutureWarning, stacklevel=2)
442-
return self.freq
443-
444-
@offset.setter
445-
def offset(self, value):
446-
"""
447-
get/set the frequency of the instance
448-
"""
449-
msg = ('{cls}.offset has been deprecated and will be removed '
450-
'in a future version; use {cls}.freq instead.'
451-
.format(cls=type(self).__name__))
452-
warnings.warn(msg, FutureWarning, stacklevel=2)
453-
self.freq = value
454-
455439
@property # NB: override with cache_readonly in immutable subclasses
456440
def is_normalized(self):
457441
"""

pandas/core/indexes/datetimelike.py

+38-11
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from pandas._libs import NaT, iNaT, lib
1111
from pandas.compat.numpy import function as nv
1212
from pandas.errors import AbstractMethodError
13-
from pandas.util._decorators import Appender, cache_readonly
13+
from pandas.util._decorators import Appender, cache_readonly, deprecate_kwarg
1414

1515
from pandas.core.dtypes.common import (
1616
ensure_int64, is_bool_dtype, is_dtype_equal, is_float, is_integer,
@@ -19,6 +19,7 @@
1919

2020
from pandas.core import algorithms, ops
2121
from pandas.core.accessor import PandasDelegate
22+
from pandas.core.arrays import ExtensionOpsMixin
2223
from pandas.core.arrays.datetimelike import (
2324
DatetimeLikeArrayMixin, _ensure_datetimelike_to_i8)
2425
import pandas.core.indexes.base as ibase
@@ -30,15 +31,30 @@
3031
_index_doc_kwargs = dict(ibase._index_doc_kwargs)
3132

3233

33-
class DatetimeIndexOpsMixin(DatetimeLikeArrayMixin):
34+
def ea_passthrough(name):
3435
"""
35-
common ops mixin to support a unified interface datetimelike Index
36+
Make an alias for a method of the underlying ExtensionArray.
37+
38+
Parameters
39+
----------
40+
name : str
41+
42+
Returns
43+
-------
44+
method
3645
"""
46+
def method(self, *args, **kwargs):
47+
return getattr(self._eadata, name)(*args, **kwargs)
48+
49+
method.__name__ = name
50+
# TODO: docstrings
51+
return method
52+
3753

38-
# override DatetimeLikeArrayMixin method
39-
copy = Index.copy
40-
view = Index.view
41-
__setitem__ = Index.__setitem__
54+
class DatetimeIndexOpsMixin(ExtensionOpsMixin):
55+
"""
56+
common ops mixin to support a unified interface datetimelike Index
57+
"""
4258

4359
# DatetimeLikeArrayMixin assumes subclasses are mutable, so these are
4460
# properties there. They can be made into cache_readonly for Index
@@ -50,6 +66,14 @@ class DatetimeIndexOpsMixin(DatetimeLikeArrayMixin):
5066
_resolution = cache_readonly(DatetimeLikeArrayMixin._resolution.fget)
5167
resolution = cache_readonly(DatetimeLikeArrayMixin.resolution.fget)
5268

69+
_box_values = ea_passthrough("_box_values")
70+
_maybe_mask_results = ea_passthrough("_maybe_mask_results")
71+
__iter__ = ea_passthrough("__iter__")
72+
73+
@property
74+
def freqstr(self):
75+
return self._eadata.freqstr
76+
5377
def unique(self, level=None):
5478
if level is not None:
5579
self._validate_index_level(level)
@@ -74,9 +98,6 @@ def wrapper(self, other):
7498
wrapper.__name__ = '__{}__'.format(op.__name__)
7599
return wrapper
76100

77-
# A few methods that are shared
78-
_maybe_mask_results = DatetimeLikeArrayMixin._maybe_mask_results
79-
80101
# ------------------------------------------------------------------------
81102

82103
def equals(self, other):
@@ -549,7 +570,7 @@ def _concat_same_dtype(self, to_concat, name):
549570
# - remove the .asi8 here
550571
# - remove the _maybe_box_as_values
551572
# - combine with the `else` block
552-
new_data = self._concat_same_type(to_concat).asi8
573+
new_data = self._eadata._concat_same_type(to_concat).asi8
553574
else:
554575
new_data = type(self._values)._concat_same_type(to_concat)
555576

@@ -581,6 +602,12 @@ def _time_shift(self, periods, freq=None):
581602
result = self._eadata._time_shift(periods, freq=freq)
582603
return type(self)(result, name=self.name)
583604

605+
@deprecate_kwarg(old_arg_name='n', new_arg_name='periods')
606+
@Appender(DatetimeLikeArrayMixin.shift.__doc__)
607+
def shift(self, periods, freq=None):
608+
result = self._eadata.shift(periods, freq=freq)
609+
return type(self)(result, name=self.name)
610+
584611

585612
def wrap_arithmetic_op(self, other, result):
586613
if result is NotImplemented:

pandas/core/indexes/datetimes.py

+72-16
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
import pandas.core.common as com
2727
from pandas.core.indexes.base import Index
2828
from pandas.core.indexes.datetimelike import (
29-
DatetimeIndexOpsMixin, DatetimelikeDelegateMixin)
29+
DatetimeIndexOpsMixin, DatetimelikeDelegateMixin, ea_passthrough)
3030
from pandas.core.indexes.numeric import Int64Index
3131
from pandas.core.ops import get_op_result_name
3232
import pandas.core.tools.datetimes as tools
@@ -96,19 +96,13 @@ class DatetimeDelegateMixin(DatetimelikeDelegateMixin):
9696
_delegate_class = DatetimeArray
9797

9898

99-
@delegate_names(DatetimeArray, ["to_period", "tz_localize", "tz_convert",
100-
"day_name", "month_name"],
101-
typ="method", overwrite=True)
102-
@delegate_names(DatetimeArray,
103-
DatetimeArray._field_ops, typ="property", overwrite=True)
10499
@delegate_names(DatetimeArray,
105100
DatetimeDelegateMixin._delegated_properties,
106101
typ="property")
107102
@delegate_names(DatetimeArray,
108103
DatetimeDelegateMixin._delegated_methods,
109104
typ="method", overwrite=False)
110-
class DatetimeIndex(DatetimeArray, DatetimeIndexOpsMixin, Int64Index,
111-
DatetimeDelegateMixin):
105+
class DatetimeIndex(DatetimeIndexOpsMixin, Int64Index, DatetimeDelegateMixin):
112106
"""
113107
Immutable ndarray of datetime64 data, represented internally as int64, and
114108
which can be boxed to Timestamp objects that are subclasses of datetime and
@@ -268,6 +262,7 @@ def _join_i8_wrapper(joinf, **kwargs):
268262
_object_ops = DatetimeArray._object_ops
269263
_field_ops = DatetimeArray._field_ops
270264
_datetimelike_ops = DatetimeArray._datetimelike_ops
265+
_datetimelike_methods = DatetimeArray._datetimelike_methods
271266

272267
# --------------------------------------------------------------------
273268
# Constructors
@@ -294,8 +289,8 @@ def __new__(cls, data=None,
294289
"endpoints is deprecated. Use "
295290
"`pandas.date_range` instead.",
296291
FutureWarning, stacklevel=2)
297-
298-
return cls(dtarr, name=name)
292+
return cls._simple_new(
293+
dtarr._data, freq=dtarr.freq, tz=dtarr.tz, name=name)
299294

300295
if is_scalar(data):
301296
raise TypeError("{cls}() must be called with a "
@@ -331,7 +326,11 @@ def _simple_new(cls, values, name=None, freq=None, tz=None, dtype=None):
331326
# DatetimeArray._simple_new will accept either i8 or M8[ns] dtypes
332327
assert isinstance(values, np.ndarray), type(values)
333328

334-
result = super(DatetimeIndex, cls)._simple_new(values, freq, tz)
329+
dtarr = DatetimeArray._simple_new(values, freq=freq, tz=tz)
330+
result = object.__new__(cls)
331+
result._data = dtarr._data
332+
result._freq = dtarr.freq
333+
result._tz = dtarr.tz
335334
result.name = name
336335
# For groupby perf. See note in indexes/base about _index_data
337336
result._index_data = result._data
@@ -340,6 +339,10 @@ def _simple_new(cls, values, name=None, freq=None, tz=None, dtype=None):
340339

341340
# --------------------------------------------------------------------
342341

342+
@property
343+
def dtype(self):
344+
return self._eadata.dtype
345+
343346
@property
344347
def _values(self):
345348
# tz-naive -> ndarray
@@ -360,6 +363,8 @@ def tz(self, value):
360363
raise AttributeError("Cannot directly set timezone. Use tz_localize() "
361364
"or tz_convert() as appropriate")
362365

366+
tzinfo = tz
367+
363368
@property
364369
def size(self):
365370
# TODO: Remove this when we have a DatetimeTZArray
@@ -670,7 +675,7 @@ def intersection(self, other):
670675
def _get_time_micros(self):
671676
values = self.asi8
672677
if self.tz is not None and not timezones.is_utc(self.tz):
673-
values = self._local_timestamps()
678+
values = self._eadata._local_timestamps()
674679
return fields.get_time_micros(values)
675680

676681
def to_series(self, keep_tz=None, index=None, name=None):
@@ -1139,12 +1144,64 @@ def _eadata(self):
11391144
_is_monotonic_increasing = Index.is_monotonic_increasing
11401145
_is_monotonic_decreasing = Index.is_monotonic_decreasing
11411146
_is_unique = Index.is_unique
1142-
astype = DatetimeIndexOpsMixin.astype
11431147

11441148
_timezone = cache_readonly(DatetimeArray._timezone.fget)
11451149
is_normalized = cache_readonly(DatetimeArray.is_normalized.fget)
11461150
_resolution = cache_readonly(DatetimeArray._resolution.fget)
11471151

1152+
strftime = ea_passthrough("strftime")
1153+
_has_same_tz = ea_passthrough("_has_same_tz")
1154+
__array__ = ea_passthrough("__array__")
1155+
1156+
@property
1157+
def offset(self):
1158+
"""
1159+
get/set the frequency of the instance
1160+
"""
1161+
msg = ('{cls}.offset has been deprecated and will be removed '
1162+
'in a future version; use {cls}.freq instead.'
1163+
.format(cls=type(self).__name__))
1164+
warnings.warn(msg, FutureWarning, stacklevel=2)
1165+
return self.freq
1166+
1167+
@offset.setter
1168+
def offset(self, value):
1169+
"""
1170+
get/set the frequency of the instance
1171+
"""
1172+
msg = ('{cls}.offset has been deprecated and will be removed '
1173+
'in a future version; use {cls}.freq instead.'
1174+
.format(cls=type(self).__name__))
1175+
warnings.warn(msg, FutureWarning, stacklevel=2)
1176+
self.freq = value
1177+
1178+
@property
1179+
def freq(self):
1180+
return self._freq
1181+
1182+
@freq.setter
1183+
def freq(self, value):
1184+
if value is not None:
1185+
# let DatetimeArray to validation
1186+
self._eadata.freq = value
1187+
1188+
self._freq = to_offset(value)
1189+
1190+
def __getitem__(self, key):
1191+
result = self._eadata.__getitem__(key)
1192+
if is_scalar(result):
1193+
return result
1194+
elif result.ndim > 1:
1195+
# To support MPL which performs slicing with 2 dim
1196+
# even though it only has 1 dim by definition
1197+
assert isinstance(result, np.ndarray), result
1198+
return result
1199+
return type(self)(result, name=self.name)
1200+
1201+
@property
1202+
def _box_func(self):
1203+
return lambda x: Timestamp(x, tz=self.tz)
1204+
11481205
# --------------------------------------------------------------------
11491206

11501207
@Substitution(klass='DatetimeIndex')
@@ -1486,9 +1543,8 @@ def date_range(start=None, end=None, periods=None, freq=None, tz=None,
14861543
start=start, end=end, periods=periods,
14871544
freq=freq, tz=tz, normalize=normalize,
14881545
closed=closed, **kwargs)
1489-
1490-
result = DatetimeIndex(dtarr, name=name)
1491-
return result
1546+
return DatetimeIndex._simple_new(
1547+
dtarr._data, tz=dtarr.tz, freq=dtarr.freq, name=name)
14921548

14931549

14941550
def bdate_range(start=None, end=None, periods=None, freq='B', tz=None,

0 commit comments

Comments
 (0)