Skip to content

cleanup order of operations kludges #19895

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 2 commits into from
Feb 26, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
48 changes: 17 additions & 31 deletions pandas/core/indexes/datetimelike.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
is_object_dtype,
is_string_dtype,
is_datetime64_dtype,
is_datetime64tz_dtype,
is_period_dtype,
is_timedelta64_dtype)
from pandas.core.dtypes.generic import (
Expand Down Expand Up @@ -200,8 +201,9 @@ def _evaluate_compare(self, other, op):
if is_bool_dtype(result):
result[mask] = False
return result

result[mask] = iNaT
try:
result[mask] = iNaT
return Index(result)
except TypeError:
return result
Expand Down Expand Up @@ -349,7 +351,7 @@ def _nat_new(self, box=True):
return result

attribs = self._get_attributes_dict()
if not isinstance(self, ABCPeriodIndex):
if not is_period_dtype(self):
attribs['freq'] = None
return self._simple_new(result, **attribs)

Expand Down Expand Up @@ -631,9 +633,9 @@ def _convert_scalar_indexer(self, key, kind=None):
._convert_scalar_indexer(key, kind=kind))

def _add_datelike(self, other):
raise TypeError("cannot add {0} and {1}"
.format(type(self).__name__,
type(other).__name__))
raise TypeError("cannot add {cls} and {typ}"
.format(cls=type(self).__name__,
typ=type(other).__name__))

