Skip to content

Commit

Permalink
DEPR: Undeprecate period - integer addition
Browse files Browse the repository at this point in the history
  • Loading branch information
TomAugspurger committed Dec 19, 2018
1 parent c230f29 commit 88025e9
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 137 deletions.
24 changes: 6 additions & 18 deletions doc/source/whatsnew/v0.24.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1149,25 +1149,19 @@ Deprecations

.. _whatsnew_0240.deprecations.datetimelike_int_ops:

Integer Addition/Subtraction with Datetime-like Classes Is Deprecated
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In the past, users could add or subtract integers or integer-dtypes arrays
from :class:`Period`, :class:`PeriodIndex`, and in some cases
:class:`Timestamp`, :class:`DatetimeIndex` and :class:`TimedeltaIndex`.
Integer Addition/Subtraction with Datetimes and Timedeltas is Deprecated
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

In the past, users could in some cases add or subtract integers or integer-dtype
arrays from :class:`Timestamp`, :class:`DatetimeIndex` and :class:`TimedeltaIndex`.

This usage is now deprecated. Instead add or subtract integer multiples of
the object's ``freq`` attribute. The result of subtraction of :class:`Period`
objects will be agnostic of the multiplier of the objects' ``freq`` attribute
(:issue:`21939`, :issue:`23878`).
the object's ``freq`` attribute (:issue:`21939`, :issue:`23878`).

*Previous Behavior*:

.. code-block:: ipython
In [3]: per = pd.Period('2016Q1')
In [4]: per + 3
Out[4]: Period('2016Q4', 'Q-DEC')
In [5]: ts = pd.Timestamp('1994-05-06 12:15:16', freq=pd.offsets.Hour())
In [6]: ts + 2
Out[6]: Timestamp('1994-05-06 14:15:16', freq='H')
Expand All @@ -1185,12 +1179,6 @@ objects will be agnostic of the multiplier of the objects' ``freq`` attribute
.. ipython:: python
:okwarning:
per = pd.Period('2016Q1')
per + 3
per = pd.Period('2016Q1')
per + 3 * per.freq
ts = pd.Timestamp('1994-05-06 12:15:16', freq=pd.offsets.Hour())
ts + 2 * ts.freq
Expand Down
6 changes: 1 addition & 5 deletions pandas/_libs/tslibs/period.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ cdef extern from "src/datetime/np_datetime.h":
cimport util
from util cimport is_period_object, is_string_object

from timestamps import Timestamp, maybe_integer_op_deprecated
from timestamps import Timestamp
from timezones cimport is_utc, is_tzlocal, get_dst_info
from timedeltas import Timedelta
from timedeltas cimport delta_to_nanoseconds
Expand Down Expand Up @@ -1655,8 +1655,6 @@ cdef class _Period(object):
elif other is NaT:
return NaT
elif util.is_integer_object(other):
maybe_integer_op_deprecated(self)

