From e7ecbf8607ed0e4310d84814b830c168c94ff1a7 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Mon, 29 Oct 2018 17:40:14 -0700 Subject: [PATCH 1/7] Implement concat_same_type, take --- pandas/core/arrays/datetimelike.py | 50 +++++++- pandas/core/arrays/datetimes.py | 30 ++++- pandas/core/arrays/period.py | 30 ++--- pandas/core/arrays/timedeltas.py | 17 ++- pandas/tests/arrays/test_datetimelike.py | 142 +++++++++++++++++++++++ 5 files changed, 251 insertions(+), 18 deletions(-) diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index 0247ce8dc6ac4..3ba02ce8cbf80 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -11,7 +11,8 @@ from pandas._libs.tslibs.period import ( Period, DIFFERENT_FREQ_INDEX, IncompatibleFrequency) -from pandas.errors import NullFrequencyError, PerformanceWarning +from pandas.errors import ( + AbstractMethodError, NullFrequencyError, PerformanceWarning) from pandas import compat from pandas.tseries import frequencies @@ -36,7 +37,7 @@ from pandas.core.dtypes.dtypes import DatetimeTZDtype import pandas.core.common as com -from pandas.core.algorithms import checked_add_with_arr +from pandas.core.algorithms import checked_add_with_arr, take from .base import ExtensionOpsMixin from pandas.util._decorators import deprecate_kwarg @@ -208,6 +209,51 @@ def astype(self, dtype, copy=True): return self._box_values(self.asi8) return super(DatetimeLikeArrayMixin, self).astype(dtype, copy) + # ------------------------------------------------------------------ + # ExtensionArray Interface + + def _validate_fill_value(self, fill_value): + """ + If a fill_value is passed to `take` convert it to an i8 representation, + raising ValueError if this is not possible. + + Parameters + ---------- + fill_value : object + + Returns + ------- + fill_value : np.int64 + + Raises + ------ + ValueError + """ + raise AbstractMethodError(self) + + def take(self, indices, allow_fill=False, fill_value=None): + if allow_fill: + fill_value = self._validate_fill_value(fill_value) + + new_values = take(self._data, + indices, + allow_fill=allow_fill, + fill_value=fill_value) + + # TODO: use "infer"? Why does not passing freq cause + # failures in py37 but not py27? + freq = self.freq if is_period_dtype(self) else None + return self._shallow_copy(new_values, freq=freq) + + @classmethod + def _concat_same_type(cls, to_concat): + # for TimedeltaArray and PeriodArray; DatetimeArray overrides + freqs = {x.freq for x in to_concat} + assert len(freqs) == 1 + freq = list(freqs)[0] + values = np.concatenate([x._data for x in to_concat]) + return cls._simple_new(values, freq=freq) + # ------------------------------------------------------------------ # Null Handling diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index b656690b30e34..13277a272bb83 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -12,7 +12,7 @@ conversion, fields, timezones, resolution as libresolution) -from pandas.util._decorators import cache_readonly +from pandas.util._decorators import cache_readonly, Appender from pandas.errors import PerformanceWarning from pandas import compat @@ -389,6 +389,34 @@ def __iter__(self): for v in converted: yield v + # ---------------------------------------------------------------- + # ExtensionArray Interface + + @Appender(dtl.DatetimeLikeArrayMixin._validate_fill_value.__doc__) + def _validate_fill_value(self, fill_value): + if isna(fill_value): + fill_value = iNaT + elif isinstance(fill_value, (datetime, np.datetime64)): + self._assert_tzawareness_compat(fill_value) + fill_value = Timestamp(fill_value).value + else: + raise ValueError("'fill_value' should be a Timestamp. " + "Got '{got}'.".format(got=fill_value)) + return fill_value + + @classmethod + def _concat_same_type(cls, to_concat): + freqs = {x.freq for x in to_concat} + assert len(freqs) == 1 + freq = list(freqs)[0] + + tzs = {x.tz for x in to_concat} + assert len(tzs) == 1 + tz = list(tzs)[0] + + values = np.concatenate([x._data for x in to_concat]) + return cls._simple_new(values, freq=freq, tz=tz) + # ----------------------------------------------------------------- # Comparison Methods diff --git a/pandas/core/arrays/period.py b/pandas/core/arrays/period.py index 31bcac2f4f529..320af666fe55c 100644 --- a/pandas/core/arrays/period.py +++ b/pandas/core/arrays/period.py @@ -374,22 +374,24 @@ def __setitem__( raise TypeError(msg) self._data[key] = value + @Appender(dtl.DatetimeLikeArrayMixin._validate_fill_value.__doc__) + def _validate_fill_value(self, fill_value): + if isna(fill_value): + fill_value = iNaT + elif isinstance(fill_value, Period): + if fill_value.freq != self.freq: + msg = DIFFERENT_FREQ_INDEX.format(self.freq.freqstr, + fill_value.freqstr) + raise IncompatibleFrequency(msg) + fill_value = fill_value.ordinal + else: + raise ValueError("'fill_value' should be a Period. " + "Got '{got}'.".format(got=fill_value)) + return fill_value + def take(self, indices, allow_fill=False, fill_value=None): if allow_fill: - if isna(fill_value): - fill_value = iNaT - elif isinstance(fill_value, Period): - if self.freq != fill_value.freq: - msg = DIFFERENT_FREQ_INDEX.format( - self.freq.freqstr, - fill_value.freqstr - ) - raise IncompatibleFrequency(msg) - - fill_value = fill_value.ordinal - else: - msg = "'fill_value' should be a Period. Got '{}'." - raise ValueError(msg.format(fill_value)) + fill_value = self._validate_fill_value(fill_value) new_values = algos.take(self._data, indices, diff --git a/pandas/core/arrays/timedeltas.py b/pandas/core/arrays/timedeltas.py index 397297c1b88d0..8e0482d25bc3e 100644 --- a/pandas/core/arrays/timedeltas.py +++ b/pandas/core/arrays/timedeltas.py @@ -4,10 +4,11 @@ import numpy as np from pandas._libs import tslibs -from pandas._libs.tslibs import Timedelta, Timestamp, NaT +from pandas._libs.tslibs import Timedelta, Timestamp, NaT, iNaT from pandas._libs.tslibs.fields import get_timedelta_field from pandas._libs.tslibs.timedeltas import array_to_timedelta64 +from pandas.util._decorators import Appender from pandas import compat from pandas.core.dtypes.common import ( @@ -178,6 +179,20 @@ def _generate_range(cls, start, end, periods, freq, closed=None): return index + # ---------------------------------------------------------------- + # ExtensionArray Interface + + @Appender(dtl.DatetimeLikeArrayMixin._validate_fill_value.__doc__) + def _validate_fill_value(self, fill_value): + if isna(fill_value): + fill_value = iNaT + elif isinstance(fill_value, (timedelta, np.timedelta64, Tick)): + fill_value = Timedelta(fill_value).value + else: + raise ValueError("'fill_value' should be a Timedelta. " + "Got '{got}'.".format(got=fill_value)) + return fill_value + # ---------------------------------------------------------------- # Arithmetic Methods diff --git a/pandas/tests/arrays/test_datetimelike.py b/pandas/tests/arrays/test_datetimelike.py index 3d5c810402fba..f38af233cb06e 100644 --- a/pandas/tests/arrays/test_datetimelike.py +++ b/pandas/tests/arrays/test_datetimelike.py @@ -56,7 +56,82 @@ def timedelta_index(request): return pd.TimedeltaIndex(['1 Day', '3 Hours', 'NaT']) +def index_to_array(index): + """ + Helper function to construct a Datetime/Timedelta/Period Array from an + instance of the corresponding Index subclass. + """ + if isinstance(index, pd.DatetimeIndex): + return DatetimeArrayMixin(index) + elif isinstance(index, pd.TimedeltaIndex): + return TimedeltaArrayMixin(index) + elif isinstance(index, pd.PeriodIndex): + return PeriodArray(index) + else: + raise TypeError(type(index)) + + +class SharedTests(object): + index_cls = None + + def test_take(self): + data = np.arange(100, dtype='i8') + np.random.shuffle(data) + + idx = self.index_cls._simple_new(data, freq='D') + arr = index_to_array(idx) + + takers = [1, 4, 94] + result = arr.take(takers) + expected = idx.take(takers) + + tm.assert_index_equal(self.index_cls(result), expected) + + takers = np.array([1, 4, 94]) + result = arr.take(takers) + expected = idx.take(takers) + + tm.assert_index_equal(self.index_cls(result), expected) + + def test_take_fill(self): + data = np.arange(10, dtype='i8') + + idx = self.index_cls._simple_new(data, freq='D') + arr = index_to_array(idx) + + result = arr.take([-1, 1], allow_fill=True, fill_value=None) + assert result[0] is pd.NaT + + result = arr.take([-1, 1], allow_fill=True, fill_value=np.nan) + assert result[0] is pd.NaT + + result = arr.take([-1, 1], allow_fill=True, fill_value=pd.NaT) + assert result[0] is pd.NaT + + with pytest.raises(ValueError): + arr.take([0, 1], allow_fill=True, fill_value=2) + + with pytest.raises(ValueError): + arr.take([0, 1], allow_fill=True, fill_value=2.0) + + with pytest.raises(ValueError): + arr.take([0, 1], allow_fill=True, + fill_value=pd.Timestamp.now().time) + + def test_concat_same_type(self): + data = np.arange(10, dtype='i8') + + idx = self.index_cls._simple_new(data, freq='D').insert(0, pd.NaT) + arr = index_to_array(idx) + + result = arr._concat_same_type([arr[:-1], arr[1:], arr]) + expected = idx._concat_same_dtype([idx[:-1], idx[1:], idx], None) + + tm.assert_index_equal(self.index_cls(result), expected) + + class TestDatetimeArray(object): + index_cls = pd.DatetimeIndex def test_from_dti(self, tz_naive_fixture): tz = tz_naive_fixture @@ -127,8 +202,45 @@ def test_int_properties(self, datetime_index, propname): tm.assert_numpy_array_equal(result, expected) + def test_take_fill_valid(self, datetime_index, tz_naive_fixture): + dti = datetime_index.tz_localize(tz_naive_fixture) + arr = index_to_array(dti) + + now = pd.Timestamp.now().tz_localize(dti.tz) + result = arr.take([-1, 1], allow_fill=True, fill_value=now) + assert result[0] == now + + with pytest.raises(ValueError): + # fill_value Timedelta invalid + arr.take([-1, 1], allow_fill=True, fill_value=now - now) + + with pytest.raises(ValueError): + # fill_value Period invalid + arr.take([-1, 1], allow_fill=True, fill_value=pd.Period('2014Q1')) + + tz = None if dti.tz is not None else 'US/Eastern' + now = pd.Timestamp.now().tz_localize(tz) + with pytest.raises(TypeError): + # Timestamp with mismatched tz-awareness + arr.take([-1, 1], allow_fill=True, fill_value=now) + + def test_concat_same_type_invalid(self, datetime_index): + # different timezones + dti = datetime_index + arr = DatetimeArrayMixin(dti) + + if arr.tz is None: + other = arr.tz_localize('UTC') + else: + other = arr.tz_localize(None) + + with pytest.raises(AssertionError): + arr._concat_same_type([arr, other]) + class TestTimedeltaArray(object): + index_cls = pd.TimedeltaIndex + def test_from_tdi(self): tdi = pd.TimedeltaIndex(['1 Day', '3 Hours']) arr = TimedeltaArrayMixin(tdi) @@ -175,8 +287,38 @@ def test_int_properties(self, timedelta_index, propname): tm.assert_numpy_array_equal(result, expected) + def test_take_fill_valid(self, timedelta_index): + tdi = timedelta_index + arr = index_to_array(tdi) + + td1 = pd.Timedelta(days=1) + result = arr.take([-1, 1], allow_fill=True, fill_value=td1) + assert result[0] == td1 + + now = pd.Timestamp.now() + with pytest.raises(ValueError): + # fill_value Timestamp invalid + arr.take([0, 1], allow_fill=True, fill_value=now) + + with pytest.raises(ValueError): + # fill_value Period invalid + arr.take([0, 1], allow_fill=True, fill_value=now.to_period('D')) + + def test_concat_same_type_invalid(self, timedelta_index): + # different freqs + tdi = timedelta_index + arr = TimedeltaArrayMixin(tdi) + other = pd.timedelta_range('1D', periods=5, freq='2D') + # FIXME: TimedeltaArray should inherit freq='2D' without specifying it + other = TimedeltaArrayMixin(other, freq='2D') + assert other.freq != arr.freq + + with pytest.raises(AssertionError): + arr._concat_same_type([arr, other]) + class TestPeriodArray(object): + index_cls = pd.PeriodIndex def test_from_pi(self, period_index): pi = period_index From e309ab94ef38c77136fef733613c600761899b99 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Wed, 31 Oct 2018 16:25:53 -0700 Subject: [PATCH 2/7] implement from_sequence, from_factorized, values_for_factore, copy --- pandas/core/arrays/datetimelike.py | 56 ++++++++++++++++++++++++++++++ pandas/core/arrays/period.py | 22 ------------ 2 files changed, 56 insertions(+), 22 deletions(-) diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index 3ba02ce8cbf80..3a11474acb9b2 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -144,6 +144,10 @@ def asi8(self): # ------------------------------------------------------------------ # Array-like Methods + @property + def nbytes(self): + return self.asi8.nbytes + @property def shape(self): return (len(self),) @@ -211,6 +215,31 @@ def astype(self, dtype, copy=True): # ------------------------------------------------------------------ # ExtensionArray Interface + # isna + # __getitem__ + # __len__ + # nbytes + # take + # _concat_same_type + # copy + # _from_factorized + # factorize / _values_for_factorize + # _from_sequence + # unique + # + # dtype + # + # dropna + # + #* _formatting_values + #* fillna + #* argsort / _values_for_argsort + #* _reduce + + def unique(self): + from pandas.core.algorithms import unique1d + result = unique1d(self.asi8) + return self._shallow_copy(result) def _validate_fill_value(self, fill_value): """ @@ -254,9 +283,36 @@ def _concat_same_type(cls, to_concat): values = np.concatenate([x._data for x in to_concat]) return cls._simple_new(values, freq=freq) + def copy(self, deep=False): + # TODO: should `deep` determine whether we copy self.asi8? + if is_datetime64tz_dtype(self): + return type(self)(self.asi8.copy(), tz=self.tz, freq=self.freq) + return type(self)(self.asi8.copy(), freq=self.freq) + + def _values_for_factorize(self): + return self.asi8, iNaT + + @classmethod + def _from_factorized(cls, values, original): + if is_datetime64tz_dtype(original): + return cls(values, tz=original.tz, freq=original.freq) + return cls(values, freq=original.freq) + + @classmethod + def _from_sequence(cls, scalars, dtype=None, copy=False): + arr = np.asarray(scalars, dtype=object) + if copy: + arr = arr.copy() + + # If necessary this will infer tz from dtype + return cls(arr, dtype=dtype) + # ------------------------------------------------------------------ # Null Handling + def isna(self): + return self._isnan + @property # NB: override with cache_readonly in immutable subclasses def _isnan(self): """ return if each value is nan""" diff --git a/pandas/core/arrays/period.py b/pandas/core/arrays/period.py index 320af666fe55c..3e9f2e3fe1ec0 100644 --- a/pandas/core/arrays/period.py +++ b/pandas/core/arrays/period.py @@ -211,14 +211,6 @@ def _from_sequence(cls, scalars, dtype=None, copy=False): ordinals = libperiod.extract_ordinals(periods, freq) return cls(ordinals, freq=freq) - def _values_for_factorize(self): - return self.asi8, iNaT - - @classmethod - def _from_factorized(cls, values, original): - # type: (Sequence[Optional[Period]], PeriodArray) -> PeriodArray - return cls(values, freq=original.freq) - @classmethod def _from_datetime64(cls, data, freq, tz=None): """Construct a PeriodArray from a datetime64 array @@ -257,14 +249,6 @@ def _generate_range(cls, start, end, periods, freq, fields): return subarr, freq - @classmethod - def _concat_same_type(cls, to_concat): - freq = {x.freq for x in to_concat} - assert len(freq) == 1 - freq = list(freq)[0] - values = np.concatenate([x._data for x in to_concat]) - return cls(values, freq=freq) - # -------------------------------------------------------------------- # Data / Attributes @property @@ -400,9 +384,6 @@ def take(self, indices, allow_fill=False, fill_value=None): return type(self)(new_values, self.freq) - def isna(self): - return self._data == iNaT - def fillna(self, value=None, method=None, limit=None): # TODO(#20300) # To avoid converting to object, we re-implement here with the changes @@ -438,9 +419,6 @@ def fillna(self, value=None, method=None, limit=None): new_values = self.copy() return new_values - def copy(self, deep=False): - return type(self)(self._data.copy(), freq=self.freq) - def value_counts(self, dropna=False): from pandas import Series, PeriodIndex From 423a3572cd440aaccc97fcb3cc4f485a11adcaea Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Wed, 31 Oct 2018 16:44:35 -0700 Subject: [PATCH 3/7] rename DatetimeArrayMixin-->DatetimeArray --- pandas/core/arrays/__init__.py | 2 +- pandas/core/arrays/datetimelike.py | 4 +- pandas/core/arrays/datetimes.py | 6 +- pandas/core/arrays/period.py | 4 +- pandas/core/arrays/timedeltas.py | 10 ++-- pandas/core/indexes/datetimes.py | 70 ++++++++++++------------ pandas/core/indexes/period.py | 2 +- pandas/tests/arrays/test_datetimelike.py | 22 ++++---- 8 files changed, 60 insertions(+), 60 deletions(-) diff --git a/pandas/core/arrays/__init__.py b/pandas/core/arrays/__init__.py index ea8837332633a..1c028bba18eae 100644 --- a/pandas/core/arrays/__init__.py +++ b/pandas/core/arrays/__init__.py @@ -2,7 +2,7 @@ ExtensionOpsMixin, ExtensionScalarOpsMixin) from .categorical import Categorical # noqa -from .datetimes import DatetimeArrayMixin # noqa +from .datetimes import DatetimeArray # noqa from .interval import IntervalArray # noqa from .period import PeriodArray, period_array # noqa from .timedeltas import TimedeltaArrayMixin # noqa diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index 3a11474acb9b2..f04a7abd97eb7 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -838,8 +838,8 @@ def __rsub__(self, other): # we need to wrap in DatetimeArray/Index and flip the operation if not isinstance(other, DatetimeLikeArrayMixin): # Avoid down-casting DatetimeIndex - from pandas.core.arrays import DatetimeArrayMixin - other = DatetimeArrayMixin(other) + from pandas.core.arrays import DatetimeArray + other = DatetimeArray(other) return other - self elif (is_datetime64_any_dtype(self) and hasattr(other, 'dtype') and not is_datetime64_any_dtype(other)): diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index 13277a272bb83..c769e7201616f 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -150,7 +150,7 @@ def wrapper(self, other): return compat.set_function_name(wrapper, opname, cls) -class DatetimeArrayMixin(dtl.DatetimeLikeArrayMixin): +class DatetimeArray(dtl.DatetimeLikeArrayMixin): """ Assumes that subclass __new__/__init__ defines: tz @@ -1343,8 +1343,8 @@ def to_julian_date(self): ) / 24.0) -DatetimeArrayMixin._add_comparison_ops() -DatetimeArrayMixin._add_datetimelike_methods() +DatetimeArray._add_comparison_ops() +DatetimeArray._add_datetimelike_methods() def _generate_regular_range(cls, start, end, periods, freq): diff --git a/pandas/core/arrays/period.py b/pandas/core/arrays/period.py index 3e9f2e3fe1ec0..28b3f98c067ef 100644 --- a/pandas/core/arrays/period.py +++ b/pandas/core/arrays/period.py @@ -560,7 +560,7 @@ def to_timestamp(self, freq=None, how='start'): ------- DatetimeArray/Index """ - from pandas.core.arrays import DatetimeArrayMixin + from pandas.core.arrays import DatetimeArray how = libperiod._validate_end_alias(how) @@ -584,7 +584,7 @@ def to_timestamp(self, freq=None, how='start'): new_data = self.asfreq(freq, how=how) new_data = libperiod.periodarr_to_dt64arr(new_data.asi8, base) - return DatetimeArrayMixin(new_data, freq='infer') + return DatetimeArray(new_data, freq='infer') # ------------------------------------------------------------------ # Formatting diff --git a/pandas/core/arrays/timedeltas.py b/pandas/core/arrays/timedeltas.py index 8e0482d25bc3e..56afc0e749883 100644 --- a/pandas/core/arrays/timedeltas.py +++ b/pandas/core/arrays/timedeltas.py @@ -225,15 +225,15 @@ def _add_datetime_arraylike(self, other): """Add DatetimeArray/Index or ndarray[datetime64] to TimedeltaArray""" if isinstance(other, np.ndarray): # At this point we have already checked that dtype is datetime64 - from pandas.core.arrays import DatetimeArrayMixin - other = DatetimeArrayMixin(other) + from pandas.core.arrays import DatetimeArray + other = DatetimeArray(other) # defer to implementation in DatetimeArray return other + self def _add_datetimelike_scalar(self, other): # adding a timedeltaindex to a datetimelike - from pandas.core.arrays import DatetimeArrayMixin + from pandas.core.arrays import DatetimeArray assert other is not NaT other = Timestamp(other) @@ -241,13 +241,13 @@ def _add_datetimelike_scalar(self, other): # In this case we specifically interpret NaT as a datetime, not # the timedelta interpretation we would get by returning self + NaT result = self.asi8.view('m8[ms]') + NaT.to_datetime64() - return DatetimeArrayMixin(result) + return DatetimeArray(result) i8 = self.asi8 result = checked_add_with_arr(i8, other.value, arr_mask=self._isnan) result = self._maybe_mask_results(result) - return DatetimeArrayMixin(result, tz=other.tz) + return DatetimeArray(result, tz=other.tz) def _addsub_offset_array(self, other, op): # Add or subtract Array-like of DateOffset objects diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index 39f247a7c4cfe..60afbe4a39d03 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -29,7 +29,7 @@ from pandas.core.dtypes.missing import isna import pandas.core.dtypes.concat as _concat -from pandas.core.arrays.datetimes import DatetimeArrayMixin, _to_m8 +from pandas.core.arrays.datetimes import DatetimeArray, _to_m8 from pandas.core.arrays import datetimelike as dtl from pandas.core.indexes.base import Index, _index_shared_docs @@ -67,7 +67,7 @@ def _new_DatetimeIndex(cls, d): return result -class DatetimeIndex(DatetimeArrayMixin, DatelikeOps, TimelikeOps, +class DatetimeIndex(DatetimeArray, DatelikeOps, TimelikeOps, DatetimeIndexOpsMixin, Int64Index): """ Immutable ndarray of datetime64 data, represented internally as int64, and @@ -174,7 +174,7 @@ class DatetimeIndex(DatetimeArrayMixin, DatelikeOps, TimelikeOps, PeriodIndex : Index of Period data pandas.to_datetime : Convert argument to datetime """ - _resolution = cache_readonly(DatetimeArrayMixin._resolution.fget) + _resolution = cache_readonly(DatetimeArray._resolution.fget) _typ = 'datetimeindex' _join_precedence = 10 @@ -215,8 +215,8 @@ def _join_i8_wrapper(joinf, **kwargs): _is_numeric_dtype = False _infer_as_myclass = True - _timezone = cache_readonly(DatetimeArrayMixin._timezone.fget) - is_normalized = cache_readonly(DatetimeArrayMixin.is_normalized.fget) + _timezone = cache_readonly(DatetimeArray._timezone.fget) + is_normalized = cache_readonly(DatetimeArray.is_normalized.fget) def __new__(cls, data=None, freq=None, start=None, end=None, periods=None, tz=None, @@ -247,7 +247,7 @@ def __new__(cls, data=None, return result if not isinstance(data, (np.ndarray, Index, ABCSeries, - DatetimeArrayMixin)): + DatetimeArray)): if is_scalar(data): raise ValueError('DatetimeIndex() must be called with a ' 'collection of some kind, %s was passed' @@ -265,7 +265,7 @@ def __new__(cls, data=None, data = tools.to_datetime(data, dayfirst=dayfirst, yearfirst=yearfirst) - if isinstance(data, DatetimeArrayMixin): + if isinstance(data, DatetimeArray): if tz is None: tz = data.tz elif data.tz is None: @@ -1107,41 +1107,41 @@ def slice_indexer(self, start=None, end=None, step=None, kind=None): else: raise - year = wrap_field_accessor(DatetimeArrayMixin.year) - month = wrap_field_accessor(DatetimeArrayMixin.month) - day = wrap_field_accessor(DatetimeArrayMixin.day) - hour = wrap_field_accessor(DatetimeArrayMixin.hour) - minute = wrap_field_accessor(DatetimeArrayMixin.minute) - second = wrap_field_accessor(DatetimeArrayMixin.second) - microsecond = wrap_field_accessor(DatetimeArrayMixin.microsecond) - nanosecond = wrap_field_accessor(DatetimeArrayMixin.nanosecond) - weekofyear = wrap_field_accessor(DatetimeArrayMixin.weekofyear) + 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(DatetimeArrayMixin.dayofweek) + dayofweek = wrap_field_accessor(DatetimeArray.dayofweek) weekday = dayofweek - weekday_name = wrap_field_accessor(DatetimeArrayMixin.weekday_name) + weekday_name = wrap_field_accessor(DatetimeArray.weekday_name) - dayofyear = wrap_field_accessor(DatetimeArrayMixin.dayofyear) - quarter = wrap_field_accessor(DatetimeArrayMixin.quarter) - days_in_month = wrap_field_accessor(DatetimeArrayMixin.days_in_month) + 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(DatetimeArrayMixin.is_month_start) - is_month_end = wrap_field_accessor(DatetimeArrayMixin.is_month_end) - is_quarter_start = wrap_field_accessor(DatetimeArrayMixin.is_quarter_start) - is_quarter_end = wrap_field_accessor(DatetimeArrayMixin.is_quarter_end) - is_year_start = wrap_field_accessor(DatetimeArrayMixin.is_year_start) - is_year_end = wrap_field_accessor(DatetimeArrayMixin.is_year_end) - is_leap_year = wrap_field_accessor(DatetimeArrayMixin.is_leap_year) - - to_perioddelta = wrap_array_method(DatetimeArrayMixin.to_perioddelta, + 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) + + to_perioddelta = wrap_array_method(DatetimeArray.to_perioddelta, False) - to_period = wrap_array_method(DatetimeArrayMixin.to_period, True) - normalize = wrap_array_method(DatetimeArrayMixin.normalize, True) - to_julian_date = wrap_array_method(DatetimeArrayMixin.to_julian_date, + 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(DatetimeArrayMixin.month_name, True) - day_name = wrap_array_method(DatetimeArrayMixin.day_name, True) + month_name = wrap_array_method(DatetimeArray.month_name, True) + day_name = wrap_array_method(DatetimeArray.day_name, True) @Substitution(klass='DatetimeIndex') @Appender(_shared_docs['searchsorted']) diff --git a/pandas/core/indexes/period.py b/pandas/core/indexes/period.py index c3728d8d956de..c439c1545f15b 100644 --- a/pandas/core/indexes/period.py +++ b/pandas/core/indexes/period.py @@ -923,7 +923,7 @@ def _add_datetimelike_methods(cls): add in the datetimelike methods (as we may have to override the superclass) """ - # TODO(DatetimeArray): move this up to DatetimeArrayMixin + # TODO(DatetimeArray): move this up to DatetimeArray def __add__(self, other): # dispatch to ExtensionArray implementation diff --git a/pandas/tests/arrays/test_datetimelike.py b/pandas/tests/arrays/test_datetimelike.py index f38af233cb06e..ce7923eb672b3 100644 --- a/pandas/tests/arrays/test_datetimelike.py +++ b/pandas/tests/arrays/test_datetimelike.py @@ -5,7 +5,7 @@ import pandas as pd import pandas.util.testing as tm from pandas.core.arrays import ( - DatetimeArrayMixin, PeriodArray, TimedeltaArrayMixin + DatetimeArray, PeriodArray, TimedeltaArrayMixin ) @@ -62,7 +62,7 @@ def index_to_array(index): instance of the corresponding Index subclass. """ if isinstance(index, pd.DatetimeIndex): - return DatetimeArrayMixin(index) + return DatetimeArray(index) elif isinstance(index, pd.TimedeltaIndex): return TimedeltaArrayMixin(index) elif isinstance(index, pd.PeriodIndex): @@ -136,7 +136,7 @@ class TestDatetimeArray(object): def test_from_dti(self, tz_naive_fixture): tz = tz_naive_fixture dti = pd.date_range('2016-01-01', periods=3, tz=tz) - arr = DatetimeArrayMixin(dti) + arr = DatetimeArray(dti) assert list(dti) == list(arr) # Check that Index.__new__ knows what to do with DatetimeArray @@ -147,7 +147,7 @@ def test_from_dti(self, tz_naive_fixture): def test_astype_object(self, tz_naive_fixture): tz = tz_naive_fixture dti = pd.date_range('2016-01-01', periods=3, tz=tz) - arr = DatetimeArrayMixin(dti) + arr = DatetimeArray(dti) asobj = arr.astype('O') assert isinstance(asobj, np.ndarray) assert asobj.dtype == 'O' @@ -157,7 +157,7 @@ def test_astype_object(self, tz_naive_fixture): def test_to_perioddelta(self, datetime_index, freqstr): # GH#23113 dti = datetime_index - arr = DatetimeArrayMixin(dti) + arr = DatetimeArray(dti) expected = dti.to_perioddelta(freq=freqstr) result = arr.to_perioddelta(freq=freqstr) @@ -170,7 +170,7 @@ def test_to_perioddelta(self, datetime_index, freqstr): @pytest.mark.parametrize('freqstr', ['D', 'B', 'W', 'M', 'Q', 'Y']) def test_to_period(self, datetime_index, freqstr): dti = datetime_index - arr = DatetimeArrayMixin(dti) + arr = DatetimeArray(dti) expected = dti.to_period(freq=freqstr) result = arr.to_period(freq=freqstr) @@ -184,7 +184,7 @@ def test_to_period(self, datetime_index, freqstr): def test_bool_properties(self, datetime_index, propname): # in this case _bool_ops is just `is_leap_year` dti = datetime_index - arr = DatetimeArrayMixin(dti) + arr = DatetimeArray(dti) assert dti.freq == arr.freq result = getattr(arr, propname) @@ -195,7 +195,7 @@ def test_bool_properties(self, datetime_index, propname): @pytest.mark.parametrize('propname', pd.DatetimeIndex._field_ops) def test_int_properties(self, datetime_index, propname): dti = datetime_index - arr = DatetimeArrayMixin(dti) + arr = DatetimeArray(dti) result = getattr(arr, propname) expected = np.array(getattr(dti, propname), dtype=result.dtype) @@ -227,7 +227,7 @@ def test_take_fill_valid(self, datetime_index, tz_naive_fixture): def test_concat_same_type_invalid(self, datetime_index): # different timezones dti = datetime_index - arr = DatetimeArrayMixin(dti) + arr = DatetimeArray(dti) if arr.tz is None: other = arr.tz_localize('UTC') @@ -343,9 +343,9 @@ def test_to_timestamp(self, how, period_index): pi = period_index arr = PeriodArray(pi) - expected = DatetimeArrayMixin(pi.to_timestamp(how=how)) + expected = DatetimeArray(pi.to_timestamp(how=how)) result = arr.to_timestamp(how=how) - assert isinstance(result, DatetimeArrayMixin) + assert isinstance(result, DatetimeArray) # placeholder until these become actual EA subclasses and we can use # an EA-specific tm.assert_ function From 47689bea930cc5665ccc554f9b9ccdeff902680c Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Thu, 1 Nov 2018 21:14:37 -0700 Subject: [PATCH 4/7] implement unique --- pandas/core/indexes/datetimelike.py | 1 + pandas/core/indexes/datetimes.py | 5 ++++- pandas/core/indexes/timedeltas.py | 6 ++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index 14325f42ff0d8..5096c745cccd1 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -212,6 +212,7 @@ def ceil(self, freq, ambiguous='raise'): class DatetimeIndexOpsMixin(DatetimeLikeArrayMixin): """ common ops mixin to support a unified interface datetimelike Index """ + copy = Index.copy # DatetimeLikeArrayMixin assumes subclasses are mutable, so these are # properties there. They can be made into cache_readonly for Index diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index 60afbe4a39d03..d01b639f4638b 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -569,7 +569,10 @@ def unique(self, level=None): naive = type(self)(self._ndarray_values, copy=False) else: naive = self - result = super(DatetimeIndex, naive).unique(level=level) + + if level is not None: + self._validate_index_level(level) + result = DatetimeArray.unique(self) return self._shallow_copy(result.values) def union(self, other): diff --git a/pandas/core/indexes/timedeltas.py b/pandas/core/indexes/timedeltas.py index e5da21478d0a4..baf52dd7a4685 100644 --- a/pandas/core/indexes/timedeltas.py +++ b/pandas/core/indexes/timedeltas.py @@ -614,6 +614,12 @@ def delete(self, loc): return TimedeltaIndex(new_tds, name=self.name, freq=freq) + def unique(self, level=None): + if level is not None: + self._validate_index_level(level) + result = TimedeltaArrayMixin.unique(self) + return self._shallow_copy(result.values) + TimedeltaIndex._add_comparison_ops() TimedeltaIndex._add_numeric_methods() From dc4a264dc79b0e80cd90134104e9a7be8e4b9824 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Fri, 2 Nov 2018 08:29:35 -0700 Subject: [PATCH 5/7] commit prelim some I can rebase --- pandas/core/arrays/datetimelike.py | 17 ++++++-- pandas/core/arrays/datetimes.py | 19 +++++--- pandas/core/indexes/datetimelike.py | 14 ++++-- pandas/core/indexes/datetimes.py | 67 ++++++++++++++++++----------- pandas/core/series.py | 3 ++ 5 files changed, 82 insertions(+), 38 deletions(-) diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index f04a7abd97eb7..68652a1f0f6ee 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -134,12 +134,14 @@ def __iter__(self): @property def values(self): """ return the underlying data as an ndarray """ - return self._data.view(np.ndarray) + if is_timedelta64_dtype(self): + return self._data.view(np.ndarray) + return self._data @property def asi8(self): # do not cache or you'll create a memory leak - return self.values.view('i8') + return self._data.view('i8') # ------------------------------------------------------------------ # Array-like Methods @@ -264,7 +266,7 @@ def take(self, indices, allow_fill=False, fill_value=None): if allow_fill: fill_value = self._validate_fill_value(fill_value) - new_values = take(self._data, + new_values = take(self.asi8, indices, allow_fill=allow_fill, fill_value=fill_value) @@ -280,7 +282,7 @@ def _concat_same_type(cls, to_concat): freqs = {x.freq for x in to_concat} assert len(freqs) == 1 freq = list(freqs)[0] - values = np.concatenate([x._data for x in to_concat]) + values = np.concatenate([x.asi8 for x in to_concat]) return cls._simple_new(values, freq=freq) def copy(self, deep=False): @@ -289,6 +291,13 @@ def copy(self, deep=False): return type(self)(self.asi8.copy(), tz=self.tz, freq=self.freq) return type(self)(self.asi8.copy(), freq=self.freq) + # Following how PeriodArray does this + # TODO: ignoring `type`? + def view(self, dtype=None, type=None): + if dtype is None or dtype is __builtins__['type'](self): + return self + return self._ndarray_values.view(dtype=dtype) + def _values_for_factorize(self): return self.asi8, iNaT diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index c769e7201616f..811d69907a40a 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -34,6 +34,7 @@ from pandas.tseries.offsets import Tick, generate_range from pandas.core.arrays import datetimelike as dtl +from pandas.core.arrays.base import ExtensionArray _midnight = time(0, 0) @@ -119,7 +120,7 @@ def wrapper(self, other): if isinstance(other, list): # FIXME: This can break for object-dtype with mixed types other = type(self)(other) - elif not isinstance(other, (np.ndarray, ABCIndexClass, ABCSeries)): + elif not isinstance(other, (np.ndarray, ABCIndexClass, ABCSeries, DatetimeArray)): # Following Timestamp convention, __eq__ is all-False # and __ne__ is all True, others raise TypeError. return ops.invalid_comparison(self, other, op) @@ -132,7 +133,7 @@ def wrapper(self, other): return ops.invalid_comparison(self, other, op) else: self._assert_tzawareness_compat(other) - result = meth(self, np.asarray(other)) + result = meth(self, type(self)(other))#np.asarray(other)) result = com.values_from_object(result) @@ -150,7 +151,7 @@ def wrapper(self, other): return compat.set_function_name(wrapper, opname, cls) -class DatetimeArray(dtl.DatetimeLikeArrayMixin): +class DatetimeArray(dtl.DatetimeLikeArrayMixin, ExtensionArray): """ Assumes that subclass __new__/__init__ defines: tz @@ -273,7 +274,7 @@ def _generate_range(cls, start, end, periods, freq, tz=None, if tz is not None and getattr(index, 'tz', None) is None: arr = conversion.tz_localize_to_utc( - ensure_int64(index.values), + ensure_int64(index.asi8), tz, ambiguous=ambiguous) index = cls(arr) @@ -296,7 +297,7 @@ def _generate_range(cls, start, end, periods, freq, tz=None, if not right_closed and len(index) and index[-1] == end: index = index[:-1] - return cls._simple_new(index.values, freq=freq, tz=tz) + return cls._simple_new(index.asi8, freq=freq, tz=tz) # ----------------------------------------------------------------- # Descriptive Properties @@ -392,6 +393,10 @@ def __iter__(self): # ---------------------------------------------------------------- # ExtensionArray Interface + @property + def _ndarray_values(self): + return self._data + @Appender(dtl.DatetimeLikeArrayMixin._validate_fill_value.__doc__) def _validate_fill_value(self, fill_value): if isna(fill_value): @@ -414,7 +419,7 @@ def _concat_same_type(cls, to_concat): assert len(tzs) == 1 tz = list(tzs)[0] - values = np.concatenate([x._data for x in to_concat]) + values = np.concatenate([x.asi8 for x in to_concat]) return cls._simple_new(values, freq=freq, tz=tz) # ----------------------------------------------------------------- @@ -852,7 +857,7 @@ def to_period(self, freq=None): freq = get_period_alias(freq) - return PeriodArray._from_datetime64(self.values, freq, tz=self.tz) + return PeriodArray._from_datetime64(self.asi8, freq, tz=self.tz) def to_perioddelta(self, freq): """ diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index 5096c745cccd1..3059cdc5eb413 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -23,6 +23,7 @@ is_bool_dtype, is_period_dtype, is_categorical_dtype, + is_timedelta64_dtype, is_datetime_or_timedelta_dtype, is_float_dtype, is_integer_dtype, @@ -757,7 +758,7 @@ def _ensure_datetimelike_to_i8(other, to_utc=False): try: return np.array(other, copy=False).view('i8') except TypeError: - # period array cannot be coerces to int + # period array cannot be coerced to int other = Index(other) return other.asi8 @@ -792,7 +793,10 @@ def wrap_array_method(method, pin_name=False): method """ def index_method(self, *args, **kwargs): - result = method(self, *args, **kwargs) + if is_timedelta64_dtype(self): + result = method(self, *args, **kwargs) + else: + result = method(self._data, *args, **kwargs) # Index.__new__ will choose the appropriate subclass to return result = Index(result) @@ -821,7 +825,11 @@ def wrap_field_accessor(prop): fget = prop.fget def f(self): - result = fget(self) + if is_timedelta64_dtype(self): + result = fget(self) + else: + result = fget(self._data) + if is_bool_dtype(result): # return numpy array b/c there is no BoolIndex return result diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index d01b639f4638b..32991bb266aa7 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -265,7 +265,7 @@ def __new__(cls, data=None, data = tools.to_datetime(data, dayfirst=dayfirst, yearfirst=yearfirst) - if isinstance(data, DatetimeArray): + if isinstance(data, (DatetimeArray, DatetimeIndex)): if tz is None: tz = data.tz elif data.tz is None: @@ -277,7 +277,7 @@ def __new__(cls, data=None, 'set specified tz: {1}') raise TypeError(msg.format(data.tz, tz)) - subarr = data.values + subarr = data.asi8 if freq is None: freq = data.freq @@ -329,6 +329,13 @@ def _simple_new(cls, values, name=None, freq=None, tz=None, we require the we have a dtype compat for the values if we are passed a non-dtype compat, then coerce using the constructor """ + if isinstance(values, (DatetimeArray, DatetimeIndex)): + # TODO: should we just pin this and skip the rigmarole? + assert tz == values.tz, (tz, values.tz) + assert freq == values.freq, (freq, values.freq) + values = values.asi8 + + assert not isinstance(values, (DatetimeArray, DatetimeIndex)) if getattr(values, 'dtype', None) is None: # empty, but with dtype compat @@ -346,20 +353,28 @@ def _simple_new(cls, values, name=None, freq=None, tz=None, assert isinstance(values, np.ndarray), "values is not an np.ndarray" assert is_datetime64_dtype(values) - result = super(DatetimeIndex, cls)._simple_new(values, freq, tz, - **kwargs) + arr = DatetimeArray(values, freq=freq, tz=tz) + result = object.__new__(cls) + result._data = arr result.name = name result._reset_identity() return result + @property + def _tz(self): + return self._data._tz + + @property + def _freq(self): + return self._data._freq + @property def _values(self): - # tz-naive -> ndarray - # tz-aware -> DatetimeIndex - if self.tz is not None: - return self - else: - return self.values + return self._data + + @property + def _ndarray_values(self): + return self._data._data # M8[ns] @property def tz(self): @@ -372,26 +387,36 @@ def tz(self, value): raise AttributeError("Cannot directly set timezone. Use tz_localize() " "or tz_convert() as appropriate") + @property + def asi8(self): + return self._data.asi8 + + def __getitem__(self, key): + result = self._data[key] + if is_scalar(result): + return result + return type(self)(result) + @property def size(self): # TODO: Remove this when we have a DatetimeTZArray # Necessary to avoid recursion error since DTI._values is a DTI # for TZ-aware - return self._ndarray_values.size + return self._data.size @property def shape(self): # TODO: Remove this when we have a DatetimeTZArray # Necessary to avoid recursion error since DTI._values is a DTI # for TZ-aware - return self._ndarray_values.shape + return self._data.shape @property def nbytes(self): # TODO: Remove this when we have a DatetimeTZArray # Necessary to avoid recursion error since DTI._values is a DTI # for TZ-aware - return self._ndarray_values.nbytes + return self._data.nbytes def _mpl_repr(self): # how to represent ourselves to matplotlib @@ -561,19 +586,11 @@ def snap(self, freq='S'): # TODO: what about self.name? if so, use shallow_copy? def unique(self, level=None): - # Override here since IndexOpsMixin.unique uses self._values.unique - # For DatetimeIndex with TZ, that's a DatetimeIndex -> recursion error - # So we extract the tz-naive DatetimeIndex, unique that, and wrap the - # result with out TZ. - if self.tz is not None: - naive = type(self)(self._ndarray_values, copy=False) - else: - naive = self - if level is not None: self._validate_index_level(level) - result = DatetimeArray.unique(self) - return self._shallow_copy(result.values) + + result = self._data.unique() + return self._shallow_copy(result) def union(self, other): """ @@ -1137,6 +1154,8 @@ def slice_indexer(self, start=None, end=None, step=None, kind=None): 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) diff --git a/pandas/core/series.py b/pandas/core/series.py index 6cc5acc4a61d0..c812126bccf61 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -4204,6 +4204,9 @@ def _try_cast(arr, take_fast_path): not (is_iterator(subarr) or isinstance(subarr, np.ndarray))): subarr = construct_1d_object_array_from_listlike(subarr) + elif type(subarr).__name__ == "DatetimeArray": + # kludge + pass elif not is_extension_type(subarr): subarr = construct_1d_ndarray_preserving_na(subarr, dtype, copy=copy) From 92923b248d79145477e6dd18f769dfa9bbb9a149 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Thu, 8 Nov 2018 11:09:55 -0800 Subject: [PATCH 6/7] update name --- pandas/core/arrays/datetimes.py | 2 +- pandas/core/indexes/datetimes.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index 8f8fd9ad51c9b..024776c08ebeb 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -208,7 +208,7 @@ def __new__(cls, values, freq=None, tz=None, dtype=None): # if dtype has an embedded tz, capture it tz = dtl.validate_tz_from_dtype(dtype, tz) - if isinstance(values, DatetimeArrayMixin): + if isinstance(values, DatetimeArray): # extract nanosecond unix timestamps values = values.asi8 if values.dtype == 'i8': diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index 917c26a02b390..27b8216145c6c 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -278,7 +278,7 @@ def __new__(cls, data=None, 'set specified tz: {1}') raise TypeError(msg.format(data.tz, tz)) - subarr = data.asi8 + subarr = data.asi8.view(_NS_DTYPE) if freq is None: freq = data.freq From cd6389242cc8302fba156d26e08dd6abf3011443 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Sun, 11 Nov 2018 09:43:56 -0800 Subject: [PATCH 7/7] update imports --- pandas/tests/arithmetic/conftest.py | 2 +- pandas/tests/arrays/test_datetimes.py | 2 +- pandas/tests/dtypes/test_generic.py | 4 ++-- pandas/util/testing.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pandas/tests/arithmetic/conftest.py b/pandas/tests/arithmetic/conftest.py index cf1abc6f79101..a570d91389a55 100644 --- a/pandas/tests/arithmetic/conftest.py +++ b/pandas/tests/arithmetic/conftest.py @@ -5,7 +5,7 @@ import pandas as pd from pandas.compat import long -from pandas.core.arrays import PeriodArray, DatetimeArrayMixin as DatetimeArray +from pandas.core.arrays import PeriodArray, DatetimeArray @pytest.fixture(params=[1, np.array(1, dtype=np.int64)]) diff --git a/pandas/tests/arrays/test_datetimes.py b/pandas/tests/arrays/test_datetimes.py index a15295cfbd81a..bcb8ea915160a 100644 --- a/pandas/tests/arrays/test_datetimes.py +++ b/pandas/tests/arrays/test_datetimes.py @@ -6,7 +6,7 @@ import numpy as np import pandas as pd -from pandas.core.arrays import DatetimeArrayMixin as DatetimeArray +from pandas.core.arrays import DatetimeArray import pandas.util.testing as tm diff --git a/pandas/tests/dtypes/test_generic.py b/pandas/tests/dtypes/test_generic.py index 53fa482bdeaef..7994d738c074f 100644 --- a/pandas/tests/dtypes/test_generic.py +++ b/pandas/tests/dtypes/test_generic.py @@ -19,8 +19,8 @@ class TestABCClasses(object): sparse_series = pd.Series([1, 2, 3]).to_sparse() sparse_array = pd.SparseArray(np.random.randn(10)) sparse_frame = pd.SparseDataFrame({'a': [1, -1, None]}) - datetime_array = pd.core.arrays.DatetimeArrayMixin(datetime_index) - timedelta_array = pd.core.arrays.TimedeltaArrayMixin(timedelta_index) + datetime_array = pd.core.arrays.DatetimeArray(datetime_index) + timedelta_array = pd.core.arrays.TimedeltaArray(timedelta_index) def test_abc_types(self): assert isinstance(pd.Index(['a', 'b', 'c']), gt.ABCIndex) diff --git a/pandas/util/testing.py b/pandas/util/testing.py index fd7012c87040f..f51b0cac60779 100644 --- a/pandas/util/testing.py +++ b/pandas/util/testing.py @@ -36,7 +36,7 @@ TimedeltaIndex, bdate_range) from pandas.core.algorithms import take_1d from pandas.core.arrays import ( - DatetimeArrayMixin as DatetimeArray, ExtensionArray, IntervalArray, + DatetimeArray, ExtensionArray, IntervalArray, PeriodArray, period_array) import pandas.core.common as com