Skip to content

Commit e84cf2a

Browse files
authored
move more methods to EAMixins (#21782)
1 parent a3f8f14 commit e84cf2a

File tree

8 files changed

+123
-73
lines changed

8 files changed

+123
-73
lines changed

pandas/core/arrays/datetimelike.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import numpy as np
44

5-
from pandas._libs import iNaT, NaT
5+
from pandas._libs import lib, iNaT, NaT
66
from pandas._libs.tslibs.timedeltas import delta_to_nanoseconds
77
from pandas._libs.tslibs.period import (
88
DIFFERENT_FREQ_INDEX, IncompatibleFrequency)
@@ -33,6 +33,12 @@ def _box_func(self):
3333
"""
3434
raise com.AbstractMethodError(self)
3535

36+
def _box_values(self, values):
37+
"""
38+
apply box func to passed values
39+
"""
40+
return lib.map_infer(values, self._box_func)
41+
3642
def __iter__(self):
3743
return (self._box_func(v) for v in self.asi8)
3844

@@ -46,6 +52,9 @@ def asi8(self):
4652
# do not cache or you'll create a memory leak
4753
return self.values.view('i8')
4854

55+
def __len__(self):
56+
return len(self._data)
57+
4958
# ------------------------------------------------------------------
5059
# Null Handling
5160

pandas/core/arrays/datetimes.py

+26
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,32 @@ def offset(self, value):
107107
warnings.warn(msg, FutureWarning, stacklevel=2)
108108
self.freq = value
109109

110+
# ----------------------------------------------------------------
111+
# Array-like Methods
112+
113+
def __iter__(self):
114+
"""
115+
Return an iterator over the boxed values
116+
117+
Yields
118+
-------
119+
tstamp : Timestamp
120+
"""
121+
122+
# convert in chunks of 10k for efficiency
123+
data = self.asi8
124+
length = len(self)
125+
chunksize = 10000
126+
chunks = int(length / chunksize) + 1
127+
for i in range(chunks):
128+
start_i = i * chunksize
129+
end_i = min((i + 1) * chunksize, length)
130+
converted = tslib.ints_to_pydatetime(data[start_i:end_i],
131+
tz=self.tz, freq=self.freq,
132+
box="timestamp")
133+
for v in converted:
134+
yield v
135+
110136
# -----------------------------------------------------------------
111137
# Comparison Methods
112138

pandas/core/arrays/period.py

+22
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
Period, IncompatibleFrequency, DIFFERENT_FREQ_INDEX,
1111
get_period_field_arr)
1212
from pandas._libs.tslibs.timedeltas import delta_to_nanoseconds
13+
from pandas._libs.tslibs.fields import isleapyear_arr
1314

1415
from pandas.util._decorators import cache_readonly
1516

@@ -86,13 +87,34 @@ def freq(self, value):
8687
"The number of days in the month")
8788
daysinmonth = days_in_month
8889

90+
@property
91+
def is_leap_year(self):
92+
""" Logical indicating if the date belongs to a leap year """
93+
return isleapyear_arr(np.asarray(self.year))
94+
8995
# ------------------------------------------------------------------
9096
# Arithmetic Methods
9197

9298
def _sub_datelike(self, other):
9399
assert other is not NaT
94100
return NotImplemented
95101

102+
def _sub_period(self, other):
103+
# If the operation is well-defined, we return an object-Index
104+
# of DateOffsets. Null entries are filled with pd.NaT
105+
if self.freq != other.freq:
106+
msg = DIFFERENT_FREQ_INDEX.format(self.freqstr, other.freqstr)
107+
raise IncompatibleFrequency(msg)
108+
109+
asi8 = self.asi8
110+
new_data = asi8 - other.ordinal
111+
new_data = np.array([self.freq * x for x in new_data])
112+
113+
if self.hasnans:
114+
new_data[self._isnan] = NaT
115+
116+
return new_data
117+
96118
def _maybe_convert_timedelta(self, other):
97119
"""
98120
Convert timedelta-like input to an integer multiple of self.freq

pandas/core/arrays/timedelta.py

+56
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,28 @@
11
# -*- coding: utf-8 -*-
2+
from datetime import timedelta
3+
4+
import numpy as np
25

36
from pandas._libs import tslib
47
from pandas._libs.tslib import Timedelta, NaT
58
from pandas._libs.tslibs.fields import get_timedelta_field
69

10+
from pandas import compat
11+
712
from pandas.core.dtypes.common import _TD_DTYPE
13+
from pandas.core.dtypes.generic import ABCSeries
14+
from pandas.core.dtypes.missing import isna
815

916
from pandas.tseries.offsets import Tick
1017

1118
from .datetimelike import DatetimeLikeArrayMixin
1219

1320

21+
def _is_convertible_to_td(key):
22+
return isinstance(key, (Tick, timedelta,
23+
np.timedelta64, compat.string_types))
24+
25+
1426
def _field_accessor(name, alias, docstring=None):
1527
def f(self):
1628
values = self.asi8
@@ -48,9 +60,53 @@ def _sub_datelike(self, other):
4860
raise TypeError("cannot subtract a datelike from a {cls}"
4961
.format(cls=type(self).__name__))
5062

63+
def _evaluate_with_timedelta_like(self, other, op):
64+
if isinstance(other, ABCSeries):
65+
# GH#19042
66+
return NotImplemented
67+
68+
opstr = '__{opname}__'.format(opname=op.__name__).replace('__r', '__')
69+
# allow division by a timedelta
70+
if opstr in ['__div__', '__truediv__', '__floordiv__']:
71+
if _is_convertible_to_td(other):
72+
other = Timedelta(other)
73+
if isna(other):
74+
raise NotImplementedError(
75+
"division by pd.NaT not implemented")
76+
77+
i8 = self.asi8
78+
left, right = i8, other.value
79+
80+
if opstr in ['__floordiv__']:
81+
result = op(left, right)
82+
else:
83+
result = op(left, np.float64(right))
84+
result = self._maybe_mask_results(result, convert='float64')
85+
return result
86+
87+
return NotImplemented
88+
5189
# ----------------------------------------------------------------
5290
# Conversion Methods - Vectorized analogues of Timedelta methods
5391

92+
def total_seconds(self):
93+
"""
94+
Return total duration of each element expressed in seconds.
95+
96+
This method is available directly on TimedeltaArray, TimedeltaIndex
97+
and on Series containing timedelta values under the ``.dt`` namespace.
98+
99+
Returns
100+
-------
101+
seconds : ndarray, Float64Index, or Series
102+
When the calling object is a TimedeltaArray, the return type
103+
is ndarray. When the calling object is a TimedeltaIndex,
104+
the return type is a Float64Index. When the calling object
105+
is a Series, the return type is Series of type `float64` whose
106+
index is the same as the original.
107+
"""
108+
return self._maybe_mask_results(1e-9 * self.asi8)
109+
54110
def to_pytimedelta(self):
55111
"""
56112
Return Timedelta Array/Index as object ndarray of datetime.timedelta

pandas/core/indexes/datetimelike.py

-6
Original file line numberDiff line numberDiff line change
@@ -344,12 +344,6 @@ def _ensure_localized(self, result):
344344
result = result.tz_localize(self.tz)
345345
return result
346346

347-
def _box_values(self, values):
348-
"""
349-
apply box func to passed values
350-
"""
351-
return lib.map_infer(values, self._box_func)
352-
353347
def _box_values_as_index(self):
354348
"""
355349
return object Index which contains boxed values

pandas/core/indexes/datetimes.py

+2-25
Original file line numberDiff line numberDiff line change
@@ -1251,29 +1251,6 @@ def _fast_union(self, other):
12511251
end=max(left_end, right_end),
12521252
freq=left.freq)
12531253

