Skip to content

DEPR fill_method and limit keywords in pct_change #53520

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Jun 9, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/source/whatsnew/v2.1.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ Other API changes
Deprecations
~~~~~~~~~~~~
- Deprecated 'broadcast_axis' keyword in :meth:`Series.align` and :meth:`DataFrame.align`, upcast before calling ``align`` with ``left = DataFrame({col: left for col in right.columns}, index=right.index)`` (:issue:`51856`)
- Deprecated 'fill_method' and 'limit' keywords in :meth:`DataFrame.pct_change`, :meth:`Series.pct_change`, :meth:`DataFrameGroupBy.pct_change`, and :meth:`SeriesGroupBy.pct_change`, explicitly call ``fillna`` before calling ``pct_change`` instead (:issue:`53491`)
- Deprecated 'method', 'limit', and 'fill_axis' keywords in :meth:`DataFrame.align` and :meth:`Series.align`, explicitly call ``fillna`` on the alignment results instead (:issue:`51856`)
- Deprecated 'quantile' keyword in :meth:`Rolling.quantile` and :meth:`Expanding.quantile`, renamed as 'q' instead (:issue:`52550`)
- Deprecated :meth:`.DataFrameGroupBy.apply` and methods on the objects returned by :meth:`.DataFrameGroupBy.resample` operating on the grouping column(s); select the columns to operate on after groupby to either explicitly include or exclude the groupings and avoid the ``FutureWarning`` (:issue:`7155`)
Expand Down
27 changes: 24 additions & 3 deletions pandas/core/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -11143,8 +11143,8 @@ def describe(
def pct_change(
self,
periods: int = 1,
fill_method: Literal["backfill", "bfill", "pad", "ffill"] | None = "pad",
limit: int | None = None,
fill_method: FillnaOptions | None | lib.NoDefault = lib.no_default,
limit: int | None | lib.NoDefault = lib.no_default,
freq=None,
**kwargs,
) -> Self:
Expand All @@ -11168,8 +11168,14 @@ def pct_change(
Periods to shift for forming percent change.
fill_method : {'backfill', 'bfill', 'pad', 'ffill', None}, default 'pad'
How to handle NAs **before** computing percent changes.

.. deprecated:: 2.1

limit : int, default None
The number of consecutive NAs to fill before stopping.

.. deprecated:: 2.1

freq : DateOffset, timedelta, or str, optional
Increment to use from time series API (e.g. 'M' or BDay()).
**kwargs
Expand Down Expand Up @@ -11222,7 +11228,7 @@ def pct_change(
3 85.0
dtype: float64

>>> s.pct_change(fill_method='ffill')
>>> s.fillna(method='ffill').pct_change()
0 NaN
1 0.011111
2 0.000000
Expand Down Expand Up @@ -11269,6 +11275,21 @@ def pct_change(
GOOG 0.179241 0.094112 NaN
APPL -0.252395 -0.011860 NaN
"""
if fill_method is not lib.no_default or limit is not lib.no_default:
# GH#53491
warnings.warn(
"The 'fill_method' and 'limit' keywords in "
f"{type(self).__name__}.pct_change are deprecated and will be "
"removed in a future version. Call fillna directly before "
"calling pct_change instead.",
FutureWarning,
stacklevel=find_stack_level(),
)
if fill_method is lib.no_default:
fill_method = "pad"
if limit is lib.no_default:
limit = None

axis = self._get_axis_number(kwargs.pop("axis", "index"))
if fill_method is None:
data = self
Expand Down
19 changes: 17 additions & 2 deletions pandas/core/groupby/groupby.py
Original file line number Diff line number Diff line change
Expand Up @@ -3945,8 +3945,8 @@ def diff(
def pct_change(
self,
periods: int = 1,
fill_method: FillnaOptions = "ffill",
limit: int | None = None,
fill_method: FillnaOptions | lib.NoDefault = lib.no_default,
limit: int | None | lib.NoDefault = lib.no_default,
freq=None,
axis: Axis | lib.NoDefault = lib.no_default,
):
Expand All @@ -3958,6 +3958,21 @@ def pct_change(
Series or DataFrame
Percentage changes within each group.
"""
if fill_method is not lib.no_default or limit is not lib.no_default:
# GH#53491
warnings.warn(
"The 'fill_method' and 'limit' keywords in "
f"{type(self).__name__}.pct_change are deprecated and will be "
"removed in a future version. Call fillna directly before "
"calling pct_change instead.",
FutureWarning,
stacklevel=find_stack_level(),
)
if fill_method is lib.no_default:
fill_method = "ffill"
if limit is lib.no_default:
limit = None

if axis is not lib.no_default:
axis = self.obj._get_axis_number(axis)
self._deprecate_axis(axis, "pct_change")
Expand Down
66 changes: 52 additions & 14 deletions pandas/tests/frame/methods/test_pct_change.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

class TestDataFramePctChange:
@pytest.mark.parametrize(
"periods,fill_method,limit,exp",
"periods, fill_method, limit, exp",
[
(1, "ffill", None, [np.nan, np.nan, np.nan, 1, 1, 1.5, 0, 0]),
(1, "ffill", 1, [np.nan, np.nan, np.nan, 1, 1, 1.5, 0, np.nan]),
Expand All @@ -28,7 +28,12 @@ def test_pct_change_with_nas(
vals = [np.nan, np.nan, 1, 2, 4, 10, np.nan, np.nan]
obj = frame_or_series(vals)

res = obj.pct_change(periods=periods, fill_method=fill_method, limit=limit)
msg = (
"The 'fill_method' and 'limit' keywords in "
f"{type(obj).__name__}.pct_change are deprecated"
)
with tm.assert_produces_warning(FutureWarning, match=msg):
res = obj.pct_change(periods=periods, fill_method=fill_method, limit=limit)
tm.assert_equal(res, frame_or_series(exp))

def test_pct_change_numeric(self):
Expand All @@ -40,21 +45,34 @@ def test_pct_change_numeric(self):
pnl.iat[1, 1] = np.nan
pnl.iat[2, 3] = 60

msg = (
"The 'fill_method' and 'limit' keywords in "
"DataFrame.pct_change are deprecated"
)

for axis in range(2):
expected = pnl.ffill(axis=axis) / pnl.ffill(axis=axis).shift(axis=axis) - 1
result = pnl.pct_change(axis=axis, fill_method="pad")

with tm.assert_produces_warning(FutureWarning, match=msg):
result = pnl.pct_change(axis=axis, fill_method="pad")
tm.assert_frame_equal(result, expected)

def test_pct_change(self, datetime_frame):
rs = datetime_frame.pct_change(fill_method=None)
msg = (
"The 'fill_method' and 'limit' keywords in "
"DataFrame.pct_change are deprecated"
)

with tm.assert_produces_warning(FutureWarning, match=msg):
rs = datetime_frame.pct_change(fill_method=None)
tm.assert_frame_equal(rs, datetime_frame / datetime_frame.shift(1) - 1)

rs = datetime_frame.pct_change(2)
filled = datetime_frame.fillna(method="pad")
tm.assert_frame_equal(rs, filled / filled.shift(2) - 1)

rs = datetime_frame.pct_change(fill_method="bfill", limit=1)
with tm.assert_produces_warning(FutureWarning, match=msg):
rs = datetime_frame.pct_change(fill_method="bfill", limit=1)
filled = datetime_frame.fillna(method="bfill", limit=1)
tm.assert_frame_equal(rs, filled / filled.shift(1) - 1)

Expand Down Expand Up @@ -88,18 +106,31 @@ def test_pct_change_shift_over_nas(self):
def test_pct_change_periods_freq(
self, datetime_frame, freq, periods, fill_method, limit
):
# GH#7292
rs_freq = datetime_frame.pct_change(
freq=freq, fill_method=fill_method, limit=limit
)
rs_periods = datetime_frame.pct_change(
periods, fill_method=fill_method, limit=limit
msg = (
"The 'fill_method' and 'limit' keywords in "
"DataFrame.pct_change are deprecated"
)

# GH#7292
with tm.assert_produces_warning(FutureWarning, match=msg):
rs_freq = datetime_frame.pct_change(
freq=freq, fill_method=fill_method, limit=limit
)
with tm.assert_produces_warning(FutureWarning, match=msg):
rs_periods = datetime_frame.pct_change(
periods, fill_method=fill_method, limit=limit
)
tm.assert_frame_equal(rs_freq, rs_periods)

empty_ts = DataFrame(index=datetime_frame.index, columns=datetime_frame.columns)
rs_freq = empty_ts.pct_change(freq=freq, fill_method=fill_method, limit=limit)
rs_periods = empty_ts.pct_change(periods, fill_method=fill_method, limit=limit)
with tm.assert_produces_warning(FutureWarning, match=msg):
rs_freq = empty_ts.pct_change(
freq=freq, fill_method=fill_method, limit=limit
)
with tm.assert_produces_warning(FutureWarning, match=msg):
rs_periods = empty_ts.pct_change(
periods, fill_method=fill_method, limit=limit
)
tm.assert_frame_equal(rs_freq, rs_periods)


Expand All @@ -109,7 +140,14 @@ def test_pct_change_with_duplicated_indices(fill_method):
data = DataFrame(
{0: [np.nan, 1, 2, 3, 9, 18], 1: [0, 1, np.nan, 3, 9, 18]}, index=["a", "b"] * 3
)
result = data.pct_change(fill_method=fill_method)

msg = (
"The 'fill_method' and 'limit' keywords in "
"DataFrame.pct_change are deprecated"
)
with tm.assert_produces_warning(FutureWarning, match=msg):
result = data.pct_change(fill_method=fill_method)

if fill_method is None:
second_column = [np.nan, np.inf, np.nan, np.nan, 2.0, 1.0]
else:
Expand Down
9 changes: 7 additions & 2 deletions pandas/tests/groupby/transform/test_transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -969,9 +969,14 @@ def test_pct_change(frame_or_series, freq, periods, fill_method, limit):
else:
expected = expected.to_frame("vals")

result = gb.pct_change(
periods=periods, fill_method=fill_method, limit=limit, freq=freq
msg = (
"The 'fill_method' and 'limit' keywords in "
f"{type(gb).__name__}.pct_change are deprecated"
)
with tm.assert_produces_warning(FutureWarning, match=msg):
result = gb.pct_change(
periods=periods, fill_method=fill_method, limit=limit, freq=freq
)
tm.assert_equal(result, expected)


Expand Down
46 changes: 35 additions & 11 deletions pandas/tests/series/methods/test_pct_change.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,21 @@

class TestSeriesPctChange:
def test_pct_change(self, datetime_series):
rs = datetime_series.pct_change(fill_method=None)
msg = (
"The 'fill_method' and 'limit' keywords in "
"Series.pct_change are deprecated"
)

with tm.assert_produces_warning(FutureWarning, match=msg):
rs = datetime_series.pct_change(fill_method=None)
tm.assert_series_equal(rs, datetime_series / datetime_series.shift(1) - 1)

rs = datetime_series.pct_change(2)
filled = datetime_series.fillna(method="pad")
tm.assert_series_equal(rs, filled / filled.shift(2) - 1)

rs = datetime_series.pct_change(fill_method="bfill", limit=1)
with tm.assert_produces_warning(FutureWarning, match=msg):
rs = datetime_series.pct_change(fill_method="bfill", limit=1)
filled = datetime_series.fillna(method="bfill", limit=1)
tm.assert_series_equal(rs, filled / filled.shift(1) - 1)

Expand Down Expand Up @@ -58,25 +65,42 @@ def test_pct_change_shift_over_nas(self):
def test_pct_change_periods_freq(
self, freq, periods, fill_method, limit, datetime_series
):
# GH#7292
rs_freq = datetime_series.pct_change(
freq=freq, fill_method=fill_method, limit=limit
)
rs_periods = datetime_series.pct_change(
periods, fill_method=fill_method, limit=limit
msg = (
"The 'fill_method' and 'limit' keywords in "
"Series.pct_change are deprecated"
)

# GH#7292
with tm.assert_produces_warning(FutureWarning, match=msg):
rs_freq = datetime_series.pct_change(
freq=freq, fill_method=fill_method, limit=limit
)
with tm.assert_produces_warning(FutureWarning, match=msg):
rs_periods = datetime_series.pct_change(
periods, fill_method=fill_method, limit=limit
)
tm.assert_series_equal(rs_freq, rs_periods)

empty_ts = Series(index=datetime_series.index, dtype=object)
rs_freq = empty_ts.pct_change(freq=freq, fill_method=fill_method, limit=limit)
rs_periods = empty_ts.pct_change(periods, fill_method=fill_method, limit=limit)
with tm.assert_produces_warning(FutureWarning, match=msg):
rs_freq = empty_ts.pct_change(
freq=freq, fill_method=fill_method, limit=limit
)
with tm.assert_produces_warning(FutureWarning, match=msg):
rs_periods = empty_ts.pct_change(
periods, fill_method=fill_method, limit=limit
)
tm.assert_series_equal(rs_freq, rs_periods)


@pytest.mark.parametrize("fill_method", ["pad", "ffill", None])
def test_pct_change_with_duplicated_indices(fill_method):
# GH30463
s = Series([np.nan, 1, 2, 3, 9, 18], index=["a", "b"] * 3)
result = s.pct_change(fill_method=fill_method)

msg = "The 'fill_method' and 'limit' keywords in Series.pct_change are deprecated"
with tm.assert_produces_warning(FutureWarning, match=msg):
result = s.pct_change(fill_method=fill_method)

expected = Series([np.nan, np.nan, 1.0, 0.5, 2.0, 1.0], index=["a", "b"] * 3)
tm.assert_series_equal(result, expected)