Skip to content

Commit b039c1d

Browse files
committed
Merge pull request #4864 from dalejung/panel-tshift-4853
Panel tshift 4853
2 parents a1f62d1 + 00827fb commit b039c1d

File tree

11 files changed

+159
-146
lines changed

11 files changed

+159
-146
lines changed

doc/source/release.rst

+1
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,7 @@ Bug Fixes
428428
single column and passing a list for ``ascending``, the argument for
429429
``ascending`` was being interpreted as ``True`` (:issue:`4839`,
430430
:issue:`4846`)
431+
- Fixed ``Panel.tshift`` not working. Added `freq` support to ``Panel.shift`` (:issue:`4853`)
431432

432433
pandas 0.12.0
433434
-------------

pandas/core/datetools.py

+20
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,23 @@
3535
isBusinessDay = BDay().onOffset
3636
isMonthEnd = MonthEnd().onOffset
3737
isBMonthEnd = BMonthEnd().onOffset
38+
39+
def _resolve_offset(freq, kwds):
40+
if 'timeRule' in kwds or 'offset' in kwds:
41+
offset = kwds.get('offset', None)
42+
offset = kwds.get('timeRule', offset)
43+
if isinstance(offset, compat.string_types):
44+
offset = getOffset(offset)
45+
warn = True
46+
else:
47+
offset = freq
48+
warn = False
49+
50+
if warn:
51+
import warnings
52+
warnings.warn("'timeRule' and 'offset' parameters are deprecated,"
53+
" please use 'freq' instead",
54+
FutureWarning)
55+
56+
return offset
57+

pandas/core/frame.py

-49
Original file line numberDiff line numberDiff line change
@@ -3497,55 +3497,6 @@ def diff(self, periods=1):
34973497
new_data = self._data.diff(periods)
34983498
return self._constructor(new_data)
34993499

3500-
def shift(self, periods=1, freq=None, **kwds):
3501-
"""
3502-
Shift the index of the DataFrame by desired number of periods with an
3503-
optional time freq
3504-
3505-
Parameters
3506-
----------
3507-
periods : int
3508-
Number of periods to move, can be positive or negative
3509-
freq : DateOffset, timedelta, or time rule string, optional
3510-
Increment to use from datetools module or time rule (e.g. 'EOM')
3511-
3512-
Notes
3513-
-----
3514-
If freq is specified then the index values are shifted but the data
3515-
if not realigned
3516-
3517-
Returns
3518-
-------
3519-
shifted : DataFrame
3520-
"""
3521-
from pandas.core.series import _resolve_offset
3522-
3523-
if periods == 0:
3524-
return self
3525-
3526-
offset = _resolve_offset(freq, kwds)
3527-
3528-
if isinstance(offset, compat.string_types):
3529-
offset = datetools.to_offset(offset)
3530-
3531-
if offset is None:
3532-
indexer = com._shift_indexer(len(self), periods)
3533-
new_data = self._data.shift(indexer, periods)
3534-
elif isinstance(self.index, PeriodIndex):
3535-
orig_offset = datetools.to_offset(self.index.freq)
3536-
if offset == orig_offset:
3537-
new_data = self._data.copy()
3538-
new_data.axes[1] = self.index.shift(periods)
3539-
else:
3540-
msg = ('Given freq %s does not match PeriodIndex freq %s' %
3541-
(offset.rule_code, orig_offset.rule_code))
3542-
raise ValueError(msg)
3543-
else:
3544-
new_data = self._data.copy()
3545-
new_data.axes[1] = self.index.shift(periods, offset)
3546-
3547-
return self._constructor(new_data)
3548-
35493500
#----------------------------------------------------------------------
35503501
# Function application
35513502

pandas/core/generic.py