1254-
def __iter__(self):
1255-
"""
1256-
Return an iterator over the boxed values
1257-
1258-
Returns
1259-
-------
1260-
Timestamps : ndarray
1261-
"""
1262-
1263-
# convert in chunks of 10k for efficiency
1264-
data = self.asi8
1265-
length = len(self)
1266-
chunksize = 10000
1267-
chunks = int(length / chunksize) + 1
1268-
for i in range(chunks):
1269-
start_i = i * chunksize
1270-
end_i = min((i + 1) * chunksize, length)
1271-
converted = libts.ints_to_pydatetime(data[start_i:end_i],
1272-
tz=self.tz, freq=self.freq,
1273-
box="timestamp")
1274-
for v in converted:
1275-
yield v
1276-
12771254
def _wrap_union_result(self, other, result):
12781255
name = self.name if self.name == other.name else None
12791256
if not timezones.tz_compare(self.tz, other.tz):
@@ -1906,8 +1883,8 @@ def tz_localize(self, tz, ambiguous='raise', errors='raise'):
19061883
tz : string, pytz.timezone, dateutil.tz.tzfile or None
19071884
Time zone to convert timestamps to. Passing ``None`` will
19081885
remove the time zone information preserving local time.
1909-
ambiguous : str {'infer', 'NaT', 'raise'} or bool array, \
1910-
default 'raise'
1886+
ambiguous : str {'infer', 'NaT', 'raise'} or bool array,
1887+
default 'raise'
19111888
- 'infer' will attempt to infer fall dst-transition hours based on
19121889
order
19131890
- bool-ndarray where True signifies a DST time, False signifies a

