Skip to content

move ndarray-returning functions to EA mixin classes #21722

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 41 additions & 1 deletion pandas/core/arrays/datetimelike.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -179,3 +182,40 @@ 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 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 or PeriodArray

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 arrays/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
15 changes: 15 additions & 0 deletions pandas/core/arrays/datetimes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
73 changes: 72 additions & 1 deletion pandas/core/arrays/period.py
Original file line number Diff line number Diff line change
@@ -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


Expand All @@ -28,9 +38,70 @@ 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 {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__),
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):
"""
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)
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 {cls}(freq={freqstr})"
raise IncompatibleFrequency(msg.format(cls=type(self).__name__,
freqstr=self.freqstr))
15 changes: 15 additions & 0 deletions pandas/core/arrays/timedelta.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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)
38 changes: 1 addition & 37 deletions pandas/core/indexes/datetimelike.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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
Expand Down
10 changes: 0 additions & 10 deletions pandas/core/indexes/datetimes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
44 changes: 1 addition & 43 deletions pandas/core/indexes/period.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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`.
Expand Down Expand Up @@ -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)
Expand Down
12 changes: 1 addition & 11 deletions pandas/core/indexes/timedeltas.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down