Skip to content

Commit 1c5b342

Browse files
jbrockmendeljreback
authored andcommitted
move ndarray-returning functions to EA mixin classes (#21722)
1 parent 1070976 commit 1c5b342

File tree

8 files changed

+146
-103
lines changed

8 files changed

+146
-103
lines changed

pandas/core/arrays/datetimelike.py

+41-1
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22

33
import numpy as np
44

5-
from pandas._libs import iNaT
5+
from pandas._libs import iNaT, NaT
66
from pandas._libs.tslibs.timedeltas import delta_to_nanoseconds
7+
from pandas._libs.tslibs.period import (
8+
DIFFERENT_FREQ_INDEX, IncompatibleFrequency)
79

810
from pandas.tseries import frequencies
911

12+
from pandas.core.dtypes.common import is_period_dtype
1013
import pandas.core.common as com
1114
from pandas.core.algorithms import checked_add_with_arr
1215

@@ -179,3 +182,40 @@ def _sub_nat(self):
179182
result = np.zeros(len(self), dtype=np.int64)
180183
result.fill(iNaT)
181184
return result.view('timedelta64[ns]')
185+
186+
def _sub_period_array(self, other):
187+
"""
188+
Subtract a Period Array/Index from self. This is only valid if self
189+
is itself a Period Array/Index, raises otherwise. Both objects must
190+
have the same frequency.
191+
192+
Parameters
193+
----------
194+
other : PeriodIndex or PeriodArray
195+
196+
Returns
197+
-------
198+
result : np.ndarray[object]
199+
Array of DateOffset objects; nulls represented by NaT
200+
"""
201+
if not is_period_dtype(self):
202+
raise TypeError("cannot subtract {dtype}-dtype to {cls}"
203+
.format(dtype=other.dtype,
204+
cls=type(self).__name__))
205+
206+
if not len(self) == len(other):
207+
raise ValueError("cannot subtract arrays/indices of "
208+
"unequal length")
209+
if self.freq != other.freq:
210+
msg = DIFFERENT_FREQ_INDEX.format(self.freqstr, other.freqstr)
211+
raise IncompatibleFrequency(msg)
212+
213+
new_values = checked_add_with_arr(self.asi8, -other.asi8,
214+
arr_mask=self._isnan,
215+
b_mask=other._isnan)
216+
217+
new_values = np.array([self.freq * x for x in new_values])
218+
if self.hasnans or other.hasnans:
219+
mask = (self._isnan) | (other._isnan)
220+
new_values[mask] = NaT
221+
return new_values

pandas/core/arrays/datetimes.py

+15
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import numpy as np
55

6+
from pandas._libs import tslib
67
from pandas._libs.tslib import Timestamp, NaT, iNaT
78
from pandas._libs.tslibs import timezones
89

@@ -108,3 +109,17 @@ def _sub_datelike_dti(self, other):
108109
mask = (self._isnan) | (other._isnan)
109110
new_values[mask] = iNaT
110111
return new_values.view('timedelta64[ns]')
112+
113+
# ----------------------------------------------------------------
114+
# Conversion Methods - Vectorized analogues of Timedelta methods
115+
116+
def to_pydatetime(self):
117+
"""
118+
Return Datetime Array/Index as object ndarray of datetime.datetime
119+
objects
120+
121+
Returns
122+
-------
123+
datetimes : ndarray
124+
"""
125+
return tslib.ints_to_pydatetime(self.asi8, tz=self.tz)

pandas/core/arrays/period.py

+72-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,22 @@
11
# -*- coding: utf-8 -*-
2+
from datetime import timedelta
3+
import warnings
24

5+
import numpy as np
6+
7+
from pandas._libs import lib
38
from pandas._libs.tslib import NaT
4-
from pandas._libs.tslibs.period import Period
9+
from pandas._libs.tslibs.period import (
10+
Period, IncompatibleFrequency, DIFFERENT_FREQ_INDEX)
11+
from pandas._libs.tslibs.timedeltas import delta_to_nanoseconds
512

613
from pandas.util._decorators import cache_readonly
714

815
from pandas.core.dtypes.dtypes import PeriodDtype
916

17+
from pandas.tseries import frequencies
18+
from pandas.tseries.offsets import Tick, DateOffset
19+
1020
from .datetimelike import DatetimeLikeArrayMixin
1121

1222

@@ -28,9 +38,70 @@ def _ndarray_values(self):
2838
def asi8(self):
2939
return self._ndarray_values.view('i8')
3040

41+
@property
42+
def freq(self):
43+
"""Return the frequency object if it is set, otherwise None"""
44+
return self._freq
45+
46+
@freq.setter
47+
def freq(self, value):
48+
msg = ('Setting {cls}.freq has been deprecated and will be '
49+
'removed in a future version; use PeriodIndex.asfreq instead. '
50+
'The {cls}.freq setter is not guaranteed to work.')
51+
warnings.warn(msg.format(cls=type(self).__name__),
52+
FutureWarning, stacklevel=2)
53+
self._freq = value
54+
3155
# ------------------------------------------------------------------
3256
# Arithmetic Methods
3357

3458
def _sub_datelike(self, other):
3559
assert other is not NaT
3660
return NotImplemented
61+
62+
def _maybe_convert_timedelta(self, other):
63+
"""
64+
Convert timedelta-like input to an integer multiple of self.freq
65+
66+
Parameters
67+
----------
68+
other : timedelta, np.timedelta64, DateOffset, int, np.ndarray
69+
70+
Returns
71+
-------
72+
converted : int, np.ndarray[int64]
73+
74+
Raises
75+
------
76+
IncompatibleFrequency : if the input cannot be written as a multiple
77+
of self.freq. Note IncompatibleFrequency subclasses ValueError.
78+
"""
79+
if isinstance(
80+
other, (timedelta, np.timedelta64, Tick, np.ndarray)):
81+
offset = frequencies.to_offset(self.freq.rule_code)
82+
if isinstance(offset, Tick):
83+
if isinstance(other, np.ndarray):
84+
nanos = np.vectorize(delta_to_nanoseconds)(other)
85+
else:
86+
nanos = delta_to_nanoseconds(other)
87+
offset_nanos = delta_to_nanoseconds(offset)
88+
check = np.all(nanos % offset_nanos == 0)
89+
if check:
90+
return nanos // offset_nanos
91+
elif isinstance(other, DateOffset):
92+
freqstr = other.rule_code
93+
base = frequencies.get_base_alias(freqstr)
94+
if base == self.freq.rule_code:
95+
return other.n
96+
msg = DIFFERENT_FREQ_INDEX.format(self.freqstr, other.freqstr)
97+
raise IncompatibleFrequency(msg)
98+
elif lib.is_integer(other):
99+
# integer is passed to .shift via
100+
# _add_datetimelike_methods basically
101+
# but ufunc may pass integer to _add_delta
102+
return other
103+
104+
# raise when input doesn't have freq
105+
msg = "Input has different freq from {cls}(freq={freqstr})"
106+
raise IncompatibleFrequency(msg.format(cls=type(self).__name__,
107+
freqstr=self.freqstr))

pandas/core/arrays/timedelta.py

+15
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# -*- coding: utf-8 -*-
22

3+
from pandas._libs import tslib
34
from pandas._libs.tslib import Timedelta, NaT
45

56
from pandas.core.dtypes.common import _TD_DTYPE
@@ -31,3 +32,17 @@ def _sub_datelike(self, other):
3132
assert other is not NaT
3233
raise TypeError("cannot subtract a datelike from a {cls}"
3334
.format(cls=type(self).__name__))
35+
36+
# ----------------------------------------------------------------
37+
# Conversion Methods - Vectorized analogues of Timedelta methods
38+
39+
def to_pytimedelta(self):
40+
"""
41+
Return Timedelta Array/Index as object ndarray of datetime.timedelta
42+
objects
43+
44+
Returns
45+
-------
46+
datetimes : ndarray
47+
"""
48+
return tslib.ints_to_pytimedelta(self.asi8)

pandas/core/indexes/datetimelike.py

+1-37
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@
1313
import numpy as np
1414

1515
from pandas._libs import lib, iNaT, NaT, Timedelta
16-
from pandas._libs.tslibs.period import (Period, IncompatibleFrequency,
17-
DIFFERENT_FREQ_INDEX)
16+
from pandas._libs.tslibs.period import Period
1817
from pandas._libs.tslibs.timestamps import round_ns
1918

2019
from pandas.core.dtypes.common import (
@@ -696,41 +695,6 @@ def _add_nat(self):
696695
# and datetime dtypes
697696
return self._nat_new(box=True)
698697

699-
def _sub_period_array(self, other):
700-
"""
701-
Subtract one PeriodIndex from another. This is only valid if they
702-
have the same frequency.
703-
704-
Parameters
705-
----------
706-
other : PeriodIndex
707-
708-
Returns
709-
-------
710-
result : np.ndarray[object]
711-
Array of DateOffset objects; nulls represented by NaT
712-
"""
713-
if not is_period_dtype(self):
714-
raise TypeError("cannot subtract {dtype}-dtype to {cls}"
715-
.format(dtype=other.dtype,
716-
cls=type(self).__name__))
717-
718-
if not len(self) == len(other):
719-
raise ValueError("cannot subtract indices of unequal length")
720-
if self.freq != other.freq:
721-
msg = DIFFERENT_FREQ_INDEX.format(self.freqstr, other.freqstr)
722-
raise IncompatibleFrequency(msg)
723-
724-
new_values = checked_add_with_arr(self.asi8, -other.asi8,
725-
arr_mask=self._isnan,
726-
b_mask=other._isnan)
727-
728-
new_values = np.array([self.freq * x for x in new_values])
729-
if self.hasnans or other.hasnans:
730-
mask = (self._isnan) | (other._isnan)
731-
new_values[mask] = NaT
732-
return new_values
733-
734698
def _addsub_offset_array(self, other, op):
735699
"""
736700
Add or subtract array-like of DateOffset objects

pandas/core/indexes/datetimes.py

-10
Original file line numberDiff line numberDiff line change
@@ -987,16 +987,6 @@ def _to_embed(self, keep_tz=False, dtype=None):
987987

988988
return self.values.copy()
989989

990-
def to_pydatetime(self):
991-
"""
992-
Return DatetimeIndex as object ndarray of datetime.datetime objects
993-
994-
Returns
995-
-------
996-
datetimes : ndarray
997-
"""
998-
return libts.ints_to_pydatetime(self.asi8, tz=self.tz)
999-
1000990
def to_period(self, freq=None):
1001991
"""
1002992
Cast to PeriodIndex at a particular frequency.

pandas/core/indexes/period.py

+1-43
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
import pandas.tseries.frequencies as frequencies
2222
from pandas.tseries.frequencies import get_freq_code as _gfc
23-
from pandas.tseries.offsets import Tick, DateOffset
23+
from pandas.tseries.offsets import Tick
2424

2525
from pandas.core.indexes.datetimes import DatetimeIndex, Int64Index, Index
2626
from pandas.core.indexes.datetimelike import DatelikeOps, DatetimeIndexOpsMixin
@@ -547,19 +547,6 @@ def is_full(self):
547547
values = self.asi8
548548
return ((values[1:] - values[:-1]) < 2).all()
549549

550-
@property
551-
def freq(self):
552-
"""Return the frequency object if it is set, otherwise None"""
553-
return self._freq
554-
555-
@freq.setter
556-
def freq(self, value):
557-
msg = ('Setting PeriodIndex.freq has been deprecated and will be '
558-
'removed in a future version; use PeriodIndex.asfreq instead. '
559-
'The PeriodIndex.freq setter is not guaranteed to work.')
560-
warnings.warn(msg, FutureWarning, stacklevel=2)
561-
self._freq = value
562-
563550
def asfreq(self, freq=None, how='E'):
564551
"""
565552
Convert the PeriodIndex to the specified frequency `freq`.
@@ -686,35 +673,6 @@ def to_timestamp(self, freq=None, how='start'):
686673
new_data = period.periodarr_to_dt64arr(new_data._ndarray_values, base)
687674
return DatetimeIndex(new_data, freq='infer', name=self.name)
688675

689-
def _maybe_convert_timedelta(self, other):
690-
if isinstance(
691-
other, (timedelta, np.timedelta64, Tick, np.ndarray)):
692-
offset = frequencies.to_offset(self.freq.rule_code)
693-
if isinstance(offset, Tick):
694-
if isinstance(other, np.ndarray):
695-
nanos = np.vectorize(delta_to_nanoseconds)(other)
696-
else:
697-
nanos = delta_to_nanoseconds(other)
698-
offset_nanos = delta_to_nanoseconds(offset)
699-
check = np.all(nanos % offset_nanos == 0)
700-
if check:
701-
return nanos // offset_nanos
702-
elif isinstance(other, DateOffset):
703-
freqstr = other.rule_code
704-
base = frequencies.get_base_alias(freqstr)
705-
if base == self.freq.rule_code:
706-
return other.n
707-
msg = DIFFERENT_FREQ_INDEX.format(self.freqstr, other.freqstr)
708-
raise IncompatibleFrequency(msg)
709-
elif is_integer(other):
710-
# integer is passed to .shift via
711-
# _add_datetimelike_methods basically
712-
# but ufunc may pass integer to _add_delta
713-
return other
714-
# raise when input doesn't have freq
715-
msg = "Input has different freq from PeriodIndex(freq={0})"
716-
raise IncompatibleFrequency(msg.format(self.freqstr))
717-
718676
def _add_offset(self, other):
719677
assert not isinstance(other, Tick)
720678
base = frequencies.get_base_alias(other.rule_code)

pandas/core/indexes/timedeltas.py

+1-11
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
from pandas.core.tools.timedeltas import (
3535
to_timedelta, _coerce_scalar_to_timedelta_type)
3636
from pandas.tseries.offsets import Tick, DateOffset
37-
from pandas._libs import (lib, index as libindex, tslib as libts,
37+
from pandas._libs import (lib, index as libindex,
3838
join as libjoin, Timedelta, NaT, iNaT)
3939
from pandas._libs.tslibs.timedeltas import array_to_timedelta64
4040
from pandas._libs.tslibs.fields import get_timedelta_field
@@ -541,16 +541,6 @@ def total_seconds(self):
541541
return Index(self._maybe_mask_results(1e-9 * self.asi8),
542542
name=self.name)
543543

544-
def to_pytimedelta(self):
545-
"""
546-
Return TimedeltaIndex as object ndarray of datetime.timedelta objects
547-
548-
Returns
549-
-------
550-
datetimes : ndarray
551-
"""
552-
return libts.ints_to_pytimedelta(self.asi8)
553-
554544
@Appender(_index_shared_docs['astype'])
555545
def astype(self, dtype, copy=True):
556546
dtype = pandas_dtype(dtype)

0 commit comments

Comments
 (0)