+68-5
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111
import pandas.core.indexing as indexing
1212
from pandas.core.indexing import _maybe_convert_indices
1313
from pandas.tseries.index import DatetimeIndex
14+
from pandas.tseries.period import PeriodIndex
1415
from pandas.core.internals import BlockManager
1516
import pandas.core.common as com
17+
import pandas.core.datetools as datetools
1618
from pandas import compat, _np_version_under1p7
1719
from pandas.compat import map, zip, lrange
1820
from pandas.core.common import (isnull, notnull, is_list_like,
@@ -2667,7 +2669,40 @@ def cummin(self, axis=None, skipna=True):
26672669
result = np.minimum.accumulate(y, axis)
26682670
return self._wrap_array(result, self.axes, copy=False)
26692671

2670-
def tshift(self, periods=1, freq=None, **kwds):
2672+
def shift(self, periods=1, freq=None, axis=0, **kwds):
2673+
"""
2674+
Shift the index of the DataFrame by desired number of periods with an
2675+
optional time freq
2676+
2677+
Parameters
2678+
----------
2679+
periods : int
2680+
Number of periods to move, can be positive or negative
2681+
freq : DateOffset, timedelta, or time rule string, optional
2682+
Increment to use from datetools module or time rule (e.g. 'EOM')
2683+
2684+
Notes
2685+
-----
2686+
If freq is specified then the index values are shifted but the data
2687+
if not realigned
2688+
2689+
Returns
2690+
-------
2691+
shifted : DataFrame
2692+
"""
2693+
if periods == 0:
2694+
return self
2695+
2696+
if freq is None and not len(kwds):
2697+
block_axis = self._get_block_manager_axis(axis)
2698+
indexer = com._shift_indexer(len(self), periods)
2699+
new_data = self._data.shift(indexer, periods, axis=block_axis)
2700+
else:
2701+
return self.tshift(periods, freq, **kwds)
2702+
2703+
return self._constructor(new_data)
2704+
2705+
def tshift(self, periods=1, freq=None, axis=0, **kwds):
26712706
"""
26722707
Shift the time index, using the index's frequency if available
26732708
@@ -2677,6 +2712,8 @@ def tshift(self, periods=1, freq=None, **kwds):
26772712
Number of periods to move, can be positive or negative
26782713
freq : DateOffset, timedelta, or time rule string, default None
26792714
Increment to use from datetools module or time rule (e.g. 'EOM')
2715+
axis : int or basestring
2716+
Corresponds to the axis that contains the Index
26802717
26812718
Notes
26822719
-----
@@ -2686,19 +2723,45 @@ def tshift(self, periods=1, freq=None, **kwds):
26862723
26872724
Returns
26882725
-------
2689-
shifted : Series
2726+
shifted : NDFrame
26902727
"""
2728+
from pandas.core.datetools import _resolve_offset
2729+
2730+
index = self._get_axis(axis)
26912731
if freq is None:
2692-
freq = getattr(self.index, 'freq', None)
2732+
freq = getattr(index, 'freq', None)
26932733

26942734
if freq is None:
2695-
freq = getattr(self.index, 'inferred_freq', None)
2735+
freq = getattr(index, 'inferred_freq', None)
26962736

26972737
if freq is None:
26982738
msg = 'Freq was not given and was not set in the index'
26992739
raise ValueError(msg)
27002740

2701-
return self.shift(periods, freq, **kwds)
2741+
2742+
if periods == 0:
2743+
return self
2744+
2745+
offset = _resolve_offset(freq, kwds)
2746+
2747+
if isinstance(offset, compat.string_types):
2748+
offset = datetools.to_offset(offset)
2749+
2750+
block_axis = self._get_block_manager_axis(axis)
2751+
if isinstance(index, PeriodIndex):
2752+
orig_offset = datetools.to_offset(index.freq)
2753+
if offset == orig_offset:
2754+
new_data = self._data.copy()
2755+
new_data.axes[block_axis] = index.shift(periods)
2756+
else:
2757+
msg = ('Given freq %s does not match PeriodIndex freq %s' %
2758+
(offset.rule_code, orig_offset.rule_code))
2759+
raise ValueError(msg)
2760+
else:
2761+
new_data = self._data.copy()
2762+
new_data.axes[block_axis] = index.shift(periods, offset)
2763+
2764+
return self._constructor(new_data)
27022765

27032766
def truncate(self, before=None, after=None, copy=True):
27042767
"""Function truncate a sorted DataFrame / Series before and/or after

pandas/core/internals.py

+16-6
Original file line numberDiff line numberDiff line change
@@ -758,17 +758,27 @@ def diff(self, n):
758758
new_values = com.diff(self.values, n, axis=1)
759759
return [make_block(new_values, self.items, self.ref_items, ndim=self.ndim, fastpath=True)]
760760

761-
def shift(self, indexer, periods):
761+
def shift(self, indexer, periods, axis=0):
762762
""" shift the block by periods, possibly upcast """
763763

764-
new_values = self.values.take(indexer, axis=1)
764+
new_values = self.values.take(indexer, axis=axis)
765765
# convert integer to float if necessary. need to do a lot more than
766766
# that, handle boolean etc also
767767
new_values, fill_value = com._maybe_upcast(new_values)
768-
if periods > 0:
769-
new_values[:, :periods] = fill_value
768+
769+
# 1-d
770+
if self.ndim == 1:
771+
if periods > 0:
772+
new_values[:periods] = fill_value
773+
else:
774+
new_values[periods:] = fill_value
775+
776+
# 2-d
770777
else:
771-
new_values[:, periods:] = fill_value
778+
if periods > 0:
779+
new_values[:, :periods] = fill_value
780+
else:
781+
new_values[:, periods:] = fill_value
772782
return [make_block(new_values, self.items, self.ref_items, ndim=self.ndim, fastpath=True)]
773783

774784
def eval(self, func, other, raise_on_error=True, try_cast=False):
@@ -1547,7 +1557,7 @@ def fillna(self, value, inplace=False, downcast=None):
15471557
values = self.values if inplace else self.values.copy()
15481558
return [ self.make_block(values.get_values(value), fill_value=value) ]
15491559

1550-
def shift(self, indexer, periods):
1560+
def shift(self, indexer, periods, axis=0):
15511561
""" shift the block by periods """
15521562

15531563
new_values = self.values.to_dense().take(indexer)

pandas/core/panel.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -1017,7 +1017,7 @@ def count(self, axis='major'):
10171017

10181018
return self._wrap_result(result, axis)
10191019

1020-
def shift(self, lags, axis='major'):
1020+
def shift(self, lags, freq=None, axis='major'):
10211021
"""
10221022
Shift major or minor axis by specified number of leads/lags. Drops
10231023
periods right now compared with DataFrame.shift
@@ -1036,6 +1036,9 @@ def shift(self, lags, axis='major'):
10361036
major_axis = self.major_axis
10371037
minor_axis = self.minor_axis
10381038

1039+
if freq:
1040+
return self.tshift(lags, freq, axis=axis)
1041+
10391042
if lags > 0:
10401043
vslicer = slice(None, -lags)
10411044
islicer = slice(lags, None)
@@ -1058,6 +1061,9 @@ def shift(self, lags, axis='major'):
10581061
return self._constructor(values, items=items, major_axis=major_axis,
10591062
minor_axis=minor_axis)
10601063

1064+
def tshift(self, periods=1, freq=None, axis='major', **kwds):
1065+
return super(Panel, self).tshift(periods, freq, axis, **kwds)
1066+
10611067
def truncate(self, before=None, after=None, axis='major'):
10621068
"""Function truncates a sorted Panel before and/or after some
10631069
particular values on the requested axis

pandas/core/series.py

-78
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,6 @@
5959
_np_version_under1p6 = LooseVersion(_np_version) < '1.6'
6060
_np_version_under1p7 = LooseVersion(_np_version) < '1.7'
6161

62-
_SHOW_WARNINGS = True
63-
6462
class _TimeOp(object):
6563
"""
6664
Wrapper around Series datetime/time/timedelta arithmetic operations.
@@ -2917,62 +2915,6 @@ def last_valid_index(self):
29172915
#----------------------------------------------------------------------
29182916
# Time series-oriented methods
29192917

2920-
def shift(self, periods=1, freq=None, copy=True, **kwds):
2921-
"""
2922-
Shift the index of the Series by desired number of periods with an
2923-
optional time offset
2924-
2925-
Parameters
2926-
----------
2927-
periods : int
2928-
Number of periods to move, can be positive or negative
2929-
freq : DateOffset, timedelta, or offset alias string, optional
2930-
Increment to use from datetools module or time rule (e.g. 'EOM')
2931-
2932-
Returns
2933-
-------
2934-
shifted : Series
2935-
"""
2936-
if periods == 0:
2937-
return self.copy()
2938-
2939-
offset = _resolve_offset(freq, kwds)
2940-
2941-
if isinstance(offset, compat.string_types):
2942-
offset = datetools.to_offset(offset)
2943-
2944-
def _get_values():
2945-
values = self.values
2946-
if copy:
2947-
values = values.copy()
2948-
return values
2949-
2950-
if offset is None:
2951-
dtype, fill_value = _maybe_promote(self.dtype)
2952-
new_values = pa.empty(len(self), dtype=dtype)
2953-
2954-
if periods > 0:
2955-
new_values[periods:] = self.values[:-periods]
2956-
new_values[:periods] = fill_value
2957-
elif periods < 0:
2958-
new_values[:periods] = self.values[-periods:]
2959-
new_values[periods:] = fill_value
2960-
2961-
return self._constructor(new_values, index=self.index, name=self.name)
2962-
elif isinstance(self.index, PeriodIndex):
2963-
orig_offset = datetools.to_offset(self.index.freq)
2964-
if orig_offset == offset:
2965-
return self._constructor(
2966-
_get_values(), self.index.shift(periods),
2967-
name=self.name)
2968-
msg = ('Given freq %s does not match PeriodIndex freq %s' %
2969-
(offset.rule_code, orig_offset.rule_code))
2970-
raise ValueError(msg)
2971-
else:
2972-
return self._constructor(_get_values(),
2973-
index=self.index.shift(periods, offset),
2974-
name=self.name)
2975-
29762918
def asof(self, where):
29772919
"""
29782920
Return last good (non-NaN) value in TimeSeries if value is NaN for
@@ -3317,26 +3259,6 @@ def _try_cast(arr, take_fast_path):
33173259

33183260
return subarr
33193261

3320-
def _resolve_offset(freq, kwds):
3321-
if 'timeRule' in kwds or 'offset' in kwds:
3322-
offset = kwds.get('offset', None)
3323-
offset = kwds.get('timeRule', offset)
3324-
if isinstance(offset, compat.string_types):
3325-
offset = datetools.getOffset(offset)
3326-
warn = True
3327-
else:
3328-
offset = freq
3329-
warn = False
3330-
3331-
if warn and _SHOW_WARNINGS: # pragma: no cover
3332-
import warnings
3333-
warnings.warn("'timeRule' and 'offset' parameters are deprecated,"
3334-
" please use 'freq' instead",
3335-
FutureWarning)
3336-
3337-
return offset
3338-
3339-
33403262
# backwards compatiblity
33413263
TimeSeries = Series
33423264

pandas/sparse/series.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -605,7 +605,7 @@ def shift(self, periods, freq=None, **kwds):
605605
"""
606606
Analogous to Series.shift
607607
"""
608-
from pandas.core.series import _resolve_offset
608+
from pandas.core.datetools import _resolve_offset
609609

610610
offset = _resolve_offset(freq, kwds)
611611

0 commit comments

Comments
 (0)