Skip to content

API/DEPR: Remove +/- as setops for DatetimeIndex/PeriodIndex (GH9630) #14164

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
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
25 changes: 22 additions & 3 deletions doc/source/whatsnew/v0.19.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -932,14 +932,16 @@ New Behavior:
Index ``+`` / ``-`` no longer used for set operations
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Addition and subtraction of the base Index type (not the numeric subclasses)
Addition and subtraction of the base Index type and of DatetimeIndex
(not the numeric index types)
previously performed set operations (set union and difference). This
behaviour was already deprecated since 0.15.0 (in favor using the specific
``.union()`` and ``.difference()`` methods), and is now disabled. When
possible, ``+`` and ``-`` are now used for element-wise operations, for
example for concatenating strings (:issue:`8227`, :issue:`14127`).
example for concatenating strings or subtracting datetimes
(:issue:`8227`, :issue:`14127`).

Previous Behavior:
Previous behavior:
Copy link
Contributor

Choose a reason for hiding this comment

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

we need to standardize here, I see @jorisvandenbossche and @sinhrks changing these!

IIRC I think we do more of Previous Behavior (e.g. capitalized), and american spelling (no Behaviour). But should just pick one :)

Copy link
Member Author

Choose a reason for hiding this comment

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

yes, will do that in #14176

We indeed need to just make a choice between American or British :-)
(is capitalizing all words also an American habit? :-))

Copy link
Contributor

Choose a reason for hiding this comment

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

I think 2 capital words sets it off s bit more
using American spelling

Copy link
Member Author

Choose a reason for hiding this comment

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

while proofreading the whatsnew, I was thinking of putting the 'New/Old Behavior' in bold, that also sets it off a bit more

Copy link
Contributor

Choose a reason for hiding this comment

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

even better


.. code-block:: ipython

Expand All @@ -962,6 +964,23 @@ For example, the behaviour of adding two integer Indexes:

is unchanged. The base ``Index`` is now made consistent with this behaviour.

Further, because of this change, it is now possible to subtract two
DatetimeIndex objects resulting in a TimedeltaIndex:

Previous behavior:

.. code-block:: ipython

In [1]: pd.DatetimeIndex(['2016-01-01', '2016-01-02']) - pd.DatetimeIndex(['2016-01-02', '2016-01-03'])
FutureWarning: using '-' to provide set differences with datetimelike Indexes is deprecated, use .difference()
Out[1]: DatetimeIndex(['2016-01-01'], dtype='datetime64[ns]', freq=None)

New behavior:

.. ipython:: python

pd.DatetimeIndex(['2016-01-01', '2016-01-02']) - pd.DatetimeIndex(['2016-01-02', '2016-01-03'])


.. _whatsnew_0190.api.difference:

Expand Down
20 changes: 10 additions & 10 deletions pandas/tseries/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
Base and utility classes for tseries type pandas objects.
"""

import warnings
from datetime import datetime, timedelta

from pandas import compat
Expand Down Expand Up @@ -628,10 +627,9 @@ def __add__(self, other):
raise TypeError("cannot add TimedeltaIndex and {typ}"
.format(typ=type(other)))
elif isinstance(other, Index):
warnings.warn("using '+' to provide set union with "
"datetimelike Indexes is deprecated, "
"use .union()", FutureWarning, stacklevel=2)
return self.union(other)
raise TypeError("cannot add {typ1} and {typ2}"
.format(typ1=type(self).__name__,
typ2=type(other).__name__))
elif isinstance(other, (DateOffset, timedelta, np.timedelta64,
tslib.Timedelta)):
return self._add_delta(other)
Expand All @@ -646,20 +644,22 @@ def __add__(self, other):

def __sub__(self, other):
from pandas.core.index import Index
from pandas.tseries.index import DatetimeIndex
from pandas.tseries.tdi import TimedeltaIndex
from pandas.tseries.offsets import DateOffset
if isinstance(other, TimedeltaIndex):
return self._add_delta(-other)
elif isinstance(self, TimedeltaIndex) and isinstance(other, Index):
if not isinstance(other, TimedeltaIndex):
raise TypeError("cannot subtract TimedeltaIndex and {typ}"
.format(typ=type(other)))
.format(typ=type(other).__name__))
return self._add_delta(-other)
elif isinstance(other, DatetimeIndex):
return self._sub_datelike(other)
elif isinstance(other, Index):
warnings.warn("using '-' to provide set differences with "
"datetimelike Indexes is deprecated, "
"use .difference()", FutureWarning, stacklevel=2)
return self.difference(other)
raise TypeError("cannot subtract {typ1} and {typ2}"
.format(typ1=type(self).__name__,
typ2=type(other).__name__))
elif isinstance(other, (DateOffset, timedelta, np.timedelta64,
tslib.Timedelta)):
return self._add_delta(-other)
Expand Down
44 changes: 34 additions & 10 deletions pandas/tseries/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -731,19 +731,43 @@ def _add_datelike(self, other):
def _sub_datelike(self, other):
# subtract a datetime from myself, yielding a TimedeltaIndex
from pandas import TimedeltaIndex
other = Timestamp(other)
if other is tslib.NaT:
result = self._nat_new(box=False)
# require tz compat
elif not self._has_same_tz(other):
raise TypeError("Timestamp subtraction must have the same "
"timezones or no timezones")
if isinstance(other, DatetimeIndex):
Copy link
Member

Choose a reason for hiding this comment

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

can this condition move to base?

Copy link
Member Author

@jorisvandenbossche jorisvandenbossche Sep 7, 2016

Choose a reason for hiding this comment

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

I moved the condition to base, but left it here as well (to follow the same pattern as for the other ops: one method in base (eg _add_delta) which in the subclass handles both scalars as index). OK?

# require tz compat
if not self._has_same_tz(other):
raise TypeError("DatetimeIndex subtraction must have the same "
"timezones or no timezones")
result = self._sub_datelike_dti(other)
elif isinstance(other, (tslib.Timestamp, datetime)):
other = Timestamp(other)
if other is tslib.NaT:
result = self._nat_new(box=False)
# require tz compat
elif not self._has_same_tz(other):
raise TypeError("Timestamp subtraction must have the same "
"timezones or no timezones")
else:
i8 = self.asi8
result = i8 - other.value
result = self._maybe_mask_results(result,
fill_value=tslib.iNaT)
else:
i8 = self.asi8
result = i8 - other.value
result = self._maybe_mask_results(result, fill_value=tslib.iNaT)
raise TypeError("cannot subtract DatetimeIndex and {typ}"
.format(typ=type(other).__name__))
return TimedeltaIndex(result, name=self.name, copy=False)

def _sub_datelike_dti(self, other):
Copy link
Contributor

Choose a reason for hiding this comment

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

this is fine. but just want to check that our naming convention is for these types of routines is consistent.

Copy link
Member Author

Choose a reason for hiding this comment

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

for adding a Timedelta-like vs TimedeltaIndex, there are the method _add_delta_td and _add_delta_tdi which are called from _add_delta, so copied that naming convention

"""subtraction of two DatetimeIndexes"""
if not len(self) == len(other):
raise ValueError("cannot add indices of unequal length")

self_i8 = self.asi8
other_i8 = other.asi8
new_values = self_i8 - other_i8
if self.hasnans or other.hasnans:
mask = (self._isnan) | (other._isnan)
new_values[mask] = tslib.iNaT
return new_values.view('i8')

def _maybe_update_attributes(self, attrs):
""" Update Index attributes (e.g. freq) depending on op """
freq = attrs.get('freq', None)
Expand Down
Loading