From 3e3c5a0a7407cc518e8fee6225cc61865b01ddf1 Mon Sep 17 00:00:00 2001 From: arminv Date: Thu, 13 Sep 2018 13:23:03 -0400 Subject: [PATCH 1/7] initial commit --- doc/source/whatsnew/v0.24.0.txt | 1 + pandas/core/arrays/datetimelike.py | 32 +++++++++++++++------- pandas/tests/indexes/datetimes/test_ops.py | 6 ++++ 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index 232f879285543..cd03642a7644e 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -527,6 +527,7 @@ Datetimelike API Changes - :class:`PeriodIndex` subtraction of another ``PeriodIndex`` will now return an object-dtype :class:`Index` of :class:`DateOffset` objects instead of raising a ``TypeError`` (:issue:`20049`) - :func:`cut` and :func:`qcut` now returns a :class:`DatetimeIndex` or :class:`TimedeltaIndex` bins when the input is datetime or timedelta dtype respectively and ``retbins=True`` (:issue:`19891`) - :meth:`DatetimeIndex.to_period` and :meth:`Timestamp.to_period` will issue a warning when timezone information will be lost (:issue:`21333`) +- :func:`shift` now accepts ``periods`` argument instead of ``n`` for consistency with `Index.shift` (:issue:`22458`) .. _whatsnew_0240.api.other: diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index eb8821382037d..b6580fb63642e 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -521,24 +521,36 @@ def _addsub_offset_array(self, other, op): kwargs['freq'] = 'infer' return type(self)(res_values, **kwargs) - def shift(self, n, freq=None): + def shift(self, periods, freq=None): """ - Specialized shift which produces a Datetime/Timedelta Array/Index + Shift index by desired number of time frequency increments. + + This method is for shifting the values of datetime-like indexes + by a specified time increment a given number of times. Parameters ---------- - n : int - Periods to shift by - freq : DateOffset or timedelta-like, optional + periods : int + Number of periods (or increments) to shift by, + can be positive or negative. + freq : pandas.DateOffset, pandas.Timedelta or string, optional + Frequency increment to shift by. + If None, the index is shifted by its own `freq` attribute. + Offset aliases are valid strings, e.g., 'D', 'W', 'M' etc. Returns ------- - shifted : same type as self + pandas.DatetimeIndex + Shifted index. + + See Also + -------- + Index.shift : Shift values of Index. """ if freq is not None and freq != self.freq: if isinstance(freq, compat.string_types): freq = frequencies.to_offset(freq) - offset = n * freq + offset = periods * freq result = self + offset if hasattr(self, 'tz'): @@ -546,15 +558,15 @@ def shift(self, n, freq=None): return result - if n == 0: + if periods == 0: # immutable so OK return self if self.freq is None: raise NullFrequencyError("Cannot shift with no freq") - start = self[0] + n * self.freq - end = self[-1] + n * self.freq + start = self[0] + periods * self.freq + end = self[-1] + periods * self.freq attribs = self._get_attributes_dict() return self._generate_range(start=start, end=end, periods=None, **attribs) diff --git a/pandas/tests/indexes/datetimes/test_ops.py b/pandas/tests/indexes/datetimes/test_ops.py index 6ccd310f33bbd..6875fb0dfebd9 100644 --- a/pandas/tests/indexes/datetimes/test_ops.py +++ b/pandas/tests/indexes/datetimes/test_ops.py @@ -540,6 +540,12 @@ def test_shift(self): shifted = rng.shift(1, freq=CDay()) assert shifted[0] == rng[0] + CDay() + def test_shift_periods(self): + # GH #22458 : + idx = pd.DatetimeIndex(start=START, end=END, + periods=3) + tm.assert_index_equal(idx.shift(periods=0), idx) + def test_pickle_unpickle(self): unpickled = tm.round_trip_pickle(self.rng) assert unpickled.freq is not None From 5b592f6950cf66451481a36b6a05df90aafa0e0d Mon Sep 17 00:00:00 2001 From: arminv Date: Fri, 14 Sep 2018 15:50:41 -0400 Subject: [PATCH 2/7] added args/kwargs signature --- pandas/core/arrays/datetimelike.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index b6580fb63642e..1efbebd213c19 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -521,7 +521,7 @@ def _addsub_offset_array(self, other, op): kwargs['freq'] = 'infer' return type(self)(res_values, **kwargs) - def shift(self, periods, freq=None): + def shift(self, *args, **kwargs): """ Shift index by desired number of time frequency increments. @@ -547,6 +547,18 @@ def shift(self, periods, freq=None): -------- Index.shift : Shift values of Index. """ + if 'n' in kwargs.keys(): + warnings.warn("n argument is deprecated, use periods instead. ", + DeprecationWarning) + periods = kwargs['n'] + elif 'periods' in kwargs.keys(): + periods = kwargs['periods'] + elif 'periods' not in kwargs.keys() and len(args) != 0: + periods = args[0] + if 'freq' not in kwargs.keys(): + freq = None + else: + freq = kwargs['freq'] if freq is not None and freq != self.freq: if isinstance(freq, compat.string_types): freq = frequencies.to_offset(freq) From f6842459d9cb4d507f101107e077509c760df9d2 Mon Sep 17 00:00:00 2001 From: arminv Date: Sat, 15 Sep 2018 05:52:20 -0400 Subject: [PATCH 3/7] Change signature and add tests --- doc/source/whatsnew/v0.24.0.txt | 2 +- pandas/core/arrays/datetimelike.py | 19 ++++++++++--------- pandas/tests/indexes/datetimes/test_ops.py | 4 ++++ 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index 7d53446475adb..eb10e83183c2c 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -528,7 +528,7 @@ Datetimelike API Changes - :class:`PeriodIndex` subtraction of another ``PeriodIndex`` will now return an object-dtype :class:`Index` of :class:`DateOffset` objects instead of raising a ``TypeError`` (:issue:`20049`) - :func:`cut` and :func:`qcut` now returns a :class:`DatetimeIndex` or :class:`TimedeltaIndex` bins when the input is datetime or timedelta dtype respectively and ``retbins=True`` (:issue:`19891`) - :meth:`DatetimeIndex.to_period` and :meth:`Timestamp.to_period` will issue a warning when timezone information will be lost (:issue:`21333`) -- :func:`shift` now accepts ``periods`` argument instead of ``n`` for consistency with `Index.shift` (:issue:`22458`) +- :func:`shift` now accepts ``periods`` argument instead of ``n`` for consistency with `Index.shift`. Using ``n`` throws a deprecation warning (:issue:`22458`) .. _whatsnew_0240.api.other: diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index 1efbebd213c19..4b9ec15412735 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -547,9 +547,10 @@ def shift(self, *args, **kwargs): -------- Index.shift : Shift values of Index. """ + # GH #22458 : if 'n' in kwargs.keys(): warnings.warn("n argument is deprecated, use periods instead. ", - DeprecationWarning) + DeprecationWarning, stacklevel=2) periods = kwargs['n'] elif 'periods' in kwargs.keys(): periods = kwargs['periods'] @@ -559,16 +560,16 @@ def shift(self, *args, **kwargs): freq = None else: freq = kwargs['freq'] - if freq is not None and freq != self.freq: - if isinstance(freq, compat.string_types): - freq = frequencies.to_offset(freq) - offset = periods * freq - result = self + offset + if freq is not None and freq != self.freq: + if isinstance(freq, compat.string_types): + freq = frequencies.to_offset(freq) + offset = periods * freq + result = self + offset - if hasattr(self, 'tz'): - result._tz = self.tz + if hasattr(self, 'tz'): + result._tz = self.tz - return result + return result if periods == 0: # immutable so OK diff --git a/pandas/tests/indexes/datetimes/test_ops.py b/pandas/tests/indexes/datetimes/test_ops.py index 6875fb0dfebd9..7409a951b6593 100644 --- a/pandas/tests/indexes/datetimes/test_ops.py +++ b/pandas/tests/indexes/datetimes/test_ops.py @@ -545,6 +545,10 @@ def test_shift_periods(self): idx = pd.DatetimeIndex(start=START, end=END, periods=3) tm.assert_index_equal(idx.shift(periods=0), idx) + tm.assert_index_equal(idx.shift(0), idx) + with tm.assert_produces_warning(DeprecationWarning, + check_stacklevel=False): + tm.assert_index_equal(idx.shift(n=0), idx) def test_pickle_unpickle(self): unpickled = tm.round_trip_pickle(self.rng) From 240aa7ee9f36465e7a1e306e15ae3cf3498ec65c Mon Sep 17 00:00:00 2001 From: arminv Date: Sat, 15 Sep 2018 14:25:51 -0400 Subject: [PATCH 4/7] use decorator for parsing/deprecating kwargs --- pandas/core/arrays/datetimelike.py | 36 +++++++--------------- pandas/tests/indexes/datetimes/test_ops.py | 2 +- 2 files changed, 12 insertions(+), 26 deletions(-) diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index 4b9ec15412735..b55179cdf51a7 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -38,7 +38,7 @@ from pandas.core.algorithms import checked_add_with_arr from .base import ExtensionOpsMixin - +from pandas.util._decorators import deprecate_kwarg def _make_comparison_op(op, cls): # TODO: share code with indexes.base version? Main difference is that @@ -521,7 +521,8 @@ def _addsub_offset_array(self, other, op): kwargs['freq'] = 'infer' return type(self)(res_values, **kwargs) - def shift(self, *args, **kwargs): + @deprecate_kwarg(old_arg_name='n', new_arg_name='periods') + def shift(self, periods, freq=None): """ Shift index by desired number of time frequency increments. @@ -547,29 +548,14 @@ def shift(self, *args, **kwargs): -------- Index.shift : Shift values of Index. """ - # GH #22458 : - if 'n' in kwargs.keys(): - warnings.warn("n argument is deprecated, use periods instead. ", - DeprecationWarning, stacklevel=2) - periods = kwargs['n'] - elif 'periods' in kwargs.keys(): - periods = kwargs['periods'] - elif 'periods' not in kwargs.keys() and len(args) != 0: - periods = args[0] - if 'freq' not in kwargs.keys(): - freq = None - else: - freq = kwargs['freq'] - if freq is not None and freq != self.freq: - if isinstance(freq, compat.string_types): - freq = frequencies.to_offset(freq) - offset = periods * freq - result = self + offset - - if hasattr(self, 'tz'): - result._tz = self.tz - - return result + if freq is not None and freq != self.freq: + if isinstance(freq, compat.string_types): + freq = frequencies.to_offset(freq) + offset = periods * freq + result = self + offset + if hasattr(self, 'tz'): + result._tz = self.tz + return result if periods == 0: # immutable so OK diff --git a/pandas/tests/indexes/datetimes/test_ops.py b/pandas/tests/indexes/datetimes/test_ops.py index 7409a951b6593..ca6c96f37a26f 100644 --- a/pandas/tests/indexes/datetimes/test_ops.py +++ b/pandas/tests/indexes/datetimes/test_ops.py @@ -546,7 +546,7 @@ def test_shift_periods(self): periods=3) tm.assert_index_equal(idx.shift(periods=0), idx) tm.assert_index_equal(idx.shift(0), idx) - with tm.assert_produces_warning(DeprecationWarning, + with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): tm.assert_index_equal(idx.shift(n=0), idx) From 6f627ac2d96aa25881f3514242c95af001ae3b74 Mon Sep 17 00:00:00 2001 From: arminv Date: Sat, 15 Sep 2018 14:58:33 -0400 Subject: [PATCH 5/7] PEP8 --- pandas/core/arrays/datetimelike.py | 1 + pandas/tests/indexes/datetimes/test_ops.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index 90a9d3745e1d1..639dbfc609313 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -40,6 +40,7 @@ from .base import ExtensionOpsMixin from pandas.util._decorators import deprecate_kwarg + def _make_comparison_op(op, cls): # TODO: share code with indexes.base version? Main difference is that # the block for MultiIndex was removed here. diff --git a/pandas/tests/indexes/datetimes/test_ops.py b/pandas/tests/indexes/datetimes/test_ops.py index ca6c96f37a26f..f9287de5b93c5 100644 --- a/pandas/tests/indexes/datetimes/test_ops.py +++ b/pandas/tests/indexes/datetimes/test_ops.py @@ -546,7 +546,7 @@ def test_shift_periods(self): periods=3) tm.assert_index_equal(idx.shift(periods=0), idx) tm.assert_index_equal(idx.shift(0), idx) - with tm.assert_produces_warning(FutureWarning, + with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): tm.assert_index_equal(idx.shift(n=0), idx) From ff335a558ddda1dd55209d4cdcff755cbf540c2b Mon Sep 17 00:00:00 2001 From: arminv Date: Tue, 18 Sep 2018 16:09:47 -0400 Subject: [PATCH 6/7] doc changes --- doc/source/whatsnew/v0.24.0.txt | 2 +- pandas/core/arrays/datetimelike.py | 3 +++ pandas/core/generic.py | 5 +++++ pandas/tests/indexes/datetimes/test_ops.py | 2 +- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index 9be3dcfee6752..61e149a568210 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -528,7 +528,6 @@ Datetimelike API Changes - :class:`PeriodIndex` subtraction of another ``PeriodIndex`` will now return an object-dtype :class:`Index` of :class:`DateOffset` objects instead of raising a ``TypeError`` (:issue:`20049`) - :func:`cut` and :func:`qcut` now returns a :class:`DatetimeIndex` or :class:`TimedeltaIndex` bins when the input is datetime or timedelta dtype respectively and ``retbins=True`` (:issue:`19891`) - :meth:`DatetimeIndex.to_period` and :meth:`Timestamp.to_period` will issue a warning when timezone information will be lost (:issue:`21333`) -- :func:`shift` now accepts ``periods`` argument instead of ``n`` for consistency with `Index.shift`. Using ``n`` throws a deprecation warning (:issue:`22458`) .. _whatsnew_0240.api.other: @@ -562,6 +561,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`) - :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`) .. _whatsnew_0240.prior_deprecations: diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index d029581e58ff5..91c119808db52 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -536,6 +536,9 @@ def shift(self, periods, freq=None): periods : int Number of periods (or increments) to shift by, can be positive or negative. + + .. versionchanged:: 0.24.0 + freq : pandas.DateOffset, pandas.Timedelta or string, optional Frequency increment to shift by. If None, the index is shifted by its own `freq` attribute. diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 373830ec7892e..077bff1e89349 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -8298,6 +8298,11 @@ def mask(self, cond, other=np.nan, inplace=False, axis=None, level=None, See Notes. axis : %(axes_single_arg)s + See Also + -------- + Index.shift : Shift values of Index. + DatetimeIndex.shift : Shift values of DatetimeIndex. + Notes ----- If freq is specified then the index values are shifted but the data diff --git a/pandas/tests/indexes/datetimes/test_ops.py b/pandas/tests/indexes/datetimes/test_ops.py index 4f078c826ffee..5b624f99e5fcd 100644 --- a/pandas/tests/indexes/datetimes/test_ops.py +++ b/pandas/tests/indexes/datetimes/test_ops.py @@ -541,7 +541,7 @@ def test_shift(self): assert shifted[0] == rng[0] + CDay() def test_shift_periods(self): - # GH #22458 : + # GH #22458 : argument 'n' was deprecated in favor of 'periods' idx = pd.DatetimeIndex(start=START, end=END, periods=3) tm.assert_index_equal(idx.shift(periods=0), idx) From d9bbfe8a57aa33e41a31b1413075b3ac7024daed Mon Sep 17 00:00:00 2001 From: arminv Date: Wed, 19 Sep 2018 17:50:05 -0400 Subject: [PATCH 7/7] check_stacklevel=True --- pandas/tests/indexes/datetimes/test_ops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/indexes/datetimes/test_ops.py b/pandas/tests/indexes/datetimes/test_ops.py index 5b624f99e5fcd..b60b222d095b9 100644 --- a/pandas/tests/indexes/datetimes/test_ops.py +++ b/pandas/tests/indexes/datetimes/test_ops.py @@ -547,7 +547,7 @@ def test_shift_periods(self): tm.assert_index_equal(idx.shift(periods=0), idx) tm.assert_index_equal(idx.shift(0), idx) with tm.assert_produces_warning(FutureWarning, - check_stacklevel=False): + check_stacklevel=True): tm.assert_index_equal(idx.shift(n=0), idx) def test_pickle_unpickle(self):