ordinal = self.ordinal + other * self.freq.n
return Period(ordinal=ordinal, freq=self.freq)
elif (PyDateTime_Check(other) or
Expand All @@ -1683,8 +1681,6 @@ cdef class _Period(object):
neg_other = -other
return self + neg_other
elif util.is_integer_object(other):
maybe_integer_op_deprecated(self)

ordinal = self.ordinal - other * self.freq.n
return Period(ordinal=ordinal, freq=self.freq)
elif is_period_object(other):
Expand Down
12 changes: 8 additions & 4 deletions pandas/core/arrays/datetimelike.py
Original file line number Diff line number Diff line change
Expand Up @@ -905,7 +905,8 @@ def __add__(self, other):
elif lib.is_integer(other):
# This check must come after the check for np.timedelta64
# as is_integer returns True for these
maybe_integer_op_deprecated(self)
if not is_period_dtype(self):
maybe_integer_op_deprecated(self)
result = self._time_shift(other)

# array-like others
Expand All @@ -919,7 +920,8 @@ def __add__(self, other):
# DatetimeIndex, ndarray[datetime64]
return self._add_datetime_arraylike(other)
elif is_integer_dtype(other):
maybe_integer_op_deprecated(self)
if not is_period_dtype(self):
maybe_integer_op_deprecated(self)
result = self._addsub_int_array(other, operator.add)
elif is_float_dtype(other):
# Explicitly catch invalid dtypes
Expand Down Expand Up @@ -966,7 +968,8 @@ def __sub__(self, other):
elif lib.is_integer(other):
# This check must come after the check for np.timedelta64
# as is_integer returns True for these
maybe_integer_op_deprecated(self)
if not is_period_dtype(self):
maybe_integer_op_deprecated(self)
result = self._time_shift(-other)

elif isinstance(other, Period):
Expand All @@ -986,7 +989,8 @@ def __sub__(self, other):
# PeriodIndex
result = self._sub_period_array(other)
elif is_integer_dtype(other):
maybe_integer_op_deprecated(self)
if not is_period_dtype(self):
maybe_integer_op_deprecated(self)
result = self._addsub_int_array(other, operator.sub)
elif isinstance(other, ABCIndexClass):
raise TypeError("cannot subtract {cls} and {typ}"
Expand Down
116 changes: 49 additions & 67 deletions pandas/tests/arithmetic/test_period.py
Original file line number Diff line number Diff line change
Expand Up @@ -557,14 +557,10 @@ def test_pi_sub_offset_array(self, box):
def test_pi_add_iadd_int(self, one):
# Variants of `one` for #19012
rng = pd.period_range('2000-01-01 09:00', freq='H', periods=10)
with tm.assert_produces_warning(FutureWarning, check_stacklevel=False,
clear=[pd.core.arrays.datetimelike]):
result = rng + one
result = rng + one
expected = pd.period_range('2000-01-01 10:00', freq='H', periods=10)
tm.assert_index_equal(result, expected)
with tm.assert_produces_warning(FutureWarning, check_stacklevel=False,
clear=[pd.core.arrays.datetimelike]):
rng += one
rng += one
tm.assert_index_equal(rng, expected)

def test_pi_sub_isub_int(self, one):
Expand All @@ -573,24 +569,18 @@ def test_pi_sub_isub_int(self, one):
the integer 1, e.g. int, long, np.int64, np.uint8, ...
"""
rng = pd.period_range('2000-01-01 09:00', freq='H', periods=10)
with tm.assert_produces_warning(FutureWarning, check_stacklevel=False,
clear=[pd.core.arrays.datetimelike]):
result = rng - one
result = rng - one
expected = pd.period_range('2000-01-01 08:00', freq='H', periods=10)
tm.assert_index_equal(result, expected)
with tm.assert_produces_warning(FutureWarning, check_stacklevel=False,
clear=[pd.core.arrays.datetimelike]):
rng -= one
rng -= one
tm.assert_index_equal(rng, expected)

@pytest.mark.parametrize('five', [5, np.array(5, dtype=np.int64)])
def test_pi_sub_intlike(self, five):
rng = period_range('2007-01', periods=50)

with tm.assert_produces_warning(FutureWarning, check_stacklevel=False,
clear=[pd.core.arrays.datetimelike]):
result = rng - five
exp = rng + (-five)
result = rng - five
exp = rng + (-five)
tm.assert_index_equal(result, exp)

def test_pi_sub_isub_offset(self):
Expand Down Expand Up @@ -655,9 +645,8 @@ def test_pi_add_intarray(self, int_holder, op):
# GH#19959
pi = pd.PeriodIndex([pd.Period('2015Q1'), pd.Period('NaT')])
other = int_holder([4, -1])
with tm.assert_produces_warning(FutureWarning, check_stacklevel=False,
clear=[pd.core.arrays.datetimelike]):
result = op(pi, other)

result = op(pi, other)
expected = pd.PeriodIndex([pd.Period('2016Q1'), pd.Period('NaT')])
tm.assert_index_equal(result, expected)

Expand All @@ -666,16 +655,13 @@ def test_pi_sub_intarray(self, int_holder):
# GH#19959
pi = pd.PeriodIndex([pd.Period('2015Q1'), pd.Period('NaT')])
other = int_holder([4, -1])
with tm.assert_produces_warning(FutureWarning, check_stacklevel=False,
clear=[pd.core.arrays.datetimelike]):
result = pi - other

result = pi - other
expected = pd.PeriodIndex([pd.Period('2014Q1'), pd.Period('NaT')])
tm.assert_index_equal(result, expected)

with pytest.raises(TypeError):
with tm.assert_produces_warning(FutureWarning,
check_stacklevel=False):
other - pi
other - pi

# ---------------------------------------------------------------
# Timedelta-like (timedelta, timedelta64, Timedelta, Tick)
Expand Down Expand Up @@ -937,12 +923,11 @@ def test_pi_ops(self):

expected = PeriodIndex(['2011-03', '2011-04', '2011-05', '2011-06'],
freq='M', name='idx')
with tm.assert_produces_warning(FutureWarning, check_stacklevel=False,
clear=[pd.core.arrays.datetimelike]):
self._check(idx, lambda x: x + 2, expected)
self._check(idx, lambda x: 2 + x, expected)

self._check(idx + 2, lambda x: x - 2, idx)
self._check(idx, lambda x: x + 2, expected)
self._check(idx, lambda x: 2 + x, expected)

self._check(idx + 2, lambda x: x - 2, idx)

result = idx - Period('2011-01', freq='M')
off = idx.freq
Expand Down Expand Up @@ -987,53 +972,50 @@ def test_pi_ops_nat(self):
freq='M', name='idx')
expected = PeriodIndex(['2011-03', '2011-04', 'NaT', '2011-06'],
freq='M', name='idx')
with tm.assert_produces_warning(FutureWarning, check_stacklevel=False,
clear=[pd.core.arrays.datetimelike]):
self._check(idx, lambda x: x + 2, expected)
self._check(idx, lambda x: 2 + x, expected)
self._check(idx, lambda x: np.add(x, 2), expected)

self._check(idx + 2, lambda x: x - 2, idx)
self._check(idx + 2, lambda x: np.subtract(x, 2), idx)
self._check(idx, lambda x: x + 2, expected)
self._check(idx, lambda x: 2 + x, expected)
self._check(idx, lambda x: np.add(x, 2), expected)

self._check(idx + 2, lambda x: x - 2, idx)
self._check(idx + 2, lambda x: np.subtract(x, 2), idx)

# freq with mult
idx = PeriodIndex(['2011-01', '2011-02', 'NaT', '2011-04'],
freq='2M', name='idx')
expected = PeriodIndex(['2011-07', '2011-08', 'NaT', '2011-10'],
freq='2M', name='idx')
with tm.assert_produces_warning(FutureWarning, check_stacklevel=False,
clear=[pd.core.arrays.datetimelike]):
self._check(idx, lambda x: x + 3, expected)
self._check(idx, lambda x: 3 + x, expected)
self._check(idx, lambda x: np.add(x, 3), expected)

self._check(idx + 3, lambda x: x - 3, idx)
self._check(idx + 3, lambda x: np.subtract(x, 3), idx)
self._check(idx, lambda x: x + 3, expected)
self._check(idx, lambda x: 3 + x, expected)
self._check(idx, lambda x: np.add(x, 3), expected)

self._check(idx + 3, lambda x: x - 3, idx)
self._check(idx + 3, lambda x: np.subtract(x, 3), idx)

def test_pi_ops_array_int(self):
with tm.assert_produces_warning(FutureWarning, check_stacklevel=False,
clear=[pd.core.arrays.datetimelike]):
idx = PeriodIndex(['2011-01', '2011-02', 'NaT', '2011-04'],
freq='M', name='idx')
f = lambda x: x + np.array([1, 2, 3, 4])
exp = PeriodIndex(['2011-02', '2011-04', 'NaT', '2011-08'],
freq='M', name='idx')
self._check(idx, f, exp)

f = lambda x: np.add(x, np.array([4, -1, 1, 2]))
exp = PeriodIndex(['2011-05', '2011-01', 'NaT', '2011-06'],
freq='M', name='idx')
self._check(idx, f, exp)

f = lambda x: x - np.array([1, 2, 3, 4])
exp = PeriodIndex(['2010-12', '2010-12', 'NaT', '2010-12'],
freq='M', name='idx')
self._check(idx, f, exp)

f = lambda x: np.subtract(x, np.array([3, 2, 3, -2]))
exp = PeriodIndex(['2010-10', '2010-12', 'NaT', '2011-06'],
freq='M', name='idx')
self._check(idx, f, exp)

idx = PeriodIndex(['2011-01', '2011-02', 'NaT', '2011-04'],
freq='M', name='idx')
f = lambda x: x + np.array([1, 2, 3, 4])
exp = PeriodIndex(['2011-02', '2011-04', 'NaT', '2011-08'],
freq='M', name='idx')
self._check(idx, f, exp)

f = lambda x: np.add(x, np.array([4, -1, 1, 2]))
exp = PeriodIndex(['2011-05', '2011-01', 'NaT', '2011-06'],
freq='M', name='idx')
self._check(idx, f, exp)

f = lambda x: x - np.array([1, 2, 3, 4])
exp = PeriodIndex(['2010-12', '2010-12', 'NaT', '2010-12'],
freq='M', name='idx')
self._check(idx, f, exp)

f = lambda x: np.subtract(x, np.array([3, 2, 3, -2]))
exp = PeriodIndex(['2010-10', '2010-12', 'NaT', '2011-06'],
freq='M', name='idx')
self._check(idx, f, exp)

def test_pi_ops_offset(self):
idx = PeriodIndex(['2011-01-01', '2011-02-01', '2011-03-01',
Expand Down
9 changes: 3 additions & 6 deletions pandas/tests/scalar/period/test_asfreq.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from pandas.errors import OutOfBoundsDatetime

from pandas import Period, offsets
from pandas.util import testing as tm


class TestFreqConversion(object):
Expand All @@ -16,17 +15,15 @@ def test_asfreq_near_zero(self, freq):
per = Period('0001-01-01', freq=freq)
tup1 = (per.year, per.hour, per.day)

with tm.assert_produces_warning(FutureWarning):
prev = per - 1
prev = per - 1
assert prev.ordinal == per.ordinal - 1
tup2 = (prev.year, prev.month, prev.day)
assert tup2 < tup1

def test_asfreq_near_zero_weekly(self):
# GH#19834
with tm.assert_produces_warning(FutureWarning):
per1 = Period('0001-01-01', 'D') + 6
per2 = Period('0001-01-01', 'D') - 6
per1 = Period('0001-01-01', 'D') + 6
per2 = Period('0001-01-01', 'D') - 6
week1 = per1.asfreq('W')
week2 = per2.asfreq('W')
assert week1 != week2
Expand Down
Loading

0 comments on commit 88025e9

Please sign in to comment.