diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index 54e1f0726d772..74bf90eb94065 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -2,7 +2,7 @@ import numpy as np -from pandas._libs import iNaT, NaT +from pandas._libs import lib, iNaT, NaT from pandas._libs.tslibs.timedeltas import delta_to_nanoseconds from pandas._libs.tslibs.period import ( DIFFERENT_FREQ_INDEX, IncompatibleFrequency) @@ -33,6 +33,12 @@ def _box_func(self): """ raise com.AbstractMethodError(self) + def _box_values(self, values): + """ + apply box func to passed values + """ + return lib.map_infer(values, self._box_func) + def __iter__(self): return (self._box_func(v) for v in self.asi8) @@ -46,6 +52,9 @@ def asi8(self): # do not cache or you'll create a memory leak return self.values.view('i8') + def __len__(self): + return len(self._data) + # ------------------------------------------------------------------ # Null Handling diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index 0e3e50278bbec..34749f3631fca 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -107,6 +107,32 @@ def offset(self, value): warnings.warn(msg, FutureWarning, stacklevel=2) self.freq = value + # ---------------------------------------------------------------- + # Array-like Methods + + def __iter__(self): + """ + Return an iterator over the boxed values + + Yields + ------- + tstamp : Timestamp + """ + + # convert in chunks of 10k for efficiency + data = self.asi8 + length = len(self) + chunksize = 10000 + chunks = int(length / chunksize) + 1 + for i in range(chunks): + start_i = i * chunksize + end_i = min((i + 1) * chunksize, length) + converted = tslib.ints_to_pydatetime(data[start_i:end_i], + tz=self.tz, freq=self.freq, + box="timestamp") + for v in converted: + yield v + # ----------------------------------------------------------------- # Comparison Methods diff --git a/pandas/core/arrays/period.py b/pandas/core/arrays/period.py index 78012688673cf..697c2ffc88050 100644 --- a/pandas/core/arrays/period.py +++ b/pandas/core/arrays/period.py @@ -10,6 +10,7 @@ Period, IncompatibleFrequency, DIFFERENT_FREQ_INDEX, get_period_field_arr) from pandas._libs.tslibs.timedeltas import delta_to_nanoseconds +from pandas._libs.tslibs.fields import isleapyear_arr from pandas.util._decorators import cache_readonly @@ -86,6 +87,11 @@ def freq(self, value): "The number of days in the month") daysinmonth = days_in_month + @property + def is_leap_year(self): + """ Logical indicating if the date belongs to a leap year """ + return isleapyear_arr(np.asarray(self.year)) + # ------------------------------------------------------------------ # Arithmetic Methods @@ -93,6 +99,22 @@ def _sub_datelike(self, other): assert other is not NaT return NotImplemented + def _sub_period(self, other): + # If the operation is well-defined, we return an object-Index + # of DateOffsets. Null entries are filled with pd.NaT + if self.freq != other.freq: + msg = DIFFERENT_FREQ_INDEX.format(self.freqstr, other.freqstr) + raise IncompatibleFrequency(msg) + + asi8 = self.asi8 + new_data = asi8 - other.ordinal + new_data = np.array([self.freq * x for x in new_data]) + + if self.hasnans: + new_data[self._isnan] = NaT + + return new_data + def _maybe_convert_timedelta(self, other): """ Convert timedelta-like input to an integer multiple of self.freq diff --git a/pandas/core/arrays/timedelta.py b/pandas/core/arrays/timedelta.py index 3bd2a0f0281e0..b93cff51bbde9 100644 --- a/pandas/core/arrays/timedelta.py +++ b/pandas/core/arrays/timedelta.py @@ -1,16 +1,28 @@ # -*- coding: utf-8 -*- +from datetime import timedelta + +import numpy as np from pandas._libs import tslib from pandas._libs.tslib import Timedelta, NaT from pandas._libs.tslibs.fields import get_timedelta_field +from pandas import compat + from pandas.core.dtypes.common import _TD_DTYPE +from pandas.core.dtypes.generic import ABCSeries +from pandas.core.dtypes.missing import isna from pandas.tseries.offsets import Tick from .datetimelike import DatetimeLikeArrayMixin +def _is_convertible_to_td(key): + return isinstance(key, (Tick, timedelta, + np.timedelta64, compat.string_types)) + + def _field_accessor(name, alias, docstring=None): def f(self): values = self.asi8 @@ -48,9 +60,53 @@ def _sub_datelike(self, other): raise TypeError("cannot subtract a datelike from a {cls}" .format(cls=type(self).__name__)) + def _evaluate_with_timedelta_like(self, other, op): + if isinstance(other, ABCSeries): + # GH#19042 + return NotImplemented + + opstr = '__{opname}__'.format(opname=op.__name__).replace('__r', '__') + # allow division by a timedelta + if opstr in ['__div__', '__truediv__', '__floordiv__']: + if _is_convertible_to_td(other): + other = Timedelta(other) + if isna(other): + raise NotImplementedError( + "division by pd.NaT not implemented") + + i8 = self.asi8 + left, right = i8, other.value + + if opstr in ['__floordiv__']: + result = op(left, right) + else: + result = op(left, np.float64(right)) + result = self._maybe_mask_results(result, convert='float64') + return result + + return NotImplemented + # ---------------------------------------------------------------- # Conversion Methods - Vectorized analogues of Timedelta methods + def total_seconds(self): + """ + Return total duration of each element expressed in seconds. + + This method is available directly on TimedeltaArray, TimedeltaIndex + and on Series containing timedelta values under the ``.dt`` namespace. + + Returns + ------- + seconds : ndarray, Float64Index, or Series + When the calling object is a TimedeltaArray, the return type + is ndarray. When the calling object is a TimedeltaIndex, + the return type is a Float64Index. When the calling object + is a Series, the return type is Series of type `float64` whose + index is the same as the original. + """ + return self._maybe_mask_results(1e-9 * self.asi8) + def to_pytimedelta(self): """ Return Timedelta Array/Index as object ndarray of datetime.timedelta diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index 41046e361ac90..cc9b09654289d 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -344,12 +344,6 @@ def _ensure_localized(self, result): result = result.tz_localize(self.tz) return result - def _box_values(self, values): - """ - apply box func to passed values - """ - return lib.map_infer(values, self._box_func) - def _box_values_as_index(self): """ return object Index which contains boxed values diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index 8e66381298e57..1d6dc14593e3b 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -1251,29 +1251,6 @@ def _fast_union(self, other): end=max(left_end, right_end), freq=left.freq) - def __iter__(self): - """ - Return an iterator over the boxed values - - Returns - ------- - Timestamps : ndarray - """ - - # convert in chunks of 10k for efficiency - data = self.asi8 - length = len(self) - chunksize = 10000 - chunks = int(length / chunksize) + 1 - for i in range(chunks): - start_i = i * chunksize - end_i = min((i + 1) * chunksize, length) - converted = libts.ints_to_pydatetime(data[start_i:end_i], - tz=self.tz, freq=self.freq, - box="timestamp") - for v in converted: - yield v - def _wrap_union_result(self, other, result): name = self.name if self.name == other.name else None if not timezones.tz_compare(self.tz, other.tz): @@ -1906,8 +1883,8 @@ def tz_localize(self, tz, ambiguous='raise', errors='raise'): tz : string, pytz.timezone, dateutil.tz.tzfile or None Time zone to convert timestamps to. Passing ``None`` will remove the time zone information preserving local time. - ambiguous : str {'infer', 'NaT', 'raise'} or bool array, \ -default 'raise' + ambiguous : str {'infer', 'NaT', 'raise'} or bool array, + default 'raise' - 'infer' will attempt to infer fall dst-transition hours based on order - bool-ndarray where True signifies a DST time, False signifies a diff --git a/pandas/core/indexes/period.py b/pandas/core/indexes/period.py index 16d003812f097..892ef611a34f3 100644 --- a/pandas/core/indexes/period.py +++ b/pandas/core/indexes/period.py @@ -31,7 +31,6 @@ from pandas._libs.tslibs.period import (Period, IncompatibleFrequency, DIFFERENT_FREQ_INDEX, _validate_end_alias, _quarter_to_myear) -from pandas._libs.tslibs.fields import isleapyear_arr from pandas._libs.tslibs import resolution, period from pandas._libs.tslibs.timedeltas import delta_to_nanoseconds @@ -625,11 +624,6 @@ def asfreq(self, freq=None, how='E'): days_in_month = _wrap_field_accessor('days_in_month') daysinmonth = days_in_month - @property - def is_leap_year(self): - """ Logical indicating if the date belongs to a leap year """ - return isleapyear_arr(np.asarray(self.year)) - @property def start_time(self): return self.to_timestamp(how='start') @@ -702,16 +696,7 @@ def _add_delta(self, other): def _sub_period(self, other): # If the operation is well-defined, we return an object-Index # of DateOffsets. Null entries are filled with pd.NaT - if self.freq != other.freq: - msg = DIFFERENT_FREQ_INDEX.format(self.freqstr, other.freqstr) - raise IncompatibleFrequency(msg) - - asi8 = self.asi8 - new_data = asi8 - other.ordinal - new_data = np.array([self.freq * x for x in new_data]) - - if self.hasnans: - new_data[self._isnan] = tslib.NaT + new_data = PeriodArrayMixin._sub_period(self, other) # TODO: Should name=self.name be passed here? return Index(new_data) diff --git a/pandas/core/indexes/timedeltas.py b/pandas/core/indexes/timedeltas.py index cd819191ef26e..e9b9fb63811d5 100644 --- a/pandas/core/indexes/timedeltas.py +++ b/pandas/core/indexes/timedeltas.py @@ -379,30 +379,11 @@ def _add_delta(self, delta): return TimedeltaIndex(new_values, freq='infer') def _evaluate_with_timedelta_like(self, other, op): - if isinstance(other, ABCSeries): - # GH#19042 + result = TimedeltaArrayMixin._evaluate_with_timedelta_like(self, other, + op) + if result is NotImplemented: return NotImplemented - - opstr = '__{opname}__'.format(opname=op.__name__).replace('__r', '__') - # allow division by a timedelta - if opstr in ['__div__', '__truediv__', '__floordiv__']: - if _is_convertible_to_td(other): - other = Timedelta(other) - if isna(other): - raise NotImplementedError( - "division by pd.NaT not implemented") - - i8 = self.asi8 - left, right = i8, other.value - - if opstr in ['__floordiv__']: - result = op(left, right) - else: - result = op(left, np.float64(right)) - result = self._maybe_mask_results(result, convert='float64') - return Index(result, name=self.name, copy=False) - - return NotImplemented + return Index(result, name=self.name, copy=False) def _add_datelike(self, other): # adding a timedeltaindex to a datetimelike @@ -528,8 +509,8 @@ def total_seconds(self): Float64Index([0.0, 86400.0, 172800.0, 259200.00000000003, 345600.0], dtype='float64') """ - return Index(self._maybe_mask_results(1e-9 * self.asi8), - name=self.name) + result = TimedeltaArrayMixin.total_seconds(self) + return Index(result, name=self.name) @Appender(_index_shared_docs['astype']) def astype(self, dtype, copy=True):