diff --git a/doc/source/computation.rst b/doc/source/computation.rst index fb616c5267e3c..ebda0cde9fb5c 100644 --- a/doc/source/computation.rst +++ b/doc/source/computation.rst @@ -26,9 +26,9 @@ Statistical functions Percent Change ~~~~~~~~~~~~~~ -Both ``Series`` and ``DataFrame`` has a method ``pct_change`` to compute the +``Series``, ``DataFrame``, and ``Panel`` all have a method ``pct_change`` to compute the percent change over a given number of periods (using ``fill_method`` to fill -NA/null values). +NA/null values *before* computing the percent change). .. ipython:: python diff --git a/doc/source/release.rst b/doc/source/release.rst index 857a4d237f423..12dc1fec5f969 100644 --- a/doc/source/release.rst +++ b/doc/source/release.rst @@ -63,7 +63,7 @@ New features noon, January 1, 4713 BC. Because nanoseconds are used to define the time in pandas the actual range of dates that you can use is 1678 AD to 2262 AD. (:issue:`4041`) - Added error bar support to the ``.plot`` method of ``DataFrame`` and ``Series`` (:issue:`3796`) - +- Implemented ``Panel.pct_change`` (:issue:`6904`) API Changes ~~~~~~~~~~~ diff --git a/doc/source/v0.14.0.txt b/doc/source/v0.14.0.txt index 11296a43e230d..187a757f53d45 100644 --- a/doc/source/v0.14.0.txt +++ b/doc/source/v0.14.0.txt @@ -470,6 +470,7 @@ Enhancements - :ref:`Holidays Calendars` are now available and can be used with CustomBusinessDay (:issue:`6719`) - ``Float64Index`` is now backed by a ``float64`` dtype ndarray instead of an ``object`` dtype array (:issue:`6471`). +- Implemented ``Panel.pct_change`` (:issue:`6904`) Performance ~~~~~~~~~~~ diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 7e5e125034189..ebcdc600f6751 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -3455,10 +3455,8 @@ def _convert_timedeltas(x): return np.abs(self) - def pct_change(self, periods=1, fill_method='pad', limit=None, freq=None, - **kwds): - """ - Percent change over given number of periods + _shared_docs['pct_change'] = """ + Percent change over given number of periods. Parameters ---------- @@ -3473,14 +3471,27 @@ def pct_change(self, periods=1, fill_method='pad', limit=None, freq=None, Returns ------- - chg : same type as caller + chg : %(klass)s + + Notes + ----- + + By default, the percentage change is calculated along the stat + axis: 0, or ``Index``, for ``DataFrame`` and 1, or ``minor`` for + ``Panel``. You can change this with the ``axis`` keyword argument. """ + + @Appender(_shared_docs['pct_change'] % _shared_doc_kwargs) + def pct_change(self, periods=1, fill_method='pad', limit=None, freq=None, + **kwds): # TODO: Not sure if above is correct - need someone to confirm. + axis = self._get_axis_number(kwds.pop('axis', self._stat_axis_name)) if fill_method is None: data = self else: data = self.fillna(method=fill_method, limit=limit) - rs = data / data.shift(periods=periods, freq=freq, **kwds) - 1 + + rs = data.div(data.shift(periods, freq=freq, axis=axis, **kwds)) - 1 if freq is None: mask = com.isnull(_values_from_object(self)) np.putmask(rs.values, mask, np.nan) diff --git a/pandas/core/panel.py b/pandas/core/panel.py index eeb0e292c01d4..f1c52a8facc0a 100644 --- a/pandas/core/panel.py +++ b/pandas/core/panel.py @@ -1116,7 +1116,7 @@ def count(self, axis='major'): def shift(self, lags, freq=None, axis='major'): """ - Shift major or minor axis by specified number of leads/lags. + Shift major or minor axis by specified number of leads/lags. Parameters ---------- diff --git a/pandas/tests/test_panel.py b/pandas/tests/test_panel.py index 198e600e8edc7..176ef13d23d94 100644 --- a/pandas/tests/test_panel.py +++ b/pandas/tests/test_panel.py @@ -1684,6 +1684,60 @@ def test_tshift(self): no_freq = panel.ix[:, [0, 5, 7], :] self.assertRaises(ValueError, no_freq.tshift) + def test_pct_change(self): + df1 = DataFrame({'c1': [1, 2, 5], 'c2': [3, 4, 6]}) + df2 = df1 + 1 + df3 = DataFrame({'c1': [3, 4, 7], 'c2': [5, 6, 8]}) + wp = Panel({'i1': df1, 'i2': df2, 'i3': df3}) + # major, 1 + result = wp.pct_change() # axis='major' + expected = Panel({'i1': df1.pct_change(), + 'i2': df2.pct_change(), + 'i3': df3.pct_change()}) + assert_panel_equal(result, expected) + result = wp.pct_change(axis=1) + assert_panel_equal(result, expected) + # major, 2 + result = wp.pct_change(periods=2) + expected = Panel({'i1': df1.pct_change(2), + 'i2': df2.pct_change(2), + 'i3': df3.pct_change(2)}) + assert_panel_equal(result, expected) + # minor, 1 + result = wp.pct_change(axis='minor') + expected = Panel({'i1': df1.pct_change(axis=1), + 'i2': df2.pct_change(axis=1), + 'i3': df3.pct_change(axis=1)}) + assert_panel_equal(result, expected) + result = wp.pct_change(axis=2) + assert_panel_equal(result, expected) + # minor, 2 + result = wp.pct_change(periods=2, axis='minor') + expected = Panel({'i1': df1.pct_change(periods=2, axis=1), + 'i2': df2.pct_change(periods=2, axis=1), + 'i3': df3.pct_change(periods=2, axis=1)}) + assert_panel_equal(result, expected) + # items, 1 + result = wp.pct_change(axis='items') + expected = Panel({'i1': DataFrame({'c1': [np.nan, np.nan, np.nan], + 'c2': [np.nan, np.nan, np.nan]}), + 'i2': DataFrame({'c1': [1, 0.5, .2], + 'c2': [1./3, 0.25, 1./6]}), + 'i3': DataFrame({'c1': [.5, 1./3, 1./6], + 'c2': [.25, .2, 1./7]})}) + assert_panel_equal(result, expected) + result = wp.pct_change(axis=0) + assert_panel_equal(result, expected) + # items, 2 + result = wp.pct_change(periods=2, axis='items') + expected = Panel({'i1': DataFrame({'c1': [np.nan, np.nan, np.nan], + 'c2': [np.nan, np.nan, np.nan]}), + 'i2': DataFrame({'c1': [np.nan, np.nan, np.nan], + 'c2': [np.nan, np.nan, np.nan]}), + 'i3': DataFrame({'c1': [2, 1, .4], + 'c2': [2./3, .5, 1./3]})}) + assert_panel_equal(result, expected) + def test_multiindex_get(self): ind = MultiIndex.from_tuples([('a', 1), ('a', 2), ('b', 1), ('b', 2)], names=['first', 'second']) diff --git a/vb_suite/panel_methods.py b/vb_suite/panel_methods.py index 6710be760e2df..45790feb4a58e 100644 --- a/vb_suite/panel_methods.py +++ b/vb_suite/panel_methods.py @@ -17,3 +17,12 @@ panel_shift_minor = Benchmark('panel.shift(1, axis=minor)', setup, start_date=datetime(2012, 1, 12)) + +panel_pct_change_major = Benchmark('panel.pct_change(1, axis="major")', setup, + start_date=datetime(2014, 4, 19)) + +panel_pct_change_minor = Benchmark('panel.pct_change(1, axis="minor")', setup, + start_date=datetime(2014, 4, 19)) + +panel_pct_change_items = Benchmark('panel.pct_change(1, axis="items")', setup, + start_date=datetime(2014, 4, 19))