def _sub_datelike(self, other):
raise com.AbstractMethodError(self)
Expand Down Expand Up @@ -677,7 +679,7 @@ def _add_datetimelike_methods(cls):
"""

def __add__(self, other):
from pandas import Index, DatetimeIndex, TimedeltaIndex, DateOffset
from pandas import DateOffset

other = lib.item_from_zerodim(other)
if isinstance(other, ABCSeries):
Expand All @@ -700,18 +702,9 @@ def __add__(self, other):
elif is_offsetlike(other):
# Array/Index of DateOffset objects
result = self._addsub_offset_array(other, operator.add)
elif isinstance(self, TimedeltaIndex) and isinstance(other, Index):
if hasattr(other, '_add_delta'):
# i.e. DatetimeIndex, TimedeltaIndex, or PeriodIndex
result = other._add_delta(self)
else:
raise TypeError("cannot add TimedeltaIndex and {typ}"
.format(typ=type(other)))
elif isinstance(other, Index):
result = self._add_datelike(other)
elif is_datetime64_dtype(other):
# ndarray[datetime64]; note DatetimeIndex is caught above
return self + DatetimeIndex(other)
elif is_datetime64_dtype(other) or is_datetime64tz_dtype(other):
# DatetimeIndex, ndarray[datetime64]
return self._add_datelike(other)
elif is_integer_dtype(other) and self.freq is None:
# GH#19123
raise NullFrequencyError("Cannot shift with no freq")
Expand All @@ -731,7 +724,7 @@ def __radd__(self, other):
cls.__radd__ = __radd__

def __sub__(self, other):
from pandas import Index, DatetimeIndex, TimedeltaIndex, DateOffset
from pandas import Index, DateOffset

other = lib.item_from_zerodim(other)
if isinstance(other, ABCSeries):
Expand All @@ -756,20 +749,13 @@ def __sub__(self, other):
elif is_offsetlike(other):
# Array/Index of DateOffset objects
result = self._addsub_offset_array(other, operator.sub)
elif isinstance(self, TimedeltaIndex) and isinstance(other, Index):
# We checked above for timedelta64_dtype(other) so this
# must be invalid.
raise TypeError("cannot subtract TimedeltaIndex and {typ}"
.format(typ=type(other).__name__))
elif isinstance(other, DatetimeIndex):
elif is_datetime64_dtype(other) or is_datetime64tz_dtype(other):
# DatetimeIndex, ndarray[datetime64]
result = self._sub_datelike(other)
elif is_datetime64_dtype(other):
# ndarray[datetime64]; note we caught DatetimeIndex earlier
return self - DatetimeIndex(other)
elif isinstance(other, Index):
raise TypeError("cannot subtract {typ1} and {typ2}"
.format(typ1=type(self).__name__,
typ2=type(other).__name__))
raise TypeError("cannot subtract {cls} and {typ}"
.format(cls=type(self).__name__,
typ=type(other).__name__))
elif is_integer_dtype(other) and self.freq is None:
# GH#19123
raise NullFrequencyError("Cannot shift with no freq")
Expand Down
16 changes: 12 additions & 4 deletions pandas/core/indexes/datetimes.py
Original file line number Diff line number Diff line change
Expand Up @@ -864,11 +864,18 @@ def _add_datelike(self, other):
def _sub_datelike(self, other):
# subtract a datetime from myself, yielding a TimedeltaIndex
from pandas import TimedeltaIndex

if isinstance(other, np.ndarray):
Copy link
Contributor

Choose a reason for hiding this comment

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

why don't you do this in 873, and just always wrap it

Copy link
Member Author

Choose a reason for hiding this comment

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

Sounds good.

# wrap in DatetimeIndex for op
assert is_datetime64_dtype(other)
other = DatetimeIndex(other)

if isinstance(other, DatetimeIndex):
# require tz compat
if not self._has_same_tz(other):
raise TypeError("DatetimeIndex subtraction must have the same "
"timezones or no timezones")
raise TypeError("{cls} subtraction must have the same "
"timezones or no timezones"
.format(cls=type(self).__name__))
result = self._sub_datelike_dti(other)
elif isinstance(other, (datetime, np.datetime64)):
other = Timestamp(other)
Expand All @@ -885,8 +892,9 @@ def _sub_datelike(self, other):
result = self._maybe_mask_results(result,
fill_value=libts.iNaT)
else:
raise TypeError("cannot subtract DatetimeIndex and {typ}"
.format(typ=type(other).__name__))
raise TypeError("cannot subtract {cls} and {typ}"
.format(cls=type(self).__name__,
typ=type(other).__name__))
return TimedeltaIndex(result)

def _sub_datelike_dti(self, other):
Expand Down
30 changes: 17 additions & 13 deletions pandas/core/indexes/timedeltas.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,30 +59,28 @@ def _td_index_cmp(opname, cls):
nat_result = True if opname == '__ne__' else False

def wrapper(self, other):
msg = "cannot compare a TimedeltaIndex with type {0}"
msg = "cannot compare a {cls} with type {typ}"
func = getattr(super(TimedeltaIndex, self), opname)
if _is_convertible_to_td(other) or other is NaT:
try:
other = _to_m8(other)
except ValueError:
# failed to parse as timedelta
raise TypeError(msg.format(type(other)))
raise TypeError(msg.format(cls=type(self).__name__,
typ=type(other).__name__))
result = func(other)
if isna(other):
result.fill(nat_result)
else:
if not is_list_like(other):
raise TypeError(msg.format(type(other)))

elif not is_list_like(other):
raise TypeError(msg.format(cls=type(self).__name__,
typ=type(other).__name__))
else:
other = TimedeltaIndex(other).values
result = func(other)
result = com._values_from_object(result)

if isinstance(other, Index):
o_mask = other.values.view('i8') == iNaT
else:
o_mask = other.view('i8') == iNaT

o_mask = np.array(isna(other))
if o_mask.any():
result[o_mask] = nat_result

Expand Down Expand Up @@ -416,9 +414,15 @@ def _evaluate_with_timedelta_like(self, other, op):
def _add_datelike(self, other):
# adding a timedeltaindex to a datetimelike
from pandas import Timestamp, DatetimeIndex
if isinstance(other, np.ndarray):
Copy link
Contributor

Choose a reason for hiding this comment

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

same

other = DatetimeIndex(other)

if other is NaT:
# GH#19124 pd.NaT is treated like a timedelta
return self._nat_new()
elif isinstance(other, DatetimeIndex):
# defer to implementation in DatetimeIndex
return other + self
else:
other = Timestamp(other)
i8 = self.asi8
Expand All @@ -434,7 +438,8 @@ def _sub_datelike(self, other):
if other is NaT:
return self._nat_new()
else:
raise TypeError("cannot subtract a datelike from a TimedeltaIndex")
raise TypeError("cannot subtract a datelike from a {cls}"
.format(cls=type(self).__name__))

def _addsub_offset_array(self, other, op):
# Add or subtract Array-like of DateOffset objects
Expand Down Expand Up @@ -962,8 +967,7 @@ def _is_convertible_to_index(other):


def _is_convertible_to_td(key):
# TODO: Not all DateOffset objects are convertible to Timedelta
return isinstance(key, (DateOffset, timedelta, Timedelta,
return isinstance(key, (Tick, timedelta,
np.timedelta64, compat.string_types))


Expand Down
4 changes: 2 additions & 2 deletions pandas/tests/indexes/datetimes/test_arithmetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,7 @@ def test_dti_sub_tdi(self, tz):
result = dti - tdi
tm.assert_index_equal(result, expected)

msg = 'cannot subtract TimedeltaIndex and DatetimeIndex'
msg = 'cannot subtract .*TimedeltaIndex'
with tm.assert_raises_regex(TypeError, msg):
tdi - dti

Expand All @@ -531,7 +531,7 @@ def test_dti_isub_tdi(self, tz):
result -= tdi
tm.assert_index_equal(result, expected)

msg = 'cannot subtract TimedeltaIndex and DatetimeIndex'
msg = 'cannot subtract .*TimedeltaIndex'
with tm.assert_raises_regex(TypeError, msg):
tdi -= dti

Expand Down
2 changes: 1 addition & 1 deletion pandas/tests/indexes/timedeltas/test_arithmetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -792,7 +792,7 @@ def test_addition_ops(self):
pytest.raises(ValueError, lambda: tdi[0:1] + dti)

# random indexes
pytest.raises(TypeError, lambda: tdi + Int64Index([1, 2, 3]))
Copy link
Contributor

Choose a reason for hiding this comment

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

is this an api change?

Copy link
Member Author

Choose a reason for hiding this comment

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

not sure if it counts as api change or bugfix. The error currently being raises is incorrect.

pytest.raises(NullFrequencyError, lambda: tdi + Int64Index([1, 2, 3]))

# this is a union!
# pytest.raises(TypeError, lambda : Int64Index([1,2,3]) + tdi)
Expand Down