From 5f23a108266c7c0c3a1ec34cadd1974994b81bc2 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Tue, 3 Jul 2018 20:23:30 -0700 Subject: [PATCH 1/4] move methods that dont need changing --- pandas/core/arrays/datetimelike.py | 40 ++++++++++++++++++++- pandas/core/arrays/datetimes.py | 15 ++++++++ pandas/core/arrays/period.py | 55 ++++++++++++++++++++++++++++- pandas/core/arrays/timedelta.py | 15 ++++++++ pandas/core/indexes/datetimelike.py | 38 +------------------- pandas/core/indexes/datetimes.py | 10 ------ pandas/core/indexes/period.py | 44 +---------------------- pandas/core/indexes/timedeltas.py | 12 +------ 8 files changed, 126 insertions(+), 103 deletions(-) diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index be3f94201f103..460004dcb5c4a 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -2,11 +2,14 @@ import numpy as np -from pandas._libs import iNaT +from pandas._libs import iNaT, NaT from pandas._libs.tslibs.timedeltas import delta_to_nanoseconds +from pandas._libs.tslibs.period import ( + DIFFERENT_FREQ_INDEX, IncompatibleFrequency) from pandas.tseries import frequencies +from pandas.core.dtypes.common import is_period_dtype import pandas.core.common as com from pandas.core.algorithms import checked_add_with_arr @@ -179,3 +182,38 @@ def _sub_nat(self): result = np.zeros(len(self), dtype=np.int64) result.fill(iNaT) return result.view('timedelta64[ns]') + + def _sub_period_array(self, other): + """ + Subtract one PeriodIndex from another. This is only valid if they + have the same frequency. + + Parameters + ---------- + other : PeriodIndex + + Returns + ------- + result : np.ndarray[object] + Array of DateOffset objects; nulls represented by NaT + """ + if not is_period_dtype(self): + raise TypeError("cannot subtract {dtype}-dtype to {cls}" + .format(dtype=other.dtype, + cls=type(self).__name__)) + + if not len(self) == len(other): + raise ValueError("cannot subtract indices of unequal length") + if self.freq != other.freq: + msg = DIFFERENT_FREQ_INDEX.format(self.freqstr, other.freqstr) + raise IncompatibleFrequency(msg) + + new_values = checked_add_with_arr(self.asi8, -other.asi8, + arr_mask=self._isnan, + b_mask=other._isnan) + + new_values = np.array([self.freq * x for x in new_values]) + if self.hasnans or other.hasnans: + mask = (self._isnan) | (other._isnan) + new_values[mask] = NaT + return new_values diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index fb51f3324c5ea..a5acc3d0e8593 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -3,6 +3,7 @@ import numpy as np +from pandas._libs import tslib from pandas._libs.tslib import Timestamp, NaT, iNaT from pandas._libs.tslibs import timezones @@ -108,3 +109,17 @@ def _sub_datelike_dti(self, other): mask = (self._isnan) | (other._isnan) new_values[mask] = iNaT return new_values.view('timedelta64[ns]') + + # ---------------------------------------------------------------- + # Conversion Methods - Vectorized analogues of Timedelta methods + + def to_pydatetime(self): + """ + Return Datetime Array/Index as object ndarray of datetime.datetime + objects + + Returns + ------- + datetimes : ndarray + """ + return tslib.ints_to_pydatetime(self.asi8, tz=self.tz) diff --git a/pandas/core/arrays/period.py b/pandas/core/arrays/period.py index 03c0128c67c99..4cc271c8a5ee8 100644 --- a/pandas/core/arrays/period.py +++ b/pandas/core/arrays/period.py @@ -1,12 +1,22 @@ # -*- coding: utf-8 -*- +from datetime import timedelta +import warnings +import numpy as np + +from pandas._libs import lib from pandas._libs.tslib import NaT -from pandas._libs.tslibs.period import Period +from pandas._libs.tslibs.period import ( + Period, IncompatibleFrequency, DIFFERENT_FREQ_INDEX) +from pandas._libs.tslibs.timedeltas import delta_to_nanoseconds from pandas.util._decorators import cache_readonly from pandas.core.dtypes.dtypes import PeriodDtype +from pandas.tseries import frequencies +from pandas.tseries.offsets import Tick, DateOffset + from .datetimelike import DatetimeLikeArrayMixin @@ -28,9 +38,52 @@ def _ndarray_values(self): def asi8(self): return self._ndarray_values.view('i8') + @property + def freq(self): + """Return the frequency object if it is set, otherwise None""" + return self._freq + + @freq.setter + def freq(self, value): + msg = ('Setting PeriodIndex.freq has been deprecated and will be ' + 'removed in a future version; use PeriodIndex.asfreq instead. ' + 'The PeriodIndex.freq setter is not guaranteed to work.') + warnings.warn(msg, FutureWarning, stacklevel=2) + self._freq = value + # ------------------------------------------------------------------ # Arithmetic Methods def _sub_datelike(self, other): assert other is not NaT return NotImplemented + + def _maybe_convert_timedelta(self, other): + if isinstance( + other, (timedelta, np.timedelta64, Tick, np.ndarray)): + offset = frequencies.to_offset(self.freq.rule_code) + if isinstance(offset, Tick): + if isinstance(other, np.ndarray): + nanos = np.vectorize(delta_to_nanoseconds)(other) + else: + nanos = delta_to_nanoseconds(other) + offset_nanos = delta_to_nanoseconds(offset) + check = np.all(nanos % offset_nanos == 0) + if check: + return nanos // offset_nanos + elif isinstance(other, DateOffset): + freqstr = other.rule_code + base = frequencies.get_base_alias(freqstr) + if base == self.freq.rule_code: + return other.n + msg = DIFFERENT_FREQ_INDEX.format(self.freqstr, other.freqstr) + raise IncompatibleFrequency(msg) + elif lib.is_integer(other): + # integer is passed to .shift via + # _add_datetimelike_methods basically + # but ufunc may pass integer to _add_delta + return other + + # raise when input doesn't have freq + msg = "Input has different freq from PeriodIndex(freq={0})" + raise IncompatibleFrequency(msg.format(self.freqstr)) diff --git a/pandas/core/arrays/timedelta.py b/pandas/core/arrays/timedelta.py index 85c5bdc566ff1..cc3478e504efb 100644 --- a/pandas/core/arrays/timedelta.py +++ b/pandas/core/arrays/timedelta.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- +from pandas._libs import tslib from pandas._libs.tslib import Timedelta, NaT from pandas.core.dtypes.common import _TD_DTYPE @@ -31,3 +32,17 @@ def _sub_datelike(self, other): assert other is not NaT raise TypeError("cannot subtract a datelike from a {cls}" .format(cls=type(self).__name__)) + + # ---------------------------------------------------------------- + # Conversion Methods - Vectorized analogues of Timedelta methods + + def to_pytimedelta(self): + """ + Return Timedelta Array/Index as object ndarray of datetime.timedelta + objects + + Returns + ------- + datetimes : ndarray + """ + return tslib.ints_to_pytimedelta(self.asi8) diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index 88a1529b34960..41046e361ac90 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -13,8 +13,7 @@ import numpy as np from pandas._libs import lib, iNaT, NaT, Timedelta -from pandas._libs.tslibs.period import (Period, IncompatibleFrequency, - DIFFERENT_FREQ_INDEX) +from pandas._libs.tslibs.period import Period from pandas._libs.tslibs.timestamps import round_ns from pandas.core.dtypes.common import ( @@ -696,41 +695,6 @@ def _add_nat(self): # and datetime dtypes return self._nat_new(box=True) - def _sub_period_array(self, other): - """ - Subtract one PeriodIndex from another. This is only valid if they - have the same frequency. - - Parameters - ---------- - other : PeriodIndex - - Returns - ------- - result : np.ndarray[object] - Array of DateOffset objects; nulls represented by NaT - """ - if not is_period_dtype(self): - raise TypeError("cannot subtract {dtype}-dtype to {cls}" - .format(dtype=other.dtype, - cls=type(self).__name__)) - - if not len(self) == len(other): - raise ValueError("cannot subtract indices of unequal length") - if self.freq != other.freq: - msg = DIFFERENT_FREQ_INDEX.format(self.freqstr, other.freqstr) - raise IncompatibleFrequency(msg) - - new_values = checked_add_with_arr(self.asi8, -other.asi8, - arr_mask=self._isnan, - b_mask=other._isnan) - - new_values = np.array([self.freq * x for x in new_values]) - if self.hasnans or other.hasnans: - mask = (self._isnan) | (other._isnan) - new_values[mask] = NaT - return new_values - 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 7475998909ec2..3494811cc3a9b 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -987,16 +987,6 @@ def _to_embed(self, keep_tz=False, dtype=None): return self.values.copy() - def to_pydatetime(self): - """ - Return DatetimeIndex as object ndarray of datetime.datetime objects - - Returns - ------- - datetimes : ndarray - """ - return libts.ints_to_pydatetime(self.asi8, tz=self.tz) - def to_period(self, freq=None): """ Cast to PeriodIndex at a particular frequency. diff --git a/pandas/core/indexes/period.py b/pandas/core/indexes/period.py index e04d729d9a8c1..143cb8cd3ff6e 100644 --- a/pandas/core/indexes/period.py +++ b/pandas/core/indexes/period.py @@ -20,7 +20,7 @@ import pandas.tseries.frequencies as frequencies from pandas.tseries.frequencies import get_freq_code as _gfc -from pandas.tseries.offsets import Tick, DateOffset +from pandas.tseries.offsets import Tick from pandas.core.indexes.datetimes import DatetimeIndex, Int64Index, Index from pandas.core.indexes.datetimelike import DatelikeOps, DatetimeIndexOpsMixin @@ -547,19 +547,6 @@ def is_full(self): values = self.asi8 return ((values[1:] - values[:-1]) < 2).all() - @property - def freq(self): - """Return the frequency object if it is set, otherwise None""" - return self._freq - - @freq.setter - def freq(self, value): - msg = ('Setting PeriodIndex.freq has been deprecated and will be ' - 'removed in a future version; use PeriodIndex.asfreq instead. ' - 'The PeriodIndex.freq setter is not guaranteed to work.') - warnings.warn(msg, FutureWarning, stacklevel=2) - self._freq = value - def asfreq(self, freq=None, how='E'): """ Convert the PeriodIndex to the specified frequency `freq`. @@ -686,35 +673,6 @@ def to_timestamp(self, freq=None, how='start'): new_data = period.periodarr_to_dt64arr(new_data._ndarray_values, base) return DatetimeIndex(new_data, freq='infer', name=self.name) - def _maybe_convert_timedelta(self, other): - if isinstance( - other, (timedelta, np.timedelta64, Tick, np.ndarray)): - offset = frequencies.to_offset(self.freq.rule_code) - if isinstance(offset, Tick): - if isinstance(other, np.ndarray): - nanos = np.vectorize(delta_to_nanoseconds)(other) - else: - nanos = delta_to_nanoseconds(other) - offset_nanos = delta_to_nanoseconds(offset) - check = np.all(nanos % offset_nanos == 0) - if check: - return nanos // offset_nanos - elif isinstance(other, DateOffset): - freqstr = other.rule_code - base = frequencies.get_base_alias(freqstr) - if base == self.freq.rule_code: - return other.n - msg = DIFFERENT_FREQ_INDEX.format(self.freqstr, other.freqstr) - raise IncompatibleFrequency(msg) - elif is_integer(other): - # integer is passed to .shift via - # _add_datetimelike_methods basically - # but ufunc may pass integer to _add_delta - return other - # raise when input doesn't have freq - msg = "Input has different freq from PeriodIndex(freq={0})" - raise IncompatibleFrequency(msg.format(self.freqstr)) - def _add_offset(self, other): assert not isinstance(other, Tick) base = frequencies.get_base_alias(other.rule_code) diff --git a/pandas/core/indexes/timedeltas.py b/pandas/core/indexes/timedeltas.py index 80b9bf92df14a..520504affaa02 100644 --- a/pandas/core/indexes/timedeltas.py +++ b/pandas/core/indexes/timedeltas.py @@ -34,7 +34,7 @@ from pandas.core.tools.timedeltas import ( to_timedelta, _coerce_scalar_to_timedelta_type) from pandas.tseries.offsets import Tick, DateOffset -from pandas._libs import (lib, index as libindex, tslib as libts, +from pandas._libs import (lib, index as libindex, join as libjoin, Timedelta, NaT, iNaT) from pandas._libs.tslibs.timedeltas import array_to_timedelta64 from pandas._libs.tslibs.fields import get_timedelta_field @@ -541,16 +541,6 @@ def total_seconds(self): return Index(self._maybe_mask_results(1e-9 * self.asi8), name=self.name) - def to_pytimedelta(self): - """ - Return TimedeltaIndex as object ndarray of datetime.timedelta objects - - Returns - ------- - datetimes : ndarray - """ - return libts.ints_to_pytimedelta(self.asi8) - @Appender(_index_shared_docs['astype']) def astype(self, dtype, copy=True): dtype = pandas_dtype(dtype) From f556508b1e05500fcdeb4eb5524d2d87d26c3ec1 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Tue, 3 Jul 2018 20:36:01 -0700 Subject: [PATCH 2/4] update docstrings and error messages --- pandas/core/arrays/datetimelike.py | 8 +++++--- pandas/core/arrays/period.py | 12 +++++++----- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index 460004dcb5c4a..54e1f0726d772 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -185,12 +185,13 @@ def _sub_nat(self): def _sub_period_array(self, other): """ - Subtract one PeriodIndex from another. This is only valid if they + Subtract a Period Array/Index from self. This is only valid if self + is itself a Period Array/Index, raises otherwise. Both objects must have the same frequency. Parameters ---------- - other : PeriodIndex + other : PeriodIndex or PeriodArray Returns ------- @@ -203,7 +204,8 @@ def _sub_period_array(self, other): cls=type(self).__name__)) if not len(self) == len(other): - raise ValueError("cannot subtract indices of unequal length") + raise ValueError("cannot subtract arrays/indices of " + "unequal length") if self.freq != other.freq: msg = DIFFERENT_FREQ_INDEX.format(self.freqstr, other.freqstr) raise IncompatibleFrequency(msg) diff --git a/pandas/core/arrays/period.py b/pandas/core/arrays/period.py index 4cc271c8a5ee8..ed160d112ac48 100644 --- a/pandas/core/arrays/period.py +++ b/pandas/core/arrays/period.py @@ -45,10 +45,11 @@ def freq(self): @freq.setter def freq(self, value): - msg = ('Setting PeriodIndex.freq has been deprecated and will be ' + msg = ('Setting {cls}.freq has been deprecated and will be ' 'removed in a future version; use PeriodIndex.asfreq instead. ' - 'The PeriodIndex.freq setter is not guaranteed to work.') - warnings.warn(msg, FutureWarning, stacklevel=2) + 'The {cls}.freq setter is not guaranteed to work.') + warnings.warn(msg.format(cls=type(self).__name__, + FutureWarning, stacklevel=2) self._freq = value # ------------------------------------------------------------------ @@ -85,5 +86,6 @@ def _maybe_convert_timedelta(self, other): return other # raise when input doesn't have freq - msg = "Input has different freq from PeriodIndex(freq={0})" - raise IncompatibleFrequency(msg.format(self.freqstr)) + msg = "Input has different freq from {cls}(freq={freqstr})" + raise IncompatibleFrequency(msg.format(cls=type(self).__name__, + freqstr=self.freqstr)) From 3f1e95a79d28b61f60838d339ecac5ad3ac7bfcf Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Tue, 3 Jul 2018 20:42:55 -0700 Subject: [PATCH 3/4] fixup typo --- pandas/core/arrays/period.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/arrays/period.py b/pandas/core/arrays/period.py index ed160d112ac48..3e978e164a7c4 100644 --- a/pandas/core/arrays/period.py +++ b/pandas/core/arrays/period.py @@ -48,7 +48,7 @@ def freq(self, value): msg = ('Setting {cls}.freq has been deprecated and will be ' 'removed in a future version; use PeriodIndex.asfreq instead. ' 'The {cls}.freq setter is not guaranteed to work.') - warnings.warn(msg.format(cls=type(self).__name__, + warnings.warn(msg.format(cls=type(self).__name__), FutureWarning, stacklevel=2) self._freq = value From 1a3fdefbbdfcd05062922c8763efcddfd360ec0e Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Wed, 4 Jul 2018 10:06:19 -0700 Subject: [PATCH 4/4] docstring --- pandas/core/arrays/period.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pandas/core/arrays/period.py b/pandas/core/arrays/period.py index 3e978e164a7c4..01acaad228067 100644 --- a/pandas/core/arrays/period.py +++ b/pandas/core/arrays/period.py @@ -60,6 +60,22 @@ def _sub_datelike(self, other): return NotImplemented def _maybe_convert_timedelta(self, other): + """ + Convert timedelta-like input to an integer multiple of self.freq + + Parameters + ---------- + other : timedelta, np.timedelta64, DateOffset, int, np.ndarray + + Returns + ------- + converted : int, np.ndarray[int64] + + Raises + ------ + IncompatibleFrequency : if the input cannot be written as a multiple + of self.freq. Note IncompatibleFrequency subclasses ValueError. + """ if isinstance( other, (timedelta, np.timedelta64, Tick, np.ndarray)): offset = frequencies.to_offset(self.freq.rule_code)