From 145eb6a912c23adb1b83ba4b91236b0332b5fe66 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Wed, 29 Aug 2018 11:10:37 -0700 Subject: [PATCH 01/24] DEPR: deprecate integer add/sub with DTI/TDI/PI/Timestamp/Period --- doc/source/whatsnew/v0.24.0.txt | 2 ++ pandas/_libs/tslibs/period.pyx | 17 +++++++++++++++++ pandas/_libs/tslibs/timestamps.pyx | 12 +++++++++++- pandas/core/arrays/datetimelike.py | 26 ++++++++++++++++++++++++++ 4 files changed, 56 insertions(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index e70f3f0f6164b..73736ea0987ba 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -514,6 +514,8 @@ Deprecations - The signature of :meth:`Series.to_csv` has been uniformed to that of doc:meth:`DataFrame.to_csv`: the name of the first argument is now 'path_or_buf', the order of subsequent arguments has changed, the 'header' argument now defaults to True. (:issue:`19715`) - :meth:`Categorical.from_codes` has deprecated providing float values for the ``codes`` argument. (:issue:`21767`) - :func:`pandas.read_table` is deprecated. Instead, use :func:`pandas.read_csv` passing ``sep='\t'`` if necessary (:issue:`21948`) +- Addition or subtraction of integers with :class:`Timestamp`, :class:`Period`, :class:`DatetimeIndex`, :class:`TimedeltaIndex`, :class:`PeriodIndex` is deprecated, will be removed in a future version (:issue:`21939`) +- Addition or subtraction of integer-dtyped arrays with:class:`DatetimeIndex`, :class:`TimedeltaIndex`, :class:`PeriodIndex` is deprecated, will be removed in a future version (:issue:`21939`) .. _whatsnew_0240.prior_deprecations: diff --git a/pandas/_libs/tslibs/period.pyx b/pandas/_libs/tslibs/period.pyx index f68b6d8fdef57..e131bf77a9cb1 100644 --- a/pandas/_libs/tslibs/period.pyx +++ b/pandas/_libs/tslibs/period.pyx @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- from datetime import datetime, date +import warnings from cpython cimport ( PyUnicode_Check, @@ -1645,6 +1646,14 @@ cdef class _Period(object): elif other is NaT: return NaT elif util.is_integer_object(other): + warnings.warn("Addition of integers to {cls} is " + "deprecated, will be removed in version " + "0.26.0 (or 1.0, whichever comes first). " + "Instead of adding `n`, add " + "`n * self.freq`" + .format(cls=type(self).__name__), + FutureWarning) + ordinal = self.ordinal + other * self.freq.n return Period(ordinal=ordinal, freq=self.freq) elif (PyDateTime_Check(other) or @@ -1671,6 +1680,14 @@ cdef class _Period(object): neg_other = -other return self + neg_other elif util.is_integer_object(other): + warnings.warn("Subtraction of integers from {cls} is " + "deprecated, will be removed in version " + "0.26.0 (or 1.0, whichever comes first). " + "Instead of subtracting `n`, subtract " + "`n * self.freq`" + .format(cls=type(self).__name__), + FutureWarning) + ordinal = self.ordinal - other * self.freq.n return Period(ordinal=ordinal, freq=self.freq) elif is_period_object(other): diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index 3ab1396c0fe38..0edc3f410f4fd 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -250,7 +250,8 @@ cdef class _Timestamp(datetime): return np.datetime64(self.value, 'ns') def __add__(self, other): - cdef int64_t other_int, nanos + cdef: + int64_t other_int, nanos if is_timedelta64_object(other): other_int = other.astype('timedelta64[ns]').view('i8') @@ -258,6 +259,15 @@ cdef class _Timestamp(datetime): tz=self.tzinfo, freq=self.freq) elif is_integer_object(other): + if self.freq is None: + warnings.warn("Addition of integers to {cls} is " + "deprecated, will be removed in version " + "0.26.0 (or 1.0, whichever comes first). " + "Instead of adding `n`, add " + "`n * self.freq`" + .format(cls=type(self).__name__), + FutureWarning) + if self is NaT: # to be compat with Period return NaT diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index eb8821382037d..bc0bcb0176323 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -466,6 +466,15 @@ def _addsub_int_array(self, other, op): result : same class as self """ assert op in [operator.add, operator.sub] + + if self.freq is not None: + # case with freq of None will raise + warnings.warn("Addition/subtraction of integer array from {cls} " + "is deprecated, will be removed in version " + "0.26.0 (or 1.0, whichever comes first). " + "Instead of adding `arr`, add `arr * self.freq`" + .format(cls=type(self).__name__), FutureWarning) + if is_period_dtype(self): # easy case for PeriodIndex if op is operator.sub: @@ -584,6 +593,14 @@ 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 + if self.freq is not None: + # case with freq of None will raise + warnings.warn("Addition of integers to {cls} is " + "deprecated, will be removed in version " + "0.26.0 (or 1.0, whichever comes first). " + "Instead of adding `n`, add `n * self.freq`" + .format(cls=type(self).__name__), + FutureWarning) result = self.shift(other) # array-like others @@ -636,6 +653,15 @@ 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 + if self.freq is not None: + # case with freq of None will raise + warnings.warn("Subtraction of integers from {cls} is " + "deprecated, will be removed in version " + "0.26.0 (or 1.0, whichever comes first). " + "Instead of subtracting `n`, subtract " + "`n * self.freq`" + .format(cls=type(self).__name__), + FutureWarning) result = self.shift(-other) elif isinstance(other, Period): result = self._sub_period(other) From d5c5f28b4908a92078a6a7cd5ddbc2a674ee31c8 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Wed, 29 Aug 2018 11:44:16 -0700 Subject: [PATCH 02/24] say future version instead of a specific version number --- pandas/_libs/tslibs/period.pyx | 10 ++++------ pandas/_libs/tslibs/timestamps.pyx | 5 ++--- pandas/core/arrays/datetimelike.py | 19 +++++++++---------- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/pandas/_libs/tslibs/period.pyx b/pandas/_libs/tslibs/period.pyx index e131bf77a9cb1..66dc81c1a6e02 100644 --- a/pandas/_libs/tslibs/period.pyx +++ b/pandas/_libs/tslibs/period.pyx @@ -1647,9 +1647,8 @@ cdef class _Period(object): return NaT elif util.is_integer_object(other): warnings.warn("Addition of integers to {cls} is " - "deprecated, will be removed in version " - "0.26.0 (or 1.0, whichever comes first). " - "Instead of adding `n`, add " + "deprecated, will be removed in a future " + "version. Instead of adding `n`, add " "`n * self.freq`" .format(cls=type(self).__name__), FutureWarning) @@ -1681,9 +1680,8 @@ cdef class _Period(object): return self + neg_other elif util.is_integer_object(other): warnings.warn("Subtraction of integers from {cls} is " - "deprecated, will be removed in version " - "0.26.0 (or 1.0, whichever comes first). " - "Instead of subtracting `n`, subtract " + "deprecated, will be removed in a future " + "version. Instead of subtracting `n`, subtract " "`n * self.freq`" .format(cls=type(self).__name__), FutureWarning) diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index 0edc3f410f4fd..5909e7da0be97 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -261,9 +261,8 @@ cdef class _Timestamp(datetime): elif is_integer_object(other): if self.freq is None: warnings.warn("Addition of integers to {cls} is " - "deprecated, will be removed in version " - "0.26.0 (or 1.0, whichever comes first). " - "Instead of adding `n`, add " + "deprecated, will be removed in a future " + "version. Instead of adding `n`, add " "`n * self.freq`" .format(cls=type(self).__name__), FutureWarning) diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index bc0bcb0176323..aed5741107179 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -470,9 +470,9 @@ def _addsub_int_array(self, other, op): if self.freq is not None: # case with freq of None will raise warnings.warn("Addition/subtraction of integer array from {cls} " - "is deprecated, will be removed in version " - "0.26.0 (or 1.0, whichever comes first). " - "Instead of adding `arr`, add `arr * self.freq`" + "is deprecated, will be removed in a future " + "version. Instead of adding `arr`, " + "add `arr * self.freq`" .format(cls=type(self).__name__), FutureWarning) if is_period_dtype(self): @@ -596,9 +596,9 @@ def __add__(self, other): if self.freq is not None: # case with freq of None will raise warnings.warn("Addition of integers to {cls} is " - "deprecated, will be removed in version " - "0.26.0 (or 1.0, whichever comes first). " - "Instead of adding `n`, add `n * self.freq`" + "deprecated, will be removed in a future " + "version. Instead of adding `n`, " + "add `n * self.freq`" .format(cls=type(self).__name__), FutureWarning) result = self.shift(other) @@ -656,10 +656,9 @@ def __sub__(self, other): if self.freq is not None: # case with freq of None will raise warnings.warn("Subtraction of integers from {cls} is " - "deprecated, will be removed in version " - "0.26.0 (or 1.0, whichever comes first). " - "Instead of subtracting `n`, subtract " - "`n * self.freq`" + "deprecated, will be removed in a future " + "version. Instead of subtracting `n`, " + "subtract `n * self.freq`" .format(cls=type(self).__name__), FutureWarning) result = self.shift(-other) From 81c9eab4c25974fd351204b051211fc534d8a112 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Fri, 31 Aug 2018 07:35:53 -0700 Subject: [PATCH 03/24] typo fixup --- doc/source/whatsnew/v0.24.0.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index d73d14fcf6ace..936d0e7d333f7 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -517,7 +517,7 @@ Deprecations - :meth:`Series.str.cat` has deprecated using arbitrary list-likes *within* list-likes. A list-like container may still contain many ``Series``, ``Index`` or 1-dimensional ``np.ndarray``, or alternatively, only scalar values. (:issue:`21950`) - Addition or subtraction of integers with :class:`Timestamp`, :class:`Period`, :class:`DatetimeIndex`, :class:`TimedeltaIndex`, :class:`PeriodIndex` is deprecated, will be removed in a future version (:issue:`21939`) -- Addition or subtraction of integer-dtyped arrays with:class:`DatetimeIndex`, :class:`TimedeltaIndex`, :class:`PeriodIndex` is deprecated, will be removed in a future version (:issue:`21939`) +- Addition or subtraction of integer-dtyped arrays with :class:`DatetimeIndex`, :class:`TimedeltaIndex`, :class:`PeriodIndex` is deprecated, will be removed in a future version (:issue:`21939`) .. _whatsnew_0240.prior_deprecations: From 26c9966a7118a544573633c111f1b1d30a16f7b8 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Sat, 1 Sep 2018 11:48:47 -0700 Subject: [PATCH 04/24] Catch FutureWarning/PerformanceWarning in tests --- pandas/_libs/tslibs/period.pyx | 5 +- pandas/core/arrays/datetimelike.py | 3 +- pandas/core/resample.py | 2 +- pandas/tests/arithmetic/test_datetime64.py | 59 ++++++++---- pandas/tests/arithmetic/test_period.py | 100 ++++++++++++--------- pandas/tests/scalar/period/test_asfreq.py | 10 ++- pandas/tests/scalar/period/test_period.py | 65 ++++++++------ 7 files changed, 152 insertions(+), 92 deletions(-) diff --git a/pandas/_libs/tslibs/period.pyx b/pandas/_libs/tslibs/period.pyx index 66dc81c1a6e02..dab7ee32209ff 100644 --- a/pandas/_libs/tslibs/period.pyx +++ b/pandas/_libs/tslibs/period.pyx @@ -1771,7 +1771,7 @@ cdef class _Period(object): def end_time(self): # freq.n can't be negative or 0 # ordinal = (self + self.freq.n).start_time.value - 1 - ordinal = (self + 1).start_time.value - 1 + ordinal = (self + self.freq).start_time.value - 1 return Timestamp(ordinal) def to_timestamp(self, freq=None, how='start', tz=None): @@ -1798,7 +1798,8 @@ cdef class _Period(object): end = how == 'E' if end: - return (self + 1).to_timestamp(how='start') - Timedelta(1, 'ns') + endpoint = (self + self.freq).to_timestamp(how='start') + return endpoint - Timedelta(1, 'ns') if freq is None: base, mult = get_freq_code(self.freq) diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index aed5741107179..7efa8e8da7cd5 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -473,7 +473,8 @@ def _addsub_int_array(self, other, op): "is deprecated, will be removed in a future " "version. Instead of adding `arr`, " "add `arr * self.freq`" - .format(cls=type(self).__name__), FutureWarning) + .format(cls=type(self).__name__), + FutureWarning) if is_period_dtype(self): # easy case for PeriodIndex diff --git a/pandas/core/resample.py b/pandas/core/resample.py index ae59014ac34f4..07485228e487a 100644 --- a/pandas/core/resample.py +++ b/pandas/core/resample.py @@ -1428,7 +1428,7 @@ def _get_time_delta_bins(self, ax): freq=self.freq, name=ax.name) - end_stamps = labels + 1 + end_stamps = labels + self.freq bins = ax.searchsorted(end_stamps, side='left') # Addresses GH #10530 diff --git a/pandas/tests/arithmetic/test_datetime64.py b/pandas/tests/arithmetic/test_datetime64.py index d597ea834f097..f1aee224a2852 100644 --- a/pandas/tests/arithmetic/test_datetime64.py +++ b/pandas/tests/arithmetic/test_datetime64.py @@ -1035,7 +1035,8 @@ def test_dti_add_int(self, tz_naive_fixture, one): tz = tz_naive_fixture rng = pd.date_range('2000-01-01 09:00', freq='H', periods=10, tz=tz) - result = rng + one + with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): + result = rng + one expected = pd.date_range('2000-01-01 10:00', freq='H', periods=10, tz=tz) tm.assert_index_equal(result, expected) @@ -1046,14 +1047,16 @@ def test_dti_iadd_int(self, tz_naive_fixture, one): periods=10, tz=tz) expected = pd.date_range('2000-01-01 10:00', freq='H', periods=10, tz=tz) - rng += one + with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): + rng += one tm.assert_index_equal(rng, expected) def test_dti_sub_int(self, tz_naive_fixture, one): tz = tz_naive_fixture rng = pd.date_range('2000-01-01 09:00', freq='H', periods=10, tz=tz) - result = rng - one + with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): + result = rng - one expected = pd.date_range('2000-01-01 08:00', freq='H', periods=10, tz=tz) tm.assert_index_equal(result, expected) @@ -1064,7 +1067,8 @@ def test_dti_isub_int(self, tz_naive_fixture, one): periods=10, tz=tz) expected = pd.date_range('2000-01-01 08:00', freq='H', periods=10, tz=tz) - rng -= one + with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): + rng -= one tm.assert_index_equal(rng, expected) # ------------------------------------------------------------- @@ -1077,9 +1081,11 @@ def test_dti_add_intarray_tick(self, box, freq): dti = pd.date_range('2016-01-01', periods=2, freq=freq) other = box([4, -1]) expected = DatetimeIndex([dti[n] + other[n] for n in range(len(dti))]) - result = dti + other + with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): + result = dti + other tm.assert_index_equal(result, expected) - result = other + dti + with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): + result = other + dti tm.assert_index_equal(result, expected) @pytest.mark.parametrize('freq', ['W', 'M', 'MS', 'Q']) @@ -1089,10 +1095,17 @@ def test_dti_add_intarray_non_tick(self, box, freq): dti = pd.date_range('2016-01-01', periods=2, freq=freq) other = box([4, -1]) expected = DatetimeIndex([dti[n] + other[n] for n in range(len(dti))]) - with tm.assert_produces_warning(PerformanceWarning): + + # tm.assert_produces_warning does not handle cases where we expect + # two warnings, in this case PerformanceWarning and FutureWarning. + # Until that is fixed, we don't catch either + with warnings.catch_warnings(): + warnings.simplefilter("ignore") result = dti + other tm.assert_index_equal(result, expected) - with tm.assert_produces_warning(PerformanceWarning): + + with warnings.catch_warnings(): + warnings.simplefilter("ignore") result = other + dti tm.assert_index_equal(result, expected) @@ -1637,13 +1650,15 @@ def test_dti_add_offset_array(self, tz_naive_fixture): dti = pd.date_range('2017-01-01', periods=2, tz=tz) other = np.array([pd.offsets.MonthEnd(), pd.offsets.Day(n=2)]) - with tm.assert_produces_warning(PerformanceWarning): + with tm.assert_produces_warning(PerformanceWarning, + clear=[pd.core.arrays.datetimelike]): res = dti + other expected = DatetimeIndex([dti[n] + other[n] for n in range(len(dti))], name=dti.name, freq='infer') tm.assert_index_equal(res, expected) - with tm.assert_produces_warning(PerformanceWarning): + with tm.assert_produces_warning(PerformanceWarning, + clear=[pd.core.arrays.datetimelike]): res2 = other + dti tm.assert_index_equal(res2, expected) @@ -1657,13 +1672,15 @@ def test_dti_add_offset_index(self, tz_naive_fixture, names): other = pd.Index([pd.offsets.MonthEnd(), pd.offsets.Day(n=2)], name=names[1]) - with tm.assert_produces_warning(PerformanceWarning): + with tm.assert_produces_warning(PerformanceWarning, + clear=[pd.core.arrays.datetimelike]): res = dti + other expected = DatetimeIndex([dti[n] + other[n] for n in range(len(dti))], name=names[2], freq='infer') tm.assert_index_equal(res, expected) - with tm.assert_produces_warning(PerformanceWarning): + with tm.assert_produces_warning(PerformanceWarning, + clear=[pd.core.arrays.datetimelike]): res2 = other + dti tm.assert_index_equal(res2, expected) @@ -1673,7 +1690,8 @@ def test_dti_sub_offset_array(self, tz_naive_fixture): dti = pd.date_range('2017-01-01', periods=2, tz=tz) other = np.array([pd.offsets.MonthEnd(), pd.offsets.Day(n=2)]) - with tm.assert_produces_warning(PerformanceWarning): + with tm.assert_produces_warning(PerformanceWarning, + clear=[pd.core.arrays.datetimelike]): res = dti - other expected = DatetimeIndex([dti[n] - other[n] for n in range(len(dti))], name=dti.name, freq='infer') @@ -1689,7 +1707,8 @@ def test_dti_sub_offset_index(self, tz_naive_fixture, names): other = pd.Index([pd.offsets.MonthEnd(), pd.offsets.Day(n=2)], name=names[1]) - with tm.assert_produces_warning(PerformanceWarning): + with tm.assert_produces_warning(PerformanceWarning, + clear=[pd.core.arrays.datetimelike]): res = dti - other expected = DatetimeIndex([dti[n] - other[n] for n in range(len(dti))], name=names[2], freq='infer') @@ -1708,18 +1727,21 @@ def test_dti_with_offset_series(self, tz_naive_fixture, names): expected_add = Series([dti[n] + other[n] for n in range(len(dti))], name=names[2]) - with tm.assert_produces_warning(PerformanceWarning): + with tm.assert_produces_warning(PerformanceWarning, + clear=[pd.core.arrays.datetimelike]): res = dti + other tm.assert_series_equal(res, expected_add) - with tm.assert_produces_warning(PerformanceWarning): + with tm.assert_produces_warning(PerformanceWarning, + clear=[pd.core.arrays.datetimelike]): res2 = other + dti tm.assert_series_equal(res2, expected_add) expected_sub = Series([dti[n] - other[n] for n in range(len(dti))], name=names[2]) - with tm.assert_produces_warning(PerformanceWarning): + with tm.assert_produces_warning(PerformanceWarning, + clear=[pd.core.arrays.datetimelike]): res3 = dti - other tm.assert_series_equal(res3, expected_sub) @@ -1755,7 +1777,8 @@ def test_dt64_with_offset_array(klass, assert_func): # GH#10699 # array of offsets box = Series if klass is Series else pd.Index - with tm.assert_produces_warning(PerformanceWarning): + with tm.assert_produces_warning(PerformanceWarning, + clear=[pd.core.arrays.datetimelike]): s = klass([Timestamp('2000-1-1'), Timestamp('2000-2-1')]) result = s + box([pd.offsets.DateOffset(years=1), pd.offsets.MonthEnd()]) diff --git a/pandas/tests/arithmetic/test_period.py b/pandas/tests/arithmetic/test_period.py index 92123bf48bb47..f2fdfc323cf89 100644 --- a/pandas/tests/arithmetic/test_period.py +++ b/pandas/tests/arithmetic/test_period.py @@ -606,18 +606,24 @@ 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) - result = rng - one + with tm.assert_produces_warning(FutureWarning, check_stacklevel=False, + clear=[pd.core.arrays.datetimelike]): + result = rng - one expected = pd.period_range('2000-01-01 08:00', freq='H', periods=10) tm.assert_index_equal(result, expected) - rng -= one + with tm.assert_produces_warning(FutureWarning, check_stacklevel=False, + clear=[pd.core.arrays.datetimelike]): + 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) - result = rng - five - exp = rng + (-five) + with tm.assert_produces_warning(FutureWarning, check_stacklevel=False, + clear=[pd.core.arrays.datetimelike]): + result = rng - five + exp = rng + (-five) tm.assert_index_equal(result, exp) def test_pi_sub_isub_offset(self): @@ -647,7 +653,9 @@ def test_pi_add_intarray(self, box, op): # GH#19959 pi = pd.PeriodIndex([pd.Period('2015Q1'), pd.Period('NaT')]) other = box([4, -1]) - result = op(pi, other) + with tm.assert_produces_warning(FutureWarning, check_stacklevel=False, + clear=[pd.core.arrays.datetimelike]): + result = op(pi, other) expected = pd.PeriodIndex([pd.Period('2016Q1'), pd.Period('NaT')]) tm.assert_index_equal(result, expected) @@ -656,7 +664,9 @@ def test_pi_sub_intarray(self, box): # GH#19959 pi = pd.PeriodIndex([pd.Period('2015Q1'), pd.Period('NaT')]) other = box([4, -1]) - result = pi - other + with tm.assert_produces_warning(FutureWarning, check_stacklevel=False, + clear=[pd.core.arrays.datetimelike]): + result = pi - other expected = pd.PeriodIndex([pd.Period('2014Q1'), pd.Period('NaT')]) tm.assert_index_equal(result, expected) @@ -869,10 +879,13 @@ def test_pi_ops(self): expected = PeriodIndex(['2011-03', '2011-04', '2011-05', '2011-06'], freq='M', name='idx') - self._check(idx, lambda x: x + 2, expected) - self._check(idx, lambda x: 2 + x, expected) + 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 + 2, lambda x: x - 2, idx) result = idx - Period('2011-01', freq='M') off = idx.freq exp = pd.Index([0 * off, 1 * off, 2 * off, 3 * off], name='idx') @@ -889,7 +902,6 @@ def test_pi_ops_errors(self, ng): ser = pd.Series(idx) msg = r"unsupported operand type\(s\)" - for obj in [idx, ser]: with tm.assert_raises_regex(TypeError, msg): obj + ng @@ -924,47 +936,53 @@ def test_pi_ops_nat(self): freq='M', name='idx') expected = PeriodIndex(['2011-03', '2011-04', 'NaT', '2011-06'], freq='M', name='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) + 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 + 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') - 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) + 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 + 3, lambda x: x - 3, idx) + self._check(idx + 3, lambda x: np.subtract(x, 3), idx) def test_pi_ops_array_int(self): - 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) + 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) def test_pi_ops_offset(self): idx = PeriodIndex(['2011-01-01', '2011-02-01', '2011-03-01', diff --git a/pandas/tests/scalar/period/test_asfreq.py b/pandas/tests/scalar/period/test_asfreq.py index 2e3867db65604..432d55ef5967a 100644 --- a/pandas/tests/scalar/period/test_asfreq.py +++ b/pandas/tests/scalar/period/test_asfreq.py @@ -16,15 +16,17 @@ def test_asfreq_near_zero(self, freq): per = Period('0001-01-01', freq=freq) tup1 = (per.year, per.hour, per.day) - prev = per - 1 - assert (per - 1).ordinal == per.ordinal - 1 + with tm.assert_produces_warning(FutureWarning): + 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 - per1 = Period('0001-01-01', 'D') + 6 - per2 = Period('0001-01-01', 'D') - 6 + with tm.assert_produces_warning(FutureWarning): + 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 c4c9a5f8452de..49a45c889098a 100644 --- a/pandas/tests/scalar/period/test_period.py +++ b/pandas/tests/scalar/period/test_period.py @@ -74,7 +74,9 @@ def test_period_cons_annual(self, month): exp = Period('1989', freq=freq) stamp = exp.to_timestamp('D', how='end') + timedelta(days=30) p = Period(stamp, freq=freq) - assert p == exp + 1 + + with tm.assert_produces_warning(FutureWarning): + assert p == exp + 1 assert isinstance(p, Period) @pytest.mark.parametrize('day', DAYS) @@ -127,13 +129,16 @@ def test_period_cons_mult(self): assert p2.freq == offsets.MonthEnd() assert p2.freqstr == 'M' - result = p1 + 1 - assert result.ordinal == (p2 + 3).ordinal + with tm.assert_produces_warning(FutureWarning): + result = p1 + 1 + assert result.ordinal == (p2 + 3).ordinal + assert result.freq == p1.freq assert result.freqstr == '3M' - result = p1 - 1 - assert result.ordinal == (p2 - 3).ordinal + with tm.assert_produces_warning(FutureWarning): + result = p1 - 1 + assert result.ordinal == (p2 - 3).ordinal assert result.freq == p1.freq assert result.freqstr == '3M' @@ -167,23 +172,27 @@ def test_period_cons_combined(self): assert p3.freq == offsets.Hour() assert p3.freqstr == 'H' - result = p1 + 1 - assert result.ordinal == (p3 + 25).ordinal + with tm.assert_produces_warning(FutureWarning): + result = p1 + 1 + assert result.ordinal == (p3 + 25).ordinal assert result.freq == p1.freq assert result.freqstr == '25H' - result = p2 + 1 - assert result.ordinal == (p3 + 25).ordinal + with tm.assert_produces_warning(FutureWarning): + result = p2 + 1 + assert result.ordinal == (p3 + 25).ordinal assert result.freq == p2.freq assert result.freqstr == '25H' - result = p1 - 1 - assert result.ordinal == (p3 - 25).ordinal + with tm.assert_produces_warning(FutureWarning): + result = p1 - 1 + assert result.ordinal == (p3 - 25).ordinal assert result.freq == p1.freq assert result.freqstr == '25H' - result = p2 - 1 - assert result.ordinal == (p3 - 25).ordinal + with tm.assert_produces_warning(FutureWarning): + result = p2 - 1 + assert result.ordinal == (p3 - 25).ordinal assert result.freq == p2.freq assert result.freqstr == '25H' @@ -598,7 +607,7 @@ def test_to_timestamp(self): from_lst = ['A', 'Q', 'M', 'W', 'B', 'D', 'H', 'Min', 'S'] def _ex(p): - return Timestamp((p + 1).start_time.value - 1) + return Timestamp((p + p.freq).start_time.value - 1) for i, fcode in enumerate(from_lst): p = Period('1982', freq=fcode) @@ -717,14 +726,16 @@ def test_properties_quarterly(self): # for x in range(3): for qd in (qedec_date, qejan_date, qejun_date): - assert (qd + x).qyear == 2007 - assert (qd + x).quarter == x + 1 + with tm.assert_produces_warning(FutureWarning): + 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): - m_ival_x = m_date + x + with tm.assert_produces_warning(FutureWarning): + m_ival_x = m_date + x assert m_ival_x.year == 2007 if 1 <= x + 1 <= 3: assert m_ival_x.quarter == 1 @@ -744,7 +755,8 @@ def test_properties_weekly(self): assert w_date.quarter == 1 assert w_date.month == 1 assert w_date.week == 1 - assert (w_date - 1).week == 52 + with tm.assert_produces_warning(FutureWarning): + 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 @@ -756,7 +768,8 @@ def test_properties_weekly_legacy(self): assert w_date.quarter == 1 assert w_date.month == 1 assert w_date.week == 1 - assert (w_date - 1).week == 52 + with tm.assert_produces_warning(FutureWarning): + assert (w_date - 1).week == 52 assert w_date.days_in_month == 31 exp = Period(freq='W', year=2012, month=2, day=1) @@ -904,10 +917,11 @@ def test_multiples(self): assert result1.freq == offsets.YearEnd(2) assert result2.freq == offsets.YearEnd() - 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 + 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 def test_round_trip(self): @@ -1013,8 +1027,9 @@ class TestMethods(object): def test_add(self): dt1 = Period(freq='D', year=2008, month=1, day=1) dt2 = Period(freq='D', year=2008, month=1, day=2) - assert dt1 + 1 == dt2 - assert 1 + dt1 == dt2 + with tm.assert_produces_warning(FutureWarning): + assert dt1 + 1 == dt2 + assert 1 + dt1 == dt2 def test_add_pdnat(self): p = pd.Period('2011-01', freq='M') From 8952cb5e5f214e3863d3f16cb92c460fe4cf160f Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Wed, 5 Sep 2018 10:32:08 -0700 Subject: [PATCH 05/24] set stacklevel --- pandas/core/arrays/datetimelike.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index 7efa8e8da7cd5..12a5a22f693fc 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -469,12 +469,14 @@ def _addsub_int_array(self, other, op): if self.freq is not None: # case with freq of None will raise + # we need to use a different stacklevel for Index vs Array + lvl = 3 + 1 * isinstance(self, ABCIndexClass) warnings.warn("Addition/subtraction of integer array from {cls} " "is deprecated, will be removed in a future " "version. Instead of adding `arr`, " "add `arr * self.freq`" .format(cls=type(self).__name__), - FutureWarning) + FutureWarning, stacklevel=lvl) if is_period_dtype(self): # easy case for PeriodIndex @@ -596,12 +598,14 @@ def __add__(self, other): # as is_integer returns True for these if self.freq is not None: # case with freq of None will raise + # we need to use a different stacklevel for Index vs Array + lvl = 2 + 1 * isinstance(self, ABCIndexClass) warnings.warn("Addition of integers to {cls} is " "deprecated, will be removed in a future " "version. Instead of adding `n`, " "add `n * self.freq`" .format(cls=type(self).__name__), - FutureWarning) + FutureWarning, stacklevel=lvl) result = self.shift(other) # array-like others @@ -656,12 +660,14 @@ def __sub__(self, other): # as is_integer returns True for these if self.freq is not None: # case with freq of None will raise + # we need to use a different stacklevel for Index vs Array + lvl = 2 + 1 * isinstance(self, ABCIndexClass) warnings.warn("Subtraction of integers from {cls} is " "deprecated, will be removed in a future " "version. Instead of subtracting `n`, " "subtract `n * self.freq`" .format(cls=type(self).__name__), - FutureWarning) + FutureWarning, stacklevel=lvl) result = self.shift(-other) elif isinstance(other, Period): result = self._sub_period(other) From d0fa41a12ed70494fd25bc3c7ce286f8204177b6 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Thu, 6 Sep 2018 21:45:46 -0700 Subject: [PATCH 06/24] Avoid warnings in plotting._converter --- pandas/plotting/_converter.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pandas/plotting/_converter.py b/pandas/plotting/_converter.py index 96ea8a542a451..6a2a6be9f3e22 100644 --- a/pandas/plotting/_converter.py +++ b/pandas/plotting/_converter.py @@ -583,7 +583,7 @@ def period_break(dates, period): Name of the period to monitor. """ current = getattr(dates, period) - previous = getattr(dates - 1, period) + previous = getattr(dates - 1 * dates.freq, period) return np.nonzero(current - previous)[0] @@ -669,7 +669,7 @@ def first_label(label_flags): def _hour_finder(label_interval, force_year_start): _hour = dates_.hour - _prev_hour = (dates_ - 1).hour + _prev_hour = (dates_ - 1 * dates_.freq).hour hour_start = (_hour - _prev_hour) != 0 info_maj[day_start] = True info_min[hour_start & (_hour % label_interval == 0)] = True @@ -683,7 +683,7 @@ def _hour_finder(label_interval, force_year_start): def _minute_finder(label_interval): hour_start = period_break(dates_, 'hour') _minute = dates_.minute - _prev_minute = (dates_ - 1).minute + _prev_minute = (dates_ - 1 * dates_.freq).minute minute_start = (_minute - _prev_minute) != 0 info_maj[hour_start] = True info_min[minute_start & (_minute % label_interval == 0)] = True @@ -696,7 +696,7 @@ def _minute_finder(label_interval): def _second_finder(label_interval): minute_start = period_break(dates_, 'minute') _second = dates_.second - _prev_second = (dates_ - 1).second + _prev_second = (dates_ - 1 * dates_.freq).second second_start = (_second - _prev_second) != 0 info['maj'][minute_start] = True info['min'][second_start & (_second % label_interval == 0)] = True From b7e3dcfceeadad52dbae5a4a2daf85974a1e0c84 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Sat, 8 Sep 2018 19:02:29 -0700 Subject: [PATCH 07/24] Fixup flipped condition --- pandas/_libs/tslibs/timestamps.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index 33a7fc9e4139d..1a3ecb7fb0e38 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -259,7 +259,7 @@ cdef class _Timestamp(datetime): tz=self.tzinfo, freq=self.freq) elif is_integer_object(other): - if self.freq is None: + if self.freq is not None: warnings.warn("Addition of integers to {cls} is " "deprecated, will be removed in a future " "version. Instead of adding `n`, add " From f24643cd03b09f8ba143c4c853d5cebe16031993 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Fri, 28 Sep 2018 15:46:42 -0700 Subject: [PATCH 08/24] whatsnew section on deprecation --- doc/source/whatsnew/v0.24.0.txt | 53 +++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index 658059050652d..8e029a743b8d9 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -574,8 +574,57 @@ Deprecations many ``Series``, ``Index`` or 1-dimensional ``np.ndarray``, or alternatively, only scalar values. (:issue:`21950`) - :meth:`FrozenNDArray.searchsorted` has deprecated the ``v`` parameter in favor of ``value`` (:issue:`14645`) - :func:`DatetimeIndex.shift` now accepts ``periods`` argument instead of ``n`` for consistency with :func:`Index.shift` and :func:`Series.shift`. Using ``n`` throws a deprecation warning (:issue:`22458`) -- Addition or subtraction of integers with :class:`Timestamp`, :class:`Period`, :class:`DatetimeIndex`, :class:`TimedeltaIndex`, :class:`PeriodIndex` is deprecated, will be removed in a future version (:issue:`21939`) -- Addition or subtraction of integer-dtyped arrays with :class:`DatetimeIndex`, :class:`TimedeltaIndex`, :class:`PeriodIndex` is deprecated, will be removed in a future version (:issue:`21939`) + +.. _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`. + +This usage is now deprecated. Instead add or subtract integer multiples of +the object's ``freq`` attribute (:issue:`21939`) + +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') + + In [7]: tdi = pd.timedelta_range('1D', periods=2) + In [8]: tdi - np.array([2, 1]) + Out[8]: TimedeltaIndex(['-1 days', '1 days'], dtype='timedelta64[ns]', freq=None) + + In [9]: dti = pd.date_range('2001-01-01', periods=2, freq='7D') + In [10]: dti + pd.Index([1, 2]) + Out[10]: DatetimeIndex(['2001-01-08', '2001-01-22'], dtype='datetime64[ns]', freq=None) + +Current Behavior: + +.. ipython:: python + + In [3]: per = pd.Period('2016Q1') + In [4]: per + 3 * per.freq + 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 * ts.freq + Out[6]: Timestamp('1994-05-06 14:15:16', freq='H') + + In [7]: tdi = pd.timedelta_range('1D', periods=2) + In [8]: tdi - np.array([2 * tdi.freq, 1 * tdi.freq]) + Out[8]: TimedeltaIndex(['-1 days', '1 days'], dtype='timedelta64[ns]', freq=None) + + In [9]: dti = pd.date_range('2001-01-01', periods=2, freq='7D') + In [10]: dti + pd.Index([1 * dti.freq, 2 * dti.freq]) + Out[10]: DatetimeIndex(['2001-01-08', '2001-01-22'], dtype='datetime64[ns]', freq=None) .. _whatsnew_0240.prior_deprecations: From b469bef56064c30f38c6b7beec1d50ca8d82d58f Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Thu, 4 Oct 2018 08:34:20 -0700 Subject: [PATCH 09/24] just input in whatsnew executable block --- doc/source/whatsnew/v0.24.0.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index b7996b166b26b..dfbeb08487553 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -642,19 +642,15 @@ Current Behavior: In [3]: per = pd.Period('2016Q1') In [4]: per + 3 * per.freq - 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 * ts.freq - Out[6]: Timestamp('1994-05-06 14:15:16', freq='H') In [7]: tdi = pd.timedelta_range('1D', periods=2) In [8]: tdi - np.array([2 * tdi.freq, 1 * tdi.freq]) - Out[8]: TimedeltaIndex(['-1 days', '1 days'], dtype='timedelta64[ns]', freq=None) In [9]: dti = pd.date_range('2001-01-01', periods=2, freq='7D') In [10]: dti + pd.Index([1 * dti.freq, 2 * dti.freq]) - Out[10]: DatetimeIndex(['2001-01-08', '2001-01-22'], dtype='datetime64[ns]', freq=None) .. _whatsnew_0240.prior_deprecations: From 20d58faa48425e20d47dc439d34a7e7af0854486 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Thu, 4 Oct 2018 11:57:31 -0700 Subject: [PATCH 10/24] Catch warnings in tests --- pandas/core/indexes/period.py | 2 +- pandas/tests/tseries/offsets/test_offsets.py | 60 ++++++++++++++++---- pandas/tseries/offsets.py | 3 +- 3 files changed, 51 insertions(+), 14 deletions(-) diff --git a/pandas/core/indexes/period.py b/pandas/core/indexes/period.py index cc008694a8b84..c190fcce9eab9 100644 --- a/pandas/core/indexes/period.py +++ b/pandas/core/indexes/period.py @@ -497,7 +497,7 @@ def to_timestamp(self, freq=None, how='start'): return self.to_timestamp(how='start') + adjust else: adjust = Timedelta(1, 'ns') - return (self + 1).to_timestamp(how='start') - adjust + return (self + self.freq).to_timestamp(how='start') - adjust if freq is None: base, mult = _gfc(self.freq) diff --git a/pandas/tests/tseries/offsets/test_offsets.py b/pandas/tests/tseries/offsets/test_offsets.py index b8fabbf52159d..330008e75e6a7 100644 --- a/pandas/tests/tseries/offsets/test_offsets.py +++ b/pandas/tests/tseries/offsets/test_offsets.py @@ -9,6 +9,7 @@ import numpy as np from pandas.compat.numpy import np_datetime64_compat +from pandas.errors import PerformanceWarning from pandas.core.series import Series from pandas._libs.tslibs import conversion @@ -2405,7 +2406,11 @@ def test_offset_whole_year(self): # ensure .apply_index works as expected s = DatetimeIndex(dates[:-1]) - result = SemiMonthEnd().apply_index(s) + with tm.assert_produces_warning(PerformanceWarning): + # GH#22535 check for PerformanceWarning and that we _dont_ + # get FutureWarning + result = SemiMonthEnd().apply_index(s) + exp = DatetimeIndex(dates[1:]) tm.assert_index_equal(result, exp) @@ -2501,7 +2506,12 @@ def test_offset(self, case): def test_apply_index(self, case): offset, cases = case s = DatetimeIndex(cases.keys()) - result = offset.apply_index(s) + warning = None if isinstance(offset, Tick) else PerformanceWarning + with tm.assert_produces_warning(warning): + # GH#22535 check for PerformanceWarning and that we _dont_ + # get FutureWarning + result = offset.apply_index(s) + exp = DatetimeIndex(cases.values()) tm.assert_index_equal(result, exp) @@ -2523,8 +2533,12 @@ def test_vectorized_offset_addition(self, klass, assert_func): s = klass([Timestamp('2000-01-15 00:15:00', tz='US/Central'), Timestamp('2000-02-15', tz='US/Central')], name='a') - result = s + SemiMonthEnd() - result2 = SemiMonthEnd() + s + with tm.assert_produces_warning(PerformanceWarning): + # GH#22535 check for PerformanceWarning and that we _dont_ + # get FutureWarning + result = s + SemiMonthEnd() + result2 = SemiMonthEnd() + s + exp = klass([Timestamp('2000-01-31 00:15:00', tz='US/Central'), Timestamp('2000-02-29', tz='US/Central')], name='a') assert_func(result, exp) @@ -2532,8 +2546,13 @@ def test_vectorized_offset_addition(self, klass, assert_func): s = klass([Timestamp('2000-01-01 00:15:00', tz='US/Central'), Timestamp('2000-02-01', tz='US/Central')], name='a') - result = s + SemiMonthEnd() - result2 = SemiMonthEnd() + s + + with tm.assert_produces_warning(PerformanceWarning): + # GH#22535 check for PerformanceWarning and that we _dont_ + # get FutureWarning + result = s + SemiMonthEnd() + result2 = SemiMonthEnd() + s + exp = klass([Timestamp('2000-01-15 00:15:00', tz='US/Central'), Timestamp('2000-02-15', tz='US/Central')], name='a') assert_func(result, exp) @@ -2577,7 +2596,11 @@ def test_offset_whole_year(self): # ensure .apply_index works as expected s = DatetimeIndex(dates[:-1]) - result = SemiMonthBegin().apply_index(s) + with tm.assert_produces_warning(PerformanceWarning): + # GH#22535 check for PerformanceWarning and that we _dont_ + # get FutureWarning + result = SemiMonthBegin().apply_index(s) + exp = DatetimeIndex(dates[1:]) tm.assert_index_equal(result, exp) @@ -2677,7 +2700,12 @@ def test_offset(self, case): def test_apply_index(self, case): offset, cases = case s = DatetimeIndex(cases.keys()) - result = offset.apply_index(s) + + with tm.assert_produces_warning(PerformanceWarning): + # GH#22535 check for PerformanceWarning and that we _dont_ + # get FutureWarning + result = offset.apply_index(s) + exp = DatetimeIndex(cases.values()) tm.assert_index_equal(result, exp) @@ -2698,8 +2726,12 @@ def test_onOffset(self, case): def test_vectorized_offset_addition(self, klass, assert_func): s = klass([Timestamp('2000-01-15 00:15:00', tz='US/Central'), Timestamp('2000-02-15', tz='US/Central')], name='a') - result = s + SemiMonthBegin() - result2 = SemiMonthBegin() + s + with tm.assert_produces_warning(PerformanceWarning): + # GH#22535 check that we don't get a FutureWarning from adding + # an integer array to PeriodIndex + result = s + SemiMonthBegin() + result2 = SemiMonthBegin() + s + exp = klass([Timestamp('2000-02-01 00:15:00', tz='US/Central'), Timestamp('2000-03-01', tz='US/Central')], name='a') assert_func(result, exp) @@ -2707,8 +2739,12 @@ def test_vectorized_offset_addition(self, klass, assert_func): s = klass([Timestamp('2000-01-01 00:15:00', tz='US/Central'), Timestamp('2000-02-01', tz='US/Central')], name='a') - result = s + SemiMonthBegin() - result2 = SemiMonthBegin() + s + with tm.assert_produces_warning(PerformanceWarning): + # GH#22535 check that we don't get a FutureWarning from adding + # an integer array to PeriodIndex, but do get a PerformanceWarning + result = s + SemiMonthBegin() + result2 = SemiMonthBegin() + s + exp = klass([Timestamp('2000-01-15 00:15:00', tz='US/Central'), Timestamp('2000-02-15', tz='US/Central')], name='a') assert_func(result, exp) diff --git a/pandas/tseries/offsets.py b/pandas/tseries/offsets.py index 0a9931c46bbd5..0a28184080e0f 100644 --- a/pandas/tseries/offsets.py +++ b/pandas/tseries/offsets.py @@ -1109,7 +1109,8 @@ def apply_index(self, i): time = i.to_perioddelta('D') # apply the correct number of months - i = (i.to_period('M') + (roll // 2)).to_timestamp() + imonths = i.to_period('M') + i = (imonths + (roll // 2) * imonths.freq).to_timestamp() # apply the correct day i = self._apply_index_days(i, roll) From 984bc7e2b82c67a17e2f907177b22ce5e866d474 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Thu, 4 Oct 2018 15:05:28 -0700 Subject: [PATCH 11/24] TST: catch warnings for strict test run --- pandas/core/indexes/period.py | 7 ++- pandas/tests/frame/test_timeseries.py | 4 +- pandas/tests/indexes/datetimelike.py | 7 ++- .../indexes/datetimes/test_arithmetic.py | 10 +++- pandas/tests/indexes/period/test_period.py | 6 ++- .../indexes/timedeltas/test_arithmetic.py | 46 +++++++++++++------ .../tests/scalar/timestamp/test_arithmetic.py | 14 ++++-- pandas/tests/tseries/offsets/test_offsets.py | 44 +++++++++--------- pandas/tseries/offsets.py | 33 ++++++++++--- 9 files changed, 112 insertions(+), 59 deletions(-) diff --git a/pandas/core/indexes/period.py b/pandas/core/indexes/period.py index c190fcce9eab9..e2ffc88b643c0 100644 --- a/pandas/core/indexes/period.py +++ b/pandas/core/indexes/period.py @@ -497,7 +497,12 @@ def to_timestamp(self, freq=None, how='start'): return self.to_timestamp(how='start') + adjust else: adjust = Timedelta(1, 'ns') - return (self + self.freq).to_timestamp(how='start') - adjust + with warnings.catch_warnings(): + # ignore the fact that integer addition is deprecated + # until GH#22998 is fixed + warnings.simplefilter("ignore", FutureWarning) + shifted = self + 1 + return shifted.to_timestamp(how='start') - adjust if freq is None: base, mult = _gfc(self.freq) diff --git a/pandas/tests/frame/test_timeseries.py b/pandas/tests/frame/test_timeseries.py index b1d9d362d1402..7fb98cd1e3e6a 100644 --- a/pandas/tests/frame/test_timeseries.py +++ b/pandas/tests/frame/test_timeseries.py @@ -423,8 +423,8 @@ def test_truncate(self): assert_frame_equal(truncated, expected) pytest.raises(ValueError, ts.truncate, - before=ts.index[-1] - 1, - after=ts.index[0] + 1) + before=ts.index[-1] - ts.index.freq, + after=ts.index[0] + ts.index.freq) def test_truncate_copy(self): index = self.tsframe.index diff --git a/pandas/tests/indexes/datetimelike.py b/pandas/tests/indexes/datetimelike.py index e32e18ea0ec4a..9f1f459577163 100644 --- a/pandas/tests/indexes/datetimelike.py +++ b/pandas/tests/indexes/datetimelike.py @@ -47,9 +47,8 @@ def test_view(self, indices): tm.assert_index_equal(result, i_view) def test_map_callable(self): - - expected = self.index + 1 - result = self.index.map(lambda x: x + 1) + expected = self.index + self.index.freq + result = self.index.map(lambda x: x + x.freq) tm.assert_index_equal(result, expected) # map to NaT @@ -63,7 +62,7 @@ def test_map_callable(self): lambda values, index: {i: e for e, i in zip(values, index)}, lambda values, index: pd.Series(values, index)]) def test_map_dictlike(self, mapper): - expected = self.index + 1 + expected = self.index + self.index.freq # don't compare the freqs if isinstance(expected, pd.DatetimeIndex): diff --git a/pandas/tests/indexes/datetimes/test_arithmetic.py b/pandas/tests/indexes/datetimes/test_arithmetic.py index 4feed589f5961..c6dec8c9f9e0b 100644 --- a/pandas/tests/indexes/datetimes/test_arithmetic.py +++ b/pandas/tests/indexes/datetimes/test_arithmetic.py @@ -57,11 +57,17 @@ def test_dti_shift_freqs(self): def test_dti_shift_int(self): rng = date_range('1/1/2000', periods=20) - result = rng + 5 + with tm.assert_produces_warning(FutureWarning): + # GH#22535 + result = rng + 5 + expected = rng.shift(5) tm.assert_index_equal(result, expected) - result = rng - 5 + with tm.assert_produces_warning(FutureWarning): + # GH#22535 + result = rng - 5 + expected = rng.shift(-5) tm.assert_index_equal(result, expected) diff --git a/pandas/tests/indexes/period/test_period.py b/pandas/tests/indexes/period/test_period.py index 405edba83dc7a..80e785a91befe 100644 --- a/pandas/tests/indexes/period/test_period.py +++ b/pandas/tests/indexes/period/test_period.py @@ -338,8 +338,10 @@ def test_is_(self): assert not index.is_(index[:]) assert not index.is_(index.asfreq('M')) assert not index.is_(index.asfreq('A')) - assert not index.is_(index - 2) - assert not index.is_(index - 0) + with tm.assert_produces_warning(FutureWarning): + # GH#22535 + 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/indexes/timedeltas/test_arithmetic.py b/pandas/tests/indexes/timedeltas/test_arithmetic.py index e425937fedf4b..6684f00dfcf68 100644 --- a/pandas/tests/indexes/timedeltas/test_arithmetic.py +++ b/pandas/tests/indexes/timedeltas/test_arithmetic.py @@ -129,26 +129,34 @@ def test_ufunc_coercions(self): def test_tdi_add_int(self, one): # Variants of `one` for #19012 rng = timedelta_range('1 days 09:00:00', freq='H', periods=10) - result = rng + one + with tm.assert_produces_warning(FutureWarning): + # GH#22535 + result = rng + one expected = timedelta_range('1 days 10:00:00', freq='H', periods=10) tm.assert_index_equal(result, expected) def test_tdi_iadd_int(self, one): rng = timedelta_range('1 days 09:00:00', freq='H', periods=10) expected = timedelta_range('1 days 10:00:00', freq='H', periods=10) - rng += one + with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): + # GH#22535 + rng += one tm.assert_index_equal(rng, expected) def test_tdi_sub_int(self, one): rng = timedelta_range('1 days 09:00:00', freq='H', periods=10) - result = rng - one + with tm.assert_produces_warning(FutureWarning): + # GH#22535 + result = rng - one expected = timedelta_range('1 days 08:00:00', freq='H', periods=10) tm.assert_index_equal(result, expected) def test_tdi_isub_int(self, one): rng = timedelta_range('1 days 09:00:00', freq='H', periods=10) expected = timedelta_range('1 days 08:00:00', freq='H', periods=10) - rng -= one + with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): + # GH#22535 + rng -= one tm.assert_index_equal(rng, expected) # ------------------------------------------------------------- @@ -160,10 +168,15 @@ def test_tdi_add_integer_array(self, box): rng = timedelta_range('1 days 09:00:00', freq='H', periods=3) other = box([4, 3, 2]) expected = TimedeltaIndex(['1 day 13:00:00'] * 3) - result = rng + other - tm.assert_index_equal(result, expected) - result = other + rng - tm.assert_index_equal(result, expected) + with tm.assert_produces_warning(FutureWarning): + # GH#22535 + result = rng + other + tm.assert_index_equal(result, expected) + + with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): + # GH#22535 + result = other + rng + tm.assert_index_equal(result, expected) @pytest.mark.parametrize('box', [np.array, pd.Index]) def test_tdi_sub_integer_array(self, box): @@ -171,10 +184,15 @@ def test_tdi_sub_integer_array(self, box): rng = timedelta_range('9H', freq='H', periods=3) other = box([4, 3, 2]) expected = TimedeltaIndex(['5H', '7H', '9H']) - result = rng - other - tm.assert_index_equal(result, expected) - result = other - rng - tm.assert_index_equal(result, -expected) + with tm.assert_produces_warning(FutureWarning): + # GH#22535 + result = rng - other + tm.assert_index_equal(result, expected) + + with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): + # GH#22535 + result = other - rng + tm.assert_index_equal(result, -expected) @pytest.mark.parametrize('box', [np.array, pd.Index]) def test_tdi_addsub_integer_array_no_freq(self, box): @@ -521,12 +539,12 @@ def test_timedelta_ops_with_missing_values(self): def test_tdi_ops_attributes(self): rng = timedelta_range('2 days', periods=5, freq='2D', name='x') - result = rng + 1 + result = rng + 1 * rng.freq exp = timedelta_range('4 days', periods=5, freq='2D', name='x') tm.assert_index_equal(result, exp) assert result.freq == '2D' - result = rng - 2 + result = rng - 2 * rng.freq exp = timedelta_range('-2 days', periods=5, freq='2D', name='x') tm.assert_index_equal(result, exp) assert result.freq == '2D' diff --git a/pandas/tests/scalar/timestamp/test_arithmetic.py b/pandas/tests/scalar/timestamp/test_arithmetic.py index 8f4809c93e28b..0f8ddc53734f6 100644 --- a/pandas/tests/scalar/timestamp/test_arithmetic.py +++ b/pandas/tests/scalar/timestamp/test_arithmetic.py @@ -4,6 +4,7 @@ import pytest import numpy as np +import pandas.util.testing as tm from pandas.compat import long from pandas.tseries import offsets from pandas import Timestamp, Timedelta @@ -46,8 +47,10 @@ def test_addition_subtraction_types(self): # addition/subtraction of integers ts = Timestamp(dt, freq='D') - assert type(ts + 1) == Timestamp - assert type(ts - 1) == Timestamp + with tm.assert_produces_warning(FutureWarning): + # GH#22535 add/sub with integers is deprecated + assert type(ts + 1) == Timestamp + assert type(ts - 1) == Timestamp # Timestamp + datetime not supported, though subtraction is supported # and yields timedelta more tests in tseries/base/tests/test_base.py @@ -66,8 +69,11 @@ def test_addition_subtraction_preserve_frequency(self): td = timedelta(days=1) original_freq = ts.freq - assert (ts + 1).freq == original_freq - assert (ts - 1).freq == original_freq + with tm.assert_produces_warning(FutureWarning): + # GH#22535 add/sub with integers is deprecated + assert (ts + 1).freq == original_freq + assert (ts - 1).freq == original_freq + assert (ts + td).freq == original_freq assert (ts - td).freq == original_freq diff --git a/pandas/tests/tseries/offsets/test_offsets.py b/pandas/tests/tseries/offsets/test_offsets.py index 330008e75e6a7..14564413f7a47 100644 --- a/pandas/tests/tseries/offsets/test_offsets.py +++ b/pandas/tests/tseries/offsets/test_offsets.py @@ -9,7 +9,6 @@ import numpy as np from pandas.compat.numpy import np_datetime64_compat -from pandas.errors import PerformanceWarning from pandas.core.series import Series from pandas._libs.tslibs import conversion @@ -2406,9 +2405,9 @@ def test_offset_whole_year(self): # ensure .apply_index works as expected s = DatetimeIndex(dates[:-1]) - with tm.assert_produces_warning(PerformanceWarning): - # GH#22535 check for PerformanceWarning and that we _dont_ - # get FutureWarning + with tm.assert_produces_warning(None): + # GH#22535 check that we don't get a FutureWarning from adding + # an integer array to PeriodIndex result = SemiMonthEnd().apply_index(s) exp = DatetimeIndex(dates[1:]) @@ -2506,10 +2505,9 @@ def test_offset(self, case): def test_apply_index(self, case): offset, cases = case s = DatetimeIndex(cases.keys()) - warning = None if isinstance(offset, Tick) else PerformanceWarning - with tm.assert_produces_warning(warning): - # GH#22535 check for PerformanceWarning and that we _dont_ - # get FutureWarning + with tm.assert_produces_warning(None): + # GH#22535 check that we don't get a FutureWarning from adding + # an integer array to PeriodIndex result = offset.apply_index(s) exp = DatetimeIndex(cases.values()) @@ -2533,9 +2531,9 @@ def test_vectorized_offset_addition(self, klass, assert_func): s = klass([Timestamp('2000-01-15 00:15:00', tz='US/Central'), Timestamp('2000-02-15', tz='US/Central')], name='a') - with tm.assert_produces_warning(PerformanceWarning): - # GH#22535 check for PerformanceWarning and that we _dont_ - # get FutureWarning + with tm.assert_produces_warning(None): + # GH#22535 check that we don't get a FutureWarning from adding + # an integer array to PeriodIndex result = s + SemiMonthEnd() result2 = SemiMonthEnd() + s @@ -2547,9 +2545,9 @@ def test_vectorized_offset_addition(self, klass, assert_func): s = klass([Timestamp('2000-01-01 00:15:00', tz='US/Central'), Timestamp('2000-02-01', tz='US/Central')], name='a') - with tm.assert_produces_warning(PerformanceWarning): - # GH#22535 check for PerformanceWarning and that we _dont_ - # get FutureWarning + with tm.assert_produces_warning(None): + # GH#22535 check that we don't get a FutureWarning from adding + # an integer array to PeriodIndex result = s + SemiMonthEnd() result2 = SemiMonthEnd() + s @@ -2596,9 +2594,9 @@ def test_offset_whole_year(self): # ensure .apply_index works as expected s = DatetimeIndex(dates[:-1]) - with tm.assert_produces_warning(PerformanceWarning): - # GH#22535 check for PerformanceWarning and that we _dont_ - # get FutureWarning + with tm.assert_produces_warning(None): + # GH#22535 check that we don't get a FutureWarning from adding + # an integer array to PeriodIndex result = SemiMonthBegin().apply_index(s) exp = DatetimeIndex(dates[1:]) @@ -2701,9 +2699,9 @@ def test_apply_index(self, case): offset, cases = case s = DatetimeIndex(cases.keys()) - with tm.assert_produces_warning(PerformanceWarning): - # GH#22535 check for PerformanceWarning and that we _dont_ - # get FutureWarning + with tm.assert_produces_warning(None): + # GH#22535 check that we don't get a FutureWarning from adding + # an integer array to PeriodIndex result = offset.apply_index(s) exp = DatetimeIndex(cases.values()) @@ -2726,7 +2724,7 @@ def test_onOffset(self, case): def test_vectorized_offset_addition(self, klass, assert_func): s = klass([Timestamp('2000-01-15 00:15:00', tz='US/Central'), Timestamp('2000-02-15', tz='US/Central')], name='a') - with tm.assert_produces_warning(PerformanceWarning): + with tm.assert_produces_warning(None): # GH#22535 check that we don't get a FutureWarning from adding # an integer array to PeriodIndex result = s + SemiMonthBegin() @@ -2739,9 +2737,9 @@ def test_vectorized_offset_addition(self, klass, assert_func): s = klass([Timestamp('2000-01-01 00:15:00', tz='US/Central'), Timestamp('2000-02-01', tz='US/Central')], name='a') - with tm.assert_produces_warning(PerformanceWarning): + with tm.assert_produces_warning(None): # GH#22535 check that we don't get a FutureWarning from adding - # an integer array to PeriodIndex, but do get a PerformanceWarning + # an integer array to PeriodIndex result = s + SemiMonthBegin() result2 = SemiMonthBegin() + s diff --git a/pandas/tseries/offsets.py b/pandas/tseries/offsets.py index 0a28184080e0f..ce8a4e065bd26 100644 --- a/pandas/tseries/offsets.py +++ b/pandas/tseries/offsets.py @@ -2,6 +2,7 @@ from datetime import date, datetime, timedelta import functools import operator +import warnings from pandas.compat import range from pandas import compat @@ -288,8 +289,12 @@ def apply_index(self, i): weeks = (kwds.get('weeks', 0)) * self.n if weeks: - i = (i.to_period('W') + weeks).to_timestamp() + \ - i.to_perioddelta('W') + with warnings.catch_warnings(record=True): + # integer-array addition on PeriodIndex is deprecated, + # but still used internally for performance + warnings.simplefilter("ignore", FutureWarning) + i = (i.to_period('W') + weeks).to_timestamp() + \ + i.to_perioddelta('W') timedelta_kwds = {k: v for k, v in kwds.items() if k in ['days', 'hours', 'minutes', @@ -542,7 +547,13 @@ def apply_index(self, i): else: roll = self.n - return (i.to_period('B') + roll).to_timestamp() + time + with warnings.catch_warnings(record=True): + # integer-array addition on PeriodIndex is deprecated, + # but still used internally for performance + warnings.simplefilter("ignore", FutureWarning) + result = (i.to_period('B') + roll).to_timestamp() + time + + return result def onOffset(self, dt): if self.normalize and not _is_normalized(dt): @@ -1109,8 +1120,11 @@ def apply_index(self, i): time = i.to_perioddelta('D') # apply the correct number of months - imonths = i.to_period('M') - i = (imonths + (roll // 2) * imonths.freq).to_timestamp() + with warnings.catch_warnings(record=True): + # integer-array addition on PeriodIndex is deprecated, + # but still used internally for performance + warnings.simplefilter("ignore", FutureWarning) + i = (i.to_period('M') + (roll // 2)).to_timestamp() # apply the correct day i = self._apply_index_days(i, roll) @@ -1291,7 +1305,7 @@ def apply(self, other): @apply_index_wraps def apply_index(self, i): if self.weekday is None: - return ((i.to_period('W') + self.n).to_timestamp() + + return ((i.to_period('W') + Day(self.n)).to_timestamp() + i.to_perioddelta('W')) else: return self._end_apply_index(i) @@ -1320,7 +1334,12 @@ def _end_apply_index(self, dtindex): else: roll = self.n - base = (base_period + roll).to_timestamp(how='end') + with warnings.catch_warnings(record=True): + # integer-array addition on PeriodIndex is deprecated, + # but still used internally for performance + warnings.simplefilter("ignore", FutureWarning) + base = (base_period + roll).to_timestamp(how='end') + return base + off + Timedelta(1, 'ns') - Timedelta(1, 'D') def onOffset(self, dt): From 7e7a348ecd7de00338340ac56d5ec848ae9a76c7 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Fri, 5 Oct 2018 09:36:10 -0700 Subject: [PATCH 12/24] Catch warnings --- pandas/tests/arithmetic/test_datetime64.py | 10 ++++++++-- pandas/tseries/offsets.py | 8 ++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/pandas/tests/arithmetic/test_datetime64.py b/pandas/tests/arithmetic/test_datetime64.py index 0b663a257ed6d..d098769bdd524 100644 --- a/pandas/tests/arithmetic/test_datetime64.py +++ b/pandas/tests/arithmetic/test_datetime64.py @@ -1054,10 +1054,13 @@ def test_dti_add_intarray_tick(self, box, freq): # GH#19959 dti = pd.date_range('2016-01-01', periods=2, freq=freq) other = box([4, -1]) - expected = DatetimeIndex([dti[n] + other[n] for n in range(len(dti))]) + with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): + expected = DatetimeIndex([dti[n] + other[n] + for n in range(len(dti))]) result = dti + other tm.assert_index_equal(result, expected) + with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): result = other + dti tm.assert_index_equal(result, expected) @@ -1068,7 +1071,10 @@ def test_dti_add_intarray_non_tick(self, box, freq): # GH#19959 dti = pd.date_range('2016-01-01', periods=2, freq=freq) other = box([4, -1]) - expected = DatetimeIndex([dti[n] + other[n] for n in range(len(dti))]) + + with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): + expected = DatetimeIndex([dti[n] + other[n] + for n in range(len(dti))]) # tm.assert_produces_warning does not handle cases where we expect # two warnings, in this case PerformanceWarning and FutureWarning. diff --git a/pandas/tseries/offsets.py b/pandas/tseries/offsets.py index ce8a4e065bd26..f07af5056a336 100644 --- a/pandas/tseries/offsets.py +++ b/pandas/tseries/offsets.py @@ -1305,8 +1305,12 @@ def apply(self, other): @apply_index_wraps def apply_index(self, i): if self.weekday is None: - return ((i.to_period('W') + Day(self.n)).to_timestamp() + - i.to_perioddelta('W')) + with warnings.catch_warnings(record=True): + # integer addition on PeriodIndex is deprecated, + # but still used internally for performance + warnings.simplefilter("ignore", FutureWarning) + shifted = i.to_period('W') + self.n + return shifted.to_timestamp() + i.to_perioddelta('W') else: return self._end_apply_index(i) From 17f6be0fb08eaead26606f8c588e5c5010d4d440 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Fri, 5 Oct 2018 11:30:02 -0700 Subject: [PATCH 13/24] Avoid warnings --- pandas/core/resample.py | 9 +++++---- pandas/tests/test_resample.py | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/pandas/core/resample.py b/pandas/core/resample.py index 9862625360728..36476a8ecb657 100644 --- a/pandas/core/resample.py +++ b/pandas/core/resample.py @@ -1443,17 +1443,18 @@ def _get_time_period_bins(self, ax): raise TypeError('axis must be a DatetimeIndex, but got ' 'an instance of %r' % type(ax).__name__) + freq = self.freq + if not len(ax): - binner = labels = PeriodIndex( - data=[], freq=self.freq, name=ax.name) + binner = labels = PeriodIndex(data=[], freq=freq, name=ax.name) return binner, [], labels labels = binner = PeriodIndex(start=ax[0], end=ax[-1], - freq=self.freq, + freq=freq, name=ax.name) - end_stamps = (labels + 1).asfreq(self.freq, 's').to_timestamp() + end_stamps = (labels + freq).asfreq(freq, 's').to_timestamp() if ax.tzinfo: end_stamps = end_stamps.tz_localize(ax.tzinfo) bins = ax.searchsorted(end_stamps, side='left') diff --git a/pandas/tests/test_resample.py b/pandas/tests/test_resample.py index 5cd31e08e0a9b..fe9c061ec7a35 100644 --- a/pandas/tests/test_resample.py +++ b/pandas/tests/test_resample.py @@ -2245,7 +2245,7 @@ def test_asfreq(self, series_and_frame, freq, kind): expected = obj.to_timestamp().resample(freq).asfreq() else: start = obj.index[0].to_timestamp(how='start') - end = (obj.index[-1] + 1).to_timestamp(how='start') + end = (obj.index[-1] + obj.index.freq).to_timestamp(how='start') new_index = date_range(start=start, end=end, freq=freq, closed='left') expected = obj.to_timestamp().reindex(new_index).to_period(freq) From 29cca467297e95156d33e6615058e988467230ea Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Fri, 5 Oct 2018 12:57:26 -0700 Subject: [PATCH 14/24] catch more warnings --- pandas/tests/arithmetic/test_period.py | 12 +++++++++--- pandas/tests/indexing/test_partial.py | 3 ++- pandas/tests/test_resample.py | 5 +++-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/pandas/tests/arithmetic/test_period.py b/pandas/tests/arithmetic/test_period.py index 6a9e5af495932..650f65744607f 100644 --- a/pandas/tests/arithmetic/test_period.py +++ b/pandas/tests/arithmetic/test_period.py @@ -523,10 +523,14 @@ 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) - result = rng + one + with tm.assert_produces_warning(FutureWarning, check_stacklevel=False, + clear=[pd.core.arrays.datetimelike]): + result = rng + one expected = pd.period_range('2000-01-01 10:00', freq='H', periods=10) tm.assert_index_equal(result, expected) - rng += one + with tm.assert_produces_warning(FutureWarning, check_stacklevel=False, + clear=[pd.core.arrays.datetimelike]): + rng += one tm.assert_index_equal(rng, expected) def test_pi_sub_isub_int(self, one): @@ -600,7 +604,9 @@ def test_pi_sub_intarray(self, box): tm.assert_index_equal(result, expected) with pytest.raises(TypeError): - other - pi + with tm.assert_produces_warning(FutureWarning, + check_stacklevel=False): + other - pi # --------------------------------------------------------------- # Timedelta-like (timedelta, timedelta64, Timedelta, Tick) diff --git a/pandas/tests/indexing/test_partial.py b/pandas/tests/indexing/test_partial.py index 5910f462cb3df..6699ece46f91e 100644 --- a/pandas/tests/indexing/test_partial.py +++ b/pandas/tests/indexing/test_partial.py @@ -159,7 +159,8 @@ def f(): columns=['A', 'B', 'C', 'D']) expected = pd.concat([df_orig, - DataFrame({'A': 7}, index=[dates[-1] + 1])], + DataFrame({'A': 7}, + index=[dates[-1] + dates.freq])], sort=True) df = df_orig.copy() df.loc[dates[-1] + 1, 'A'] = 7 diff --git a/pandas/tests/test_resample.py b/pandas/tests/test_resample.py index fe9c061ec7a35..69a0613c95475 100644 --- a/pandas/tests/test_resample.py +++ b/pandas/tests/test_resample.py @@ -2467,7 +2467,8 @@ def test_with_local_timezone_pytz(self): # Create the expected series # Index is moved back a day with the timezone conversion from UTC to # Pacific - expected_index = (pd.period_range(start=start, end=end, freq='D') - 1) + expected_index = (pd.period_range(start=start, end=end, freq='D') - + offsets.Day()) expected = Series(1, index=expected_index) assert_series_equal(result, expected) @@ -2503,7 +2504,7 @@ def test_with_local_timezone_dateutil(self): # Index is moved back a day with the timezone conversion from UTC to # Pacific expected_index = (pd.period_range(start=start, end=end, freq='D', - name='idx') - 1) + name='idx') - offsets.Day()) expected = Series(1, index=expected_index) assert_series_equal(result, expected) From b0a222a8edac7a02adda2410e10ea7fdfad4218c Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Fri, 5 Oct 2018 15:22:38 -0700 Subject: [PATCH 15/24] avoid warning --- pandas/tests/indexing/test_partial.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pandas/tests/indexing/test_partial.py b/pandas/tests/indexing/test_partial.py index 6699ece46f91e..96b864e17f065 100644 --- a/pandas/tests/indexing/test_partial.py +++ b/pandas/tests/indexing/test_partial.py @@ -163,20 +163,20 @@ def f(): index=[dates[-1] + dates.freq])], sort=True) df = df_orig.copy() - df.loc[dates[-1] + 1, 'A'] = 7 + df.loc[dates[-1] + dates.freq, 'A'] = 7 tm.assert_frame_equal(df, expected) df = df_orig.copy() - df.at[dates[-1] + 1, 'A'] = 7 + df.at[dates[-1] + dates.freq, 'A'] = 7 tm.assert_frame_equal(df, expected) - exp_other = DataFrame({0: 7}, index=[dates[-1] + 1]) + exp_other = DataFrame({0: 7}, index=[dates[-1] + dates.freq]) expected = pd.concat([df_orig, exp_other], axis=1) df = df_orig.copy() - df.loc[dates[-1] + 1, 0] = 7 + df.loc[dates[-1] + dates.freq, 0] = 7 tm.assert_frame_equal(df, expected) df = df_orig.copy() - df.at[dates[-1] + 1, 0] = 7 + df.at[dates[-1] + dates.freq, 0] = 7 tm.assert_frame_equal(df, expected) def test_partial_setting_mixed_dtype(self): From 55dc265f3b80c2d047967b1a7afc3fbfe20d83d3 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Mon, 15 Oct 2018 12:17:23 -0700 Subject: [PATCH 16/24] Avoid need to catch deprecation warnings --- pandas/core/arrays/datetimelike.py | 6 ++- pandas/tseries/offsets.py | 60 +++++++++++++++--------------- 2 files changed, 35 insertions(+), 31 deletions(-) diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index 7667683a39432..3bfbad0cdba85 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -458,7 +458,7 @@ def _sub_period_array(self, other): new_values[mask] = NaT return new_values - def _addsub_int_array(self, other, op): + def _addsub_int_array(self, other, op, suppress=False): """ Add or subtract array-like of integers equivalent to applying `_time_shift` pointwise. @@ -468,6 +468,8 @@ def _addsub_int_array(self, other, op): other : Index, ExtensionArray, np.ndarray integer-dtype op : {operator.add, operator.sub} + suppress : bool, default False + Whether to suppress a deprecation warning Returns ------- @@ -475,7 +477,7 @@ def _addsub_int_array(self, other, op): """ assert op in [operator.add, operator.sub] - if self.freq is not None: + if self.freq is not None and not suppress: # case with freq of None will raise # we need to use a different stacklevel for Index vs Array lvl = 3 + 1 * isinstance(self, ABCIndexClass) diff --git a/pandas/tseries/offsets.py b/pandas/tseries/offsets.py index 72ec0ff3f13af..4257fc2296d04 100644 --- a/pandas/tseries/offsets.py +++ b/pandas/tseries/offsets.py @@ -2,7 +2,6 @@ from datetime import date, datetime, timedelta import functools import operator -import warnings from pandas.compat import range from pandas import compat @@ -289,12 +288,11 @@ def apply_index(self, i): weeks = (kwds.get('weeks', 0)) * self.n if weeks: - with warnings.catch_warnings(record=True): - # integer-array addition on PeriodIndex is deprecated, - # but still used internally for performance - warnings.simplefilter("ignore", FutureWarning) - i = (i.to_period('W') + weeks).to_timestamp() + \ - i.to_perioddelta('W') + # integer addition on PeriodIndex is deprecated, + # so we directly use _time_shift instead + asper = i.to_period('W') + shifted = asper._time_shift(weeks) + i = shifted.to_timestamp() + i.to_perioddelta('W') timedelta_kwds = {k: v for k, v in kwds.items() if k in ['days', 'hours', 'minutes', @@ -543,15 +541,17 @@ def apply_index(self, i): # reduce n where it does when rolling forward shifted = (i.to_perioddelta('B') - time).asi8 != 0 if self.n > 0: + # Integer-array addition is deprecated, so we use + # _time_shift directly roll = np.where(shifted, self.n - 1, self.n) + asper = i.to_period('B') + shifted = asper._addsub_int_array(roll, operator.add, + suppress=True) + result = shifted.to_timestamp() + time else: + # Integer addition is deprecated, so we use _time_shift directly roll = self.n - - with warnings.catch_warnings(record=True): - # integer-array addition on PeriodIndex is deprecated, - # but still used internally for performance - warnings.simplefilter("ignore", FutureWarning) - result = (i.to_period('B') + roll).to_timestamp() + time + result = (i.to_period('B')._time_shift(roll)).to_timestamp() + time return result @@ -1118,11 +1118,13 @@ def apply_index(self, i): time = i.to_perioddelta('D') # apply the correct number of months - with warnings.catch_warnings(record=True): - # integer-array addition on PeriodIndex is deprecated, - # but still used internally for performance - warnings.simplefilter("ignore", FutureWarning) - i = (i.to_period('M') + (roll // 2)).to_timestamp() + + # integer-array addition on PeriodIndex is deprecated, + # so we use _addsub_int_array directly + asper = i.to_period('M') + shifted = asper._addsub_int_array(roll // 2, operator.add, + suppress=True) + i = shifted.to_timestamp() # apply the correct day i = self._apply_index_days(i, roll) @@ -1303,11 +1305,9 @@ def apply(self, other): @apply_index_wraps def apply_index(self, i): if self.weekday is None: - with warnings.catch_warnings(record=True): - # integer addition on PeriodIndex is deprecated, - # but still used internally for performance - warnings.simplefilter("ignore", FutureWarning) - shifted = i.to_period('W') + self.n + # integer addition on PeriodIndex is deprecated, + # so we use _time_shift directly + shifted = i.to_period('W')._time_shift(self.n) return shifted.to_timestamp() + i.to_perioddelta('W') else: return self._end_apply_index(i) @@ -1333,14 +1333,16 @@ def _end_apply_index(self, dtindex): normed = dtindex - off + Timedelta(1, 'D') - Timedelta(1, 'ns') roll = np.where(base_period.to_timestamp(how='end') == normed, self.n, self.n - 1) + # integer-array addition on PeriodIndex is deprecated, + # so we use _addsub_int_array directly + shifted = base_period._addsub_int_array(roll, operator.add, + suppress=True) + base = shifted.to_timestamp(how='end') else: + # integer addition on PeriodIndex is deprecated, + # so we use _time_shift directly roll = self.n - - with warnings.catch_warnings(record=True): - # integer-array addition on PeriodIndex is deprecated, - # but still used internally for performance - warnings.simplefilter("ignore", FutureWarning) - base = (base_period + roll).to_timestamp(how='end') + base = base_period._time_shift(roll).to_timestamp(how='end') return base + off + Timedelta(1, 'ns') - Timedelta(1, 'D') From 9443df9db88da784cc6418c07064841ecd310da2 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Mon, 15 Oct 2018 13:42:33 -0700 Subject: [PATCH 17/24] stop using deprecated usage --- pandas/core/arrays/period.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/arrays/period.py b/pandas/core/arrays/period.py index 8624ddd8965e8..9c19c2d963d51 100644 --- a/pandas/core/arrays/period.py +++ b/pandas/core/arrays/period.py @@ -314,7 +314,7 @@ def to_timestamp(self, freq=None, how='start'): return self.to_timestamp(how='start') + adjust else: adjust = Timedelta(1, 'ns') - return (self + 1).to_timestamp(how='start') - adjust + return (self + self.freq).to_timestamp(how='start') - adjust if freq is None: base, mult = frequencies.get_freq_code(self.freq) From c7dd7ce98d844fe2c9390557cd01c26bd5e0bbd6 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Sun, 28 Oct 2018 19:58:18 -0700 Subject: [PATCH 18/24] update to user PeriodArray private method --- pandas/core/arrays/period.py | 8 +++--- pandas/tests/indexes/period/test_period.py | 2 +- pandas/tseries/offsets.py | 30 ++++++++++++---------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/pandas/core/arrays/period.py b/pandas/core/arrays/period.py index 9281a53cbf340..ed1d98fbac3cf 100644 --- a/pandas/core/arrays/period.py +++ b/pandas/core/arrays/period.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from datetime import timedelta import operator +import warnings import numpy as np @@ -720,20 +721,17 @@ def _addsub_int_array( self, other, # type: Union[Index, ExtensionArray, np.ndarray[int]] op, # type: Callable[Any, Any] - suppress # type: bool + suppress=False # type: bool ): # type: (...) -> PeriodArray if not suppress: - # case with freq of None will raise - # we need to use a different stacklevel for Index vs Array - lvl = 3 warnings.warn("Addition/subtraction of integer array from {cls} " "is deprecated, will be removed in a future " "version. Instead of adding `arr`, " "add `arr * self.freq`" .format(cls=type(self).__name__), - FutureWarning, stacklevel=lvl) + FutureWarning, stacklevel=3) assert op in [operator.add, operator.sub] if op is operator.sub: diff --git a/pandas/tests/indexes/period/test_period.py b/pandas/tests/indexes/period/test_period.py index 41ed4162d235b..c4fe3acea243b 100644 --- a/pandas/tests/indexes/period/test_period.py +++ b/pandas/tests/indexes/period/test_period.py @@ -338,7 +338,7 @@ 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): + with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): # GH#22535 assert not index.is_(index - 2) assert not index.is_(index - 0) diff --git a/pandas/tseries/offsets.py b/pandas/tseries/offsets.py index 2319d4ac0c62a..a6a7be6a838e3 100644 --- a/pandas/tseries/offsets.py +++ b/pandas/tseries/offsets.py @@ -291,7 +291,7 @@ def apply_index(self, i): # integer addition on PeriodIndex is deprecated, # so we directly use _time_shift instead asper = i.to_period('W') - shifted = asper._time_shift(weeks) + shifted = asper._data._time_shift(weeks) i = shifted.to_timestamp() + i.to_perioddelta('W') timedelta_kwds = {k: v for k, v in kwds.items() @@ -539,20 +539,21 @@ def apply_index(self, i): time = i.to_perioddelta('D') # to_period rolls forward to next BDay; track and # reduce n where it does when rolling forward - shifted = (i.to_perioddelta('B') - time).asi8 != 0 + asper = i.to_period('B') if self.n > 0: + shifted = (i.to_perioddelta('B') - time).asi8 != 0 + # Integer-array addition is deprecated, so we use # _time_shift directly roll = np.where(shifted, self.n - 1, self.n) - asper = i.to_period('B') - shifted = asper._addsub_int_array(roll, operator.add, - suppress=True) - result = shifted.to_timestamp() + time + shifted = asper._data._addsub_int_array(roll, operator.add, + suppress=True) else: # Integer addition is deprecated, so we use _time_shift directly roll = self.n - result = (i.to_period('B')._time_shift(roll)).to_timestamp() + time + shifted = asper._data._time_shift(roll) + result = shifted.to_timestamp() + time return result def onOffset(self, dt): @@ -1102,6 +1103,7 @@ def _apply(self, n, other): @apply_index_wraps def apply_index(self, i): # determine how many days away from the 1st of the month we are + dti = i days_from_start = i.to_perioddelta('M').asi8 delta = Timedelta(days=self.day_of_month - 1).value @@ -1122,9 +1124,9 @@ def apply_index(self, i): # integer-array addition on PeriodIndex is deprecated, # so we use _addsub_int_array directly asper = i.to_period('M') - shifted = asper._addsub_int_array(roll // 2, operator.add, - suppress=True) - i = shifted.to_timestamp() + shifted = asper._data._addsub_int_array(roll // 2, operator.add, + suppress=True) + i = type(dti)(shifted.to_timestamp()) # apply the correct day i = self._apply_index_days(i, roll) @@ -1307,7 +1309,7 @@ def apply_index(self, i): if self.weekday is None: # integer addition on PeriodIndex is deprecated, # so we use _time_shift directly - shifted = i.to_period('W')._time_shift(self.n) + shifted = i.to_period('W')._data._time_shift(self.n) return shifted.to_timestamp() + i.to_perioddelta('W') else: return self._end_apply_index(i) @@ -1335,14 +1337,14 @@ def _end_apply_index(self, dtindex): self.n, self.n - 1) # integer-array addition on PeriodIndex is deprecated, # so we use _addsub_int_array directly - shifted = base_period._addsub_int_array(roll, operator.add, - suppress=True) + shifted = base_period._data._addsub_int_array(roll, operator.add, + suppress=True) base = shifted.to_timestamp(how='end') else: # integer addition on PeriodIndex is deprecated, # so we use _time_shift directly roll = self.n - base = base_period._time_shift(roll).to_timestamp(how='end') + base = base_period._data._time_shift(roll).to_timestamp(how='end') return base + off + Timedelta(1, 'ns') - Timedelta(1, 'D') From 394a3b85c18d98330920be302f33b764adcacba4 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Mon, 29 Oct 2018 08:03:07 -0700 Subject: [PATCH 19/24] suppress --- pandas/core/arrays/period.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/arrays/period.py b/pandas/core/arrays/period.py index ed1d98fbac3cf..874853b1cdb94 100644 --- a/pandas/core/arrays/period.py +++ b/pandas/core/arrays/period.py @@ -789,7 +789,7 @@ def _add_delta_tdi(self, other): assert isinstance(self.freq, Tick) # checked by calling function delta = self._check_timedeltalike_freq_compat(other) - return self._addsub_int_array(delta, operator.add).asi8 + return self._addsub_int_array(delta, operator.add, suppress=True).asi8 def _add_delta(self, other): """ From 525667110c942766c117662c637cc8ab7e8a9fad Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Tue, 30 Oct 2018 10:05:47 -0700 Subject: [PATCH 20/24] function for warning --- doc/source/whatsnew/v0.24.0.txt | 16 ++++++------- pandas/_libs/tslibs/period.pyx | 17 +++----------- pandas/_libs/tslibs/timestamps.pyx | 19 +++++++++------ pandas/core/arrays/datetimelike.py | 37 ++++-------------------------- pandas/core/arrays/period.py | 9 ++------ 5 files changed, 30 insertions(+), 68 deletions(-) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index 28cb388c6e9fd..2f2bbb17a7b14 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -990,17 +990,17 @@ Current Behavior: .. ipython:: python - In [3]: per = pd.Period('2016Q1') - In [4]: per + 3 * per.freq + per = pd.Period('2016Q1') + per + 3 * per.freq - In [5]: ts = pd.Timestamp('1994-05-06 12:15:16', freq=pd.offsets.Hour()) - In [6]: ts + 2 * ts.freq + ts = pd.Timestamp('1994-05-06 12:15:16', freq=pd.offsets.Hour()) + ts + 2 * ts.freq - In [7]: tdi = pd.timedelta_range('1D', periods=2) - In [8]: tdi - np.array([2 * tdi.freq, 1 * tdi.freq]) + tdi = pd.timedelta_range('1D', periods=2) + tdi - np.array([2 * tdi.freq, 1 * tdi.freq]) - In [9]: dti = pd.date_range('2001-01-01', periods=2, freq='7D') - In [10]: dti + pd.Index([1 * dti.freq, 2 * dti.freq]) + dti = pd.date_range('2001-01-01', periods=2, freq='7D') + dti + pd.Index([1 * dti.freq, 2 * dti.freq]) .. _whatsnew_0240.prior_deprecations: diff --git a/pandas/_libs/tslibs/period.pyx b/pandas/_libs/tslibs/period.pyx index f35373c896240..5408d40f76407 100644 --- a/pandas/_libs/tslibs/period.pyx +++ b/pandas/_libs/tslibs/period.pyx @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- from datetime import datetime, date -import warnings from cpython cimport ( PyUnicode_Check, @@ -34,7 +33,7 @@ cdef extern from "src/datetime/np_datetime.h": cimport util from util cimport is_period_object, is_string_object -from timestamps import Timestamp +from timestamps import Timestamp, int_op_deprecated from timezones cimport is_utc, is_tzlocal, get_dst_info from timedeltas import Timedelta from timedeltas cimport delta_to_nanoseconds @@ -1646,12 +1645,7 @@ cdef class _Period(object): elif other is NaT: return NaT elif util.is_integer_object(other): - warnings.warn("Addition of integers to {cls} is " - "deprecated, will be removed in a future " - "version. Instead of adding `n`, add " - "`n * self.freq`" - .format(cls=type(self).__name__), - FutureWarning) + int_op_deprecated(self) ordinal = self.ordinal + other * self.freq.n return Period(ordinal=ordinal, freq=self.freq) @@ -1679,12 +1673,7 @@ cdef class _Period(object): neg_other = -other return self + neg_other elif util.is_integer_object(other): - warnings.warn("Subtraction of integers from {cls} is " - "deprecated, will be removed in a future " - "version. Instead of subtracting `n`, subtract " - "`n * self.freq`" - .format(cls=type(self).__name__), - FutureWarning) + int_op_deprecated(self) ordinal = self.ordinal - other * self.freq.n return Period(ordinal=ordinal, freq=self.freq) diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index c0abbbf0c8dec..60e92042ca205 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -40,8 +40,19 @@ from timezones cimport ( _zero_time = datetime_time(0, 0) _no_input = object() + # ---------------------------------------------------------------------- +def int_op_deprecated(obj): + # GH#22535 add/sub of integers and int-arrays is deprecated + if obj.freq is not None: + warnings.warn("Addition/subtraction of integers and integer-arrays " + "to {cls} is deprecated, will be removed in a future " + "version. Instead of adding/subtracting `n`, use " + "`n * self.freq`" + .format(cls=type(obj).__name__), + FutureWarning) + cdef inline object create_timestamp_from_ts(int64_t value, npy_datetimestruct dts, @@ -324,13 +335,7 @@ cdef class _Timestamp(datetime): tz=self.tzinfo, freq=self.freq) elif is_integer_object(other): - if self.freq is not None: - warnings.warn("Addition of integers to {cls} is " - "deprecated, will be removed in a future " - "version. Instead of adding `n`, add " - "`n * self.freq`" - .format(cls=type(self).__name__), - FutureWarning) + int_op_deprecated(self) if self is NaT: # to be compat with Period diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index f4167ca7d576c..f7f8fc9c5d68d 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -8,6 +8,7 @@ from pandas._libs import lib, iNaT, NaT from pandas._libs.tslibs import timezones from pandas._libs.tslibs.timedeltas import delta_to_nanoseconds, Timedelta +from pandas._libs.tslibs.timestamps import int_op_deprecated from pandas._libs.tslibs.period import ( Period, DIFFERENT_FREQ_INDEX, IncompatibleFrequency) @@ -494,16 +495,8 @@ def _addsub_int_array(self, other, op, suppress=False): assert not is_period_dtype(self) assert op in [operator.add, operator.sub] - if self.freq is not None and not suppress: - # case with freq of None will raise - # we need to use a different stacklevel for Index vs Array - lvl = 3 + 1 * isinstance(self, ABCIndexClass) - warnings.warn("Addition/subtraction of integer array from {cls} " - "is deprecated, will be removed in a future " - "version. Instead of adding `arr`, " - "add `arr * self.freq`" - .format(cls=type(self).__name__), - FutureWarning, stacklevel=lvl) + if not suppress: + int_op_deprecated(self) if self.freq is None: # GH#19123 @@ -647,17 +640,7 @@ 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 - if self.freq is not None: - # case with freq of None will raise - # we need to use a different stacklevel for Index vs Array - lvl = 2 + 1 * isinstance(self, ABCIndexClass) - warnings.warn("Addition of integers to {cls} is " - "deprecated, will be removed in a future " - "version. Instead of adding `n`, " - "add `n * self.freq`" - .format(cls=type(self).__name__), - FutureWarning, stacklevel=lvl) - + int_op_deprecated(self) result = self._time_shift(other) # array-like others @@ -716,17 +699,7 @@ 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 - if self.freq is not None: - # case with freq of None will raise - # we need to use a different stacklevel for Index vs Array - lvl = 2 + 1 * isinstance(self, ABCIndexClass) - warnings.warn("Subtraction of integers from {cls} is " - "deprecated, will be removed in a future " - "version. Instead of subtracting `n`, " - "subtract `n * self.freq`" - .format(cls=type(self).__name__), - FutureWarning, stacklevel=lvl) - + int_op_deprecated(self) result = self._time_shift(-other) elif isinstance(other, Period): diff --git a/pandas/core/arrays/period.py b/pandas/core/arrays/period.py index 874853b1cdb94..430a9bbdd744e 100644 --- a/pandas/core/arrays/period.py +++ b/pandas/core/arrays/period.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- from datetime import timedelta import operator -import warnings import numpy as np @@ -14,6 +13,7 @@ ) from pandas._libs.tslibs import period as libperiod from pandas._libs.tslibs.timedeltas import delta_to_nanoseconds, Timedelta +from pandas._libs.tslibs.timestamps import int_op_deprecated from pandas._libs.tslibs.fields import isleapyear_arr from pandas.util._decorators import cache_readonly, Appender from pandas.util._validators import validate_fillna_kwargs @@ -726,12 +726,7 @@ def _addsub_int_array( # type: (...) -> PeriodArray if not suppress: - warnings.warn("Addition/subtraction of integer array from {cls} " - "is deprecated, will be removed in a future " - "version. Instead of adding `arr`, " - "add `arr * self.freq`" - .format(cls=type(self).__name__), - FutureWarning, stacklevel=3) + int_op_deprecated(self) assert op in [operator.add, operator.sub] if op is operator.sub: From d8865d76dac80fc3547393f0c72fc3b82f355aef Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Tue, 30 Oct 2018 11:28:13 -0700 Subject: [PATCH 21/24] ignore stacklevel --- pandas/tests/indexes/datetimes/test_arithmetic.py | 4 ++-- pandas/tests/indexes/timedeltas/test_arithmetic.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pandas/tests/indexes/datetimes/test_arithmetic.py b/pandas/tests/indexes/datetimes/test_arithmetic.py index c1cc97bbcb141..1b75d6bd34764 100644 --- a/pandas/tests/indexes/datetimes/test_arithmetic.py +++ b/pandas/tests/indexes/datetimes/test_arithmetic.py @@ -58,14 +58,14 @@ def test_dti_shift_freqs(self): def test_dti_shift_int(self): rng = date_range('1/1/2000', periods=20) - with tm.assert_produces_warning(FutureWarning): + with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): # GH#22535 result = rng + 5 expected = rng.shift(5) tm.assert_index_equal(result, expected) - with tm.assert_produces_warning(FutureWarning): + with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): # GH#22535 result = rng - 5 diff --git a/pandas/tests/indexes/timedeltas/test_arithmetic.py b/pandas/tests/indexes/timedeltas/test_arithmetic.py index bf1b81b373cbe..a03698c9ea0de 100644 --- a/pandas/tests/indexes/timedeltas/test_arithmetic.py +++ b/pandas/tests/indexes/timedeltas/test_arithmetic.py @@ -130,7 +130,7 @@ def test_ufunc_coercions(self): def test_tdi_add_int(self, one): # Variants of `one` for #19012 rng = timedelta_range('1 days 09:00:00', freq='H', periods=10) - with tm.assert_produces_warning(FutureWarning): + with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): # GH#22535 result = rng + one expected = timedelta_range('1 days 10:00:00', freq='H', periods=10) @@ -146,7 +146,7 @@ def test_tdi_iadd_int(self, one): def test_tdi_sub_int(self, one): rng = timedelta_range('1 days 09:00:00', freq='H', periods=10) - with tm.assert_produces_warning(FutureWarning): + with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): # GH#22535 result = rng - one expected = timedelta_range('1 days 08:00:00', freq='H', periods=10) @@ -169,7 +169,7 @@ def test_tdi_add_integer_array(self, box): rng = timedelta_range('1 days 09:00:00', freq='H', periods=3) other = box([4, 3, 2]) expected = TimedeltaIndex(['1 day 13:00:00'] * 3) - with tm.assert_produces_warning(FutureWarning): + with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): # GH#22535 result = rng + other tm.assert_index_equal(result, expected) @@ -185,7 +185,7 @@ def test_tdi_sub_integer_array(self, box): rng = timedelta_range('9H', freq='H', periods=3) other = box([4, 3, 2]) expected = TimedeltaIndex(['5H', '7H', '9H']) - with tm.assert_produces_warning(FutureWarning): + with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): # GH#22535 result = rng - other tm.assert_index_equal(result, expected) From fcd65b3b82a791b0cc76c72733d9690662fd3f92 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Wed, 31 Oct 2018 08:24:27 -0700 Subject: [PATCH 22/24] Show deprecation warning in whatsnew --- doc/source/whatsnew/v0.24.0.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index 2f2bbb17a7b14..22427e1ada1ad 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -989,6 +989,9 @@ Previous Behavior: Current Behavior: .. ipython:: python + :okwarning: + per = pd.Period('2016Q1') + per + 3 per = pd.Period('2016Q1') per + 3 * per.freq From 6ded8b1396c28f8b118ee35cf26ad7cde2a2176d Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Wed, 31 Oct 2018 08:28:26 -0700 Subject: [PATCH 23/24] move location of addsub_int_array warning --- pandas/core/arrays/datetimelike.py | 9 +++------ pandas/core/arrays/period.py | 8 ++------ pandas/tseries/offsets.py | 9 +++------ 3 files changed, 8 insertions(+), 18 deletions(-) diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index 65b91d12d8c76..40bbccbbb7f9c 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -474,7 +474,7 @@ def _sub_period_array(self, other): new_values[mask] = NaT return new_values - def _addsub_int_array(self, other, op, suppress=False): + def _addsub_int_array(self, other, op): """ Add or subtract array-like of integers equivalent to applying `_time_shift` pointwise. @@ -484,8 +484,6 @@ def _addsub_int_array(self, other, op, suppress=False): other : Index, ExtensionArray, np.ndarray integer-dtype op : {operator.add, operator.sub} - suppress : bool, default False - Whether to suppress a deprecation warning Returns ------- @@ -495,9 +493,6 @@ def _addsub_int_array(self, other, op, suppress=False): assert not is_period_dtype(self) assert op in [operator.add, operator.sub] - if not suppress: - int_op_deprecated(self) - if self.freq is None: # GH#19123 raise NullFrequencyError("Cannot shift with no freq") @@ -654,6 +649,7 @@ def __add__(self, other): # DatetimeIndex, ndarray[datetime64] return self._add_datetime_arraylike(other) elif is_integer_dtype(other): + int_op_deprecated(self) result = self._addsub_int_array(other, operator.add) elif is_float_dtype(other): # Explicitly catch invalid dtypes @@ -719,6 +715,7 @@ def __sub__(self, other): # PeriodIndex result = self._sub_period_array(other) elif is_integer_dtype(other): + int_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/core/arrays/period.py b/pandas/core/arrays/period.py index 430a9bbdd744e..3bd97709ea7ef 100644 --- a/pandas/core/arrays/period.py +++ b/pandas/core/arrays/period.py @@ -720,14 +720,10 @@ def _sub_period(self, other): def _addsub_int_array( self, other, # type: Union[Index, ExtensionArray, np.ndarray[int]] - op, # type: Callable[Any, Any] - suppress=False # type: bool + op # type: Callable[Any, Any] ): # type: (...) -> PeriodArray - if not suppress: - int_op_deprecated(self) - assert op in [operator.add, operator.sub] if op is operator.sub: other = -other @@ -784,7 +780,7 @@ def _add_delta_tdi(self, other): assert isinstance(self.freq, Tick) # checked by calling function delta = self._check_timedeltalike_freq_compat(other) - return self._addsub_int_array(delta, operator.add, suppress=True).asi8 + return self._addsub_int_array(delta, operator.add).asi8 def _add_delta(self, other): """ diff --git a/pandas/tseries/offsets.py b/pandas/tseries/offsets.py index a6a7be6a838e3..6fb562e301ac2 100644 --- a/pandas/tseries/offsets.py +++ b/pandas/tseries/offsets.py @@ -546,8 +546,7 @@ def apply_index(self, i): # Integer-array addition is deprecated, so we use # _time_shift directly roll = np.where(shifted, self.n - 1, self.n) - shifted = asper._data._addsub_int_array(roll, operator.add, - suppress=True) + shifted = asper._data._addsub_int_array(roll, operator.add) else: # Integer addition is deprecated, so we use _time_shift directly roll = self.n @@ -1124,8 +1123,7 @@ def apply_index(self, i): # integer-array addition on PeriodIndex is deprecated, # so we use _addsub_int_array directly asper = i.to_period('M') - shifted = asper._data._addsub_int_array(roll // 2, operator.add, - suppress=True) + shifted = asper._data._addsub_int_array(roll // 2, operator.add) i = type(dti)(shifted.to_timestamp()) # apply the correct day @@ -1337,8 +1335,7 @@ def _end_apply_index(self, dtindex): self.n, self.n - 1) # integer-array addition on PeriodIndex is deprecated, # so we use _addsub_int_array directly - shifted = base_period._data._addsub_int_array(roll, operator.add, - suppress=True) + shifted = base_period._data._addsub_int_array(roll, operator.add) base = shifted.to_timestamp(how='end') else: # integer addition on PeriodIndex is deprecated, From 4a7b589b80da97628ee2e779303c7a42274e93d0 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Wed, 31 Oct 2018 08:30:20 -0700 Subject: [PATCH 24/24] rename deprecation warning function --- pandas/_libs/tslibs/period.pyx | 6 +++--- pandas/_libs/tslibs/timestamps.pyx | 4 ++-- pandas/core/arrays/datetimelike.py | 10 +++++----- pandas/core/arrays/period.py | 1 - 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/pandas/_libs/tslibs/period.pyx b/pandas/_libs/tslibs/period.pyx index 5408d40f76407..73fccb9125a85 100644 --- a/pandas/_libs/tslibs/period.pyx +++ b/pandas/_libs/tslibs/period.pyx @@ -33,7 +33,7 @@ cdef extern from "src/datetime/np_datetime.h": cimport util from util cimport is_period_object, is_string_object -from timestamps import Timestamp, int_op_deprecated +from timestamps import Timestamp, maybe_integer_op_deprecated from timezones cimport is_utc, is_tzlocal, get_dst_info from timedeltas import Timedelta from timedeltas cimport delta_to_nanoseconds @@ -1645,7 +1645,7 @@ cdef class _Period(object): elif other is NaT: return NaT elif util.is_integer_object(other): - int_op_deprecated(self) + maybe_integer_op_deprecated(self) ordinal = self.ordinal + other * self.freq.n return Period(ordinal=ordinal, freq=self.freq) @@ -1673,7 +1673,7 @@ cdef class _Period(object): neg_other = -other return self + neg_other elif util.is_integer_object(other): - int_op_deprecated(self) + maybe_integer_op_deprecated(self) ordinal = self.ordinal - other * self.freq.n return Period(ordinal=ordinal, freq=self.freq) diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index 60e92042ca205..93fa99ce1bd87 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -43,7 +43,7 @@ _no_input = object() # ---------------------------------------------------------------------- -def int_op_deprecated(obj): +def maybe_integer_op_deprecated(obj): # GH#22535 add/sub of integers and int-arrays is deprecated if obj.freq is not None: warnings.warn("Addition/subtraction of integers and integer-arrays " @@ -335,7 +335,7 @@ cdef class _Timestamp(datetime): tz=self.tzinfo, freq=self.freq) elif is_integer_object(other): - int_op_deprecated(self) + maybe_integer_op_deprecated(self) if self is NaT: # to be compat with Period diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index 40bbccbbb7f9c..2266c7be53523 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -8,7 +8,7 @@ from pandas._libs import lib, iNaT, NaT from pandas._libs.tslibs import timezones from pandas._libs.tslibs.timedeltas import delta_to_nanoseconds, Timedelta -from pandas._libs.tslibs.timestamps import int_op_deprecated +from pandas._libs.tslibs.timestamps import maybe_integer_op_deprecated from pandas._libs.tslibs.period import ( Period, DIFFERENT_FREQ_INDEX, IncompatibleFrequency) @@ -635,7 +635,7 @@ 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 - int_op_deprecated(self) + maybe_integer_op_deprecated(self) result = self._time_shift(other) # array-like others @@ -649,7 +649,7 @@ def __add__(self, other): # DatetimeIndex, ndarray[datetime64] return self._add_datetime_arraylike(other) elif is_integer_dtype(other): - int_op_deprecated(self) + maybe_integer_op_deprecated(self) result = self._addsub_int_array(other, operator.add) elif is_float_dtype(other): # Explicitly catch invalid dtypes @@ -695,7 +695,7 @@ 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 - int_op_deprecated(self) + maybe_integer_op_deprecated(self) result = self._time_shift(-other) elif isinstance(other, Period): @@ -715,7 +715,7 @@ def __sub__(self, other): # PeriodIndex result = self._sub_period_array(other) elif is_integer_dtype(other): - int_op_deprecated(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/core/arrays/period.py b/pandas/core/arrays/period.py index 3bd97709ea7ef..1bbad4b73953d 100644 --- a/pandas/core/arrays/period.py +++ b/pandas/core/arrays/period.py @@ -13,7 +13,6 @@ ) from pandas._libs.tslibs import period as libperiod from pandas._libs.tslibs.timedeltas import delta_to_nanoseconds, Timedelta -from pandas._libs.tslibs.timestamps import int_op_deprecated from pandas._libs.tslibs.fields import isleapyear_arr from pandas.util._decorators import cache_readonly, Appender from pandas.util._validators import validate_fillna_kwargs