From 9def9bc16d9e924a97a6a887512f4d0ac04674df Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Sun, 23 Dec 2018 09:15:41 -0600 Subject: [PATCH] DEPR: Undeprecate period - integer addition (#24352) --- doc/source/whatsnew/v0.24.0.rst | 24 ++--- pandas/_libs/tslibs/period.pyx | 6 +- pandas/core/arrays/datetimelike.py | 12 ++- pandas/tests/arithmetic/test_period.py | 116 +++++++++------------ pandas/tests/indexes/period/test_period.py | 7 +- pandas/tests/scalar/period/test_asfreq.py | 9 +- pandas/tests/scalar/period/test_period.py | 61 +++++------ 7 files changed, 94 insertions(+), 141 deletions(-) diff --git a/doc/source/whatsnew/v0.24.0.rst b/doc/source/whatsnew/v0.24.0.rst index 343f17d8ff89c9..724cfddb1b94c0 100644 --- a/doc/source/whatsnew/v0.24.0.rst +++ b/doc/source/whatsnew/v0.24.0.rst @@ -1153,25 +1153,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') @@ -1189,12 +1183,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 diff --git a/pandas/_libs/tslibs/period.pyx b/pandas/_libs/tslibs/period.pyx index 7f5305065382cb..6a257d40dd44b8 100644 --- a/pandas/_libs/tslibs/period.pyx +++ b/pandas/_libs/tslibs/period.pyx @@ -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 @@ -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 @@ -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): diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index c01b04991e52bc..b8364760caa37c 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -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 @@ -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 @@ -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): @@ -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}" diff --git a/pandas/tests/arithmetic/test_period.py b/pandas/tests/arithmetic/test_period.py index 6288e4ec26e1e0..469353042a878f 100644 --- a/pandas/tests/arithmetic/test_period.py +++ b/pandas/tests/arithmetic/test_period.py @@ -659,14 +659,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): @@ -675,24 +671,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): @@ -757,9 +747,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) @@ -768,16 +757,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) @@ -1039,12 +1025,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 @@ -1089,53 +1074,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', diff --git a/pandas/tests/indexes/period/test_period.py b/pandas/tests/indexes/period/test_period.py index 5d78333016f74a..a5169aba2db33c 100644 --- a/pandas/tests/indexes/period/test_period.py +++ b/pandas/tests/indexes/period/test_period.py @@ -339,10 +339,9 @@ def test_is_(self): assert not index.is_(index[:]) assert not index.is_(index.asfreq('M')) assert not index.is_(index.asfreq('A')) - with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): - # GH#22535 - assert not index.is_(index - 2) - assert not index.is_(index - 0) + + assert not index.is_(index - 2) + assert not index.is_(index - 0) def test_contains(self): rng = period_range('2007-01', freq='M', periods=10) diff --git a/pandas/tests/scalar/period/test_asfreq.py b/pandas/tests/scalar/period/test_asfreq.py index 24f2ed88936b78..f46f2da6c076d3 100644 --- a/pandas/tests/scalar/period/test_asfreq.py +++ b/pandas/tests/scalar/period/test_asfreq.py @@ -5,7 +5,6 @@ from pandas.errors import OutOfBoundsDatetime from pandas import Period, offsets -from pandas.util import testing as tm class TestFreqConversion(object): @@ -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 diff --git a/pandas/tests/scalar/period/test_period.py b/pandas/tests/scalar/period/test_period.py index 715a596999e425..d0f87618ad3af3 100644 --- a/pandas/tests/scalar/period/test_period.py +++ b/pandas/tests/scalar/period/test_period.py @@ -303,11 +303,10 @@ def test_multiples(self): assert result1.freq == offsets.YearEnd(2) assert result2.freq == offsets.YearEnd() - with tm.assert_produces_warning(FutureWarning): - assert (result1 + 1).ordinal == result1.ordinal + 2 - assert (1 + result1).ordinal == result1.ordinal + 2 - assert (result1 - 1).ordinal == result2.ordinal - 2 - assert (-1 + result1).ordinal == result2.ordinal - 2 + assert (result1 + 1).ordinal == result1.ordinal + 2 + assert (1 + result1).ordinal == result1.ordinal + 2 + assert (result1 - 1).ordinal == result2.ordinal - 2 + assert (-1 + result1).ordinal == result2.ordinal - 2 @pytest.mark.parametrize('month', MONTHS) def test_period_cons_quarterly(self, month): @@ -331,8 +330,7 @@ def test_period_cons_annual(self, month): stamp = exp.to_timestamp('D', how='end') + timedelta(days=30) p = Period(stamp, freq=freq) - with tm.assert_produces_warning(FutureWarning): - assert p == exp + 1 + assert p == exp + 1 assert isinstance(p, Period) @pytest.mark.parametrize('day', DAYS) @@ -385,16 +383,14 @@ def test_period_cons_mult(self): assert p2.freq == offsets.MonthEnd() assert p2.freqstr == 'M' - with tm.assert_produces_warning(FutureWarning): - result = p1 + 1 - assert result.ordinal == (p2 + 3).ordinal + result = p1 + 1 + assert result.ordinal == (p2 + 3).ordinal assert result.freq == p1.freq assert result.freqstr == '3M' - with tm.assert_produces_warning(FutureWarning): - result = p1 - 1 - assert result.ordinal == (p2 - 3).ordinal + result = p1 - 1 + assert result.ordinal == (p2 - 3).ordinal assert result.freq == p1.freq assert result.freqstr == '3M' @@ -428,27 +424,23 @@ def test_period_cons_combined(self): assert p3.freq == offsets.Hour() assert p3.freqstr == 'H' - with tm.assert_produces_warning(FutureWarning): - result = p1 + 1 - assert result.ordinal == (p3 + 25).ordinal + result = p1 + 1 + assert result.ordinal == (p3 + 25).ordinal assert result.freq == p1.freq assert result.freqstr == '25H' - with tm.assert_produces_warning(FutureWarning): - result = p2 + 1 - assert result.ordinal == (p3 + 25).ordinal + result = p2 + 1 + assert result.ordinal == (p3 + 25).ordinal assert result.freq == p2.freq assert result.freqstr == '25H' - with tm.assert_produces_warning(FutureWarning): - result = p1 - 1 - assert result.ordinal == (p3 - 25).ordinal + result = p1 - 1 + assert result.ordinal == (p3 - 25).ordinal assert result.freq == p1.freq assert result.freqstr == '25H' - with tm.assert_produces_warning(FutureWarning): - result = p2 - 1 - assert result.ordinal == (p3 - 25).ordinal + result = p2 - 1 + assert result.ordinal == (p3 - 25).ordinal assert result.freq == p2.freq assert result.freqstr == '25H' @@ -803,16 +795,14 @@ def test_properties_quarterly(self): # for x in range(3): for qd in (qedec_date, qejan_date, qejun_date): - with tm.assert_produces_warning(FutureWarning): - assert (qd + x).qyear == 2007 - assert (qd + x).quarter == x + 1 + assert (qd + x).qyear == 2007 + assert (qd + x).quarter == x + 1 def test_properties_monthly(self): # Test properties on Periods with daily frequency. m_date = Period(freq='M', year=2007, month=1) for x in range(11): - with tm.assert_produces_warning(FutureWarning): - m_ival_x = m_date + x + m_ival_x = m_date + x assert m_ival_x.year == 2007 if 1 <= x + 1 <= 3: assert m_ival_x.quarter == 1 @@ -832,8 +822,7 @@ def test_properties_weekly(self): assert w_date.quarter == 1 assert w_date.month == 1 assert w_date.week == 1 - with tm.assert_produces_warning(FutureWarning): - assert (w_date - 1).week == 52 + assert (w_date - 1).week == 52 assert w_date.days_in_month == 31 assert Period(freq='W', year=2012, month=2, day=1).days_in_month == 29 @@ -845,8 +834,7 @@ def test_properties_weekly_legacy(self): assert w_date.quarter == 1 assert w_date.month == 1 assert w_date.week == 1 - with tm.assert_produces_warning(FutureWarning): - assert (w_date - 1).week == 52 + assert (w_date - 1).week == 52 assert w_date.days_in_month == 31 exp = Period(freq='W', year=2012, month=2, day=1) @@ -1039,9 +1027,8 @@ def test_sub_delta(self): def test_add_integer(self): per1 = Period(freq='D', year=2008, month=1, day=1) per2 = Period(freq='D', year=2008, month=1, day=2) - with tm.assert_produces_warning(FutureWarning): - assert per1 + 1 == per2 - assert 1 + per1 == per2 + assert per1 + 1 == per2 + assert 1 + per1 == per2 def test_add_sub_nat(self): # GH#13071