pandas/core/indexes/period.py

+1-16
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
from pandas._libs.tslibs.period import (Period, IncompatibleFrequency,
3232
DIFFERENT_FREQ_INDEX,
3333
_validate_end_alias, _quarter_to_myear)
34-
from pandas._libs.tslibs.fields import isleapyear_arr
3534
from pandas._libs.tslibs import resolution, period
3635
from pandas._libs.tslibs.timedeltas import delta_to_nanoseconds
3736

@@ -625,11 +624,6 @@ def asfreq(self, freq=None, how='E'):
625624
days_in_month = _wrap_field_accessor('days_in_month')
626625
daysinmonth = days_in_month
627626

628-
@property
629-
def is_leap_year(self):
630-
""" Logical indicating if the date belongs to a leap year """
631-
return isleapyear_arr(np.asarray(self.year))
632-
633627
@property
634628
def start_time(self):
635629
return self.to_timestamp(how='start')
@@ -702,16 +696,7 @@ def _add_delta(self, other):
702696
def _sub_period(self, other):
703697
# If the operation is well-defined, we return an object-Index
704698
# of DateOffsets. Null entries are filled with pd.NaT
705-
if self.freq != other.freq:
706-
msg = DIFFERENT_FREQ_INDEX.format(self.freqstr, other.freqstr)
707-
raise IncompatibleFrequency(msg)
708-
709-
asi8 = self.asi8
710-
new_data = asi8 - other.ordinal
711-
new_data = np.array([self.freq * x for x in new_data])
712-
713-
if self.hasnans:
714-
new_data[self._isnan] = tslib.NaT
699+
new_data = PeriodArrayMixin._sub_period(self, other)
715700

716701
# TODO: Should name=self.name be passed here?
717702
return Index(new_data)

pandas/core/indexes/timedeltas.py

+6-25
Original file line numberDiff line numberDiff line change
@@ -379,30 +379,11 @@ def _add_delta(self, delta):
379379
return TimedeltaIndex(new_values, freq='infer')
380380

381381
def _evaluate_with_timedelta_like(self, other, op):
382-
if isinstance(other, ABCSeries):
383-
# GH#19042
382+
result = TimedeltaArrayMixin._evaluate_with_timedelta_like(self, other,
383+
op)
384+
if result is NotImplemented:
384385
return NotImplemented
385-
386-
opstr = '__{opname}__'.format(opname=op.__name__).replace('__r', '__')
387-
# allow division by a timedelta
388-
if opstr in ['__div__', '__truediv__', '__floordiv__']:
389-
if _is_convertible_to_td(other):
390-
other = Timedelta(other)
391-
if isna(other):
392-
raise NotImplementedError(
393-
"division by pd.NaT not implemented")
394-
395-
i8 = self.asi8
396-
left, right = i8, other.value
397-
398-
if opstr in ['__floordiv__']:
399-
result = op(left, right)
400-
else:
401-
result = op(left, np.float64(right))
402-
result = self._maybe_mask_results(result, convert='float64')
403-
return Index(result, name=self.name, copy=False)
404-
405-
return NotImplemented
386+
return Index(result, name=self.name, copy=False)
406387

407388
def _add_datelike(self, other):
408389
# adding a timedeltaindex to a datetimelike
@@ -528,8 +509,8 @@ def total_seconds(self):
528509
Float64Index([0.0, 86400.0, 172800.0, 259200.00000000003, 345600.0],
529510
dtype='float64')
530511
"""
531-
return Index(self._maybe_mask_results(1e-9 * self.asi8),
532-
name=self.name)
512+
result = TimedeltaArrayMixin.total_seconds(self)
513+
return Index(result, name=self.name)
533514

534515
@Appender(_index_shared_docs['astype'])
535516
def astype(self, dtype, copy=True):

0 commit comments

Comments
 (0)