Skip to content

REF: move more methods to EAMixins #21782

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 1 commit into from
Jul 7, 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
11 changes: 10 additions & 1 deletion pandas/core/arrays/datetimelike.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -33,6 +33,12 @@ def _box_func(self):
"""
raise com.AbstractMethodError(self)

def _box_values(self, values):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so for future, any methods that we implement in the sub classes but not here, should be an AbstractMethodError (not specific to this comment, just generally)

"""
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)

Expand All @@ -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

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed from existing version on DatetimeIndex where it says

        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 = 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

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

Expand Down Expand Up @@ -86,13 +87,34 @@ 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

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
Expand Down
56 changes: 56 additions & 0 deletions pandas/core/arrays/timedelta.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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', '__')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we ought to have a method to do exactly this in the base ExtensionOpsMixin (but for later cleanup)

# 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
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated docstring to include ndarray

When the calling object is a TimedeltaArray, the return type
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you make this in to a list of these return values

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
Expand Down
6 changes: 0 additions & 6 deletions pandas/core/indexes/datetimelike.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 2 additions & 25 deletions pandas/core/indexes/datetimes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand Down
17 changes: 1 addition & 16 deletions pandas/core/indexes/period.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -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?
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the answer to this is a) yes, but b) if we remove this method entirely then when the inherited method is called from __sub__ it will correctly get wrapped.

return Index(new_data)
Expand Down
31 changes: 6 additions & 25 deletions pandas/core/indexes/timedeltas.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down