Skip to content

Commit 6e3c37c

Browse files
authored
DEPR fill_method and limit keywords in pct_change (#53520)
* DEPR fill_method and limit keywords in DataFrame/Series pct_change * DEPR fill_method and limit keywords in GroupBy pct_change * changelog added * DataFrame/Series pct_change only deprecate default * typo * GroupBy pct_change only deprecate default * changelod updated correspondingly * reverting * Revert "reverting" This reverts commit 2cb449b. * resolved conversation: also warn if nan and not specified * modify msgs since fillna method deprecated; tests updated * changelog updated * docstring use ffill instead of fillna
1 parent 462ee50 commit 6e3c37c

File tree

7 files changed

+192
-38
lines changed

7 files changed

+192
-38
lines changed

Diff for: doc/source/whatsnew/v2.1.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ Other API changes
227227
Deprecations
228228
~~~~~~~~~~~~
229229
- 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`)
230+
- 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 ``ffill`` or ``bfill`` before calling ``pct_change`` instead (:issue:`53491`)
230231
- 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`)
231232
- Deprecated 'quantile' keyword in :meth:`Rolling.quantile` and :meth:`Expanding.quantile`, renamed as 'q' instead (:issue:`52550`)
232233
- 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`)

Diff for: pandas/core/generic.py

+34-3
Original file line numberDiff line numberDiff line change
@@ -11219,8 +11219,8 @@ def describe(
1121911219
def pct_change(
1122011220
self,
1122111221
periods: int = 1,
11222-
fill_method: Literal["backfill", "bfill", "pad", "ffill"] | None = "pad",
11223-
limit: int | None = None,
11222+
fill_method: FillnaOptions | None | lib.NoDefault = lib.no_default,
11223+
limit: int | None | lib.NoDefault = lib.no_default,
1122411224
freq=None,
1122511225
**kwargs,
1122611226
) -> Self:
@@ -11244,8 +11244,14 @@ def pct_change(
1124411244
Periods to shift for forming percent change.
1124511245
fill_method : {'backfill', 'bfill', 'pad', 'ffill', None}, default 'pad'
1124611246
How to handle NAs **before** computing percent changes.
11247+
11248+
.. deprecated:: 2.1
11249+
1124711250
limit : int, default None
1124811251
The number of consecutive NAs to fill before stopping.
11252+
11253+
.. deprecated:: 2.1
11254+
1124911255
freq : DateOffset, timedelta, or str, optional
1125011256
Increment to use from time series API (e.g. 'M' or BDay()).
1125111257
**kwargs
@@ -11298,7 +11304,7 @@ def pct_change(
1129811304
3 85.0
1129911305
dtype: float64
1130011306
11301-
>>> s.pct_change(fill_method='ffill')
11307+
>>> s.ffill().pct_change()
1130211308
0 NaN
1130311309
1 0.011111
1130411310
2 0.000000
@@ -11345,6 +11351,31 @@ def pct_change(
1134511351
GOOG 0.179241 0.094112 NaN
1134611352
APPL -0.252395 -0.011860 NaN
1134711353
"""
11354+
# GH#53491
11355+
if fill_method is not lib.no_default or limit is not lib.no_default:
11356+
warnings.warn(
11357+
"The 'fill_method' and 'limit' keywords in "
11358+
f"{type(self).__name__}.pct_change are deprecated and will be "
11359+
"removed in a future version. Call "
11360+
f"{'bfill' if fill_method in ('backfill', 'bfill') else 'ffill'} "
11361+
"before calling pct_change instead.",
11362+
FutureWarning,
11363+
stacklevel=find_stack_level(),
11364+
)
11365+
if fill_method is lib.no_default:
11366+
if self.isna().values.any():
11367+
warnings.warn(
11368+
"The default fill_method='pad' in "
11369+
f"{type(self).__name__}.pct_change is deprecated and will be "
11370+
"removed in a future version. Call ffill before calling "
11371+
"pct_change to retain current behavior and silence this warning.",
11372+
FutureWarning,
11373+
stacklevel=find_stack_level(),
11374+
)
11375+
fill_method = "pad"
11376+
if limit is lib.no_default:
11377+
limit = None
11378+
1134811379
axis = self._get_axis_number(kwargs.pop("axis", "index"))
1134911380
if fill_method is None:
1135011381
data = self

Diff for: pandas/core/groupby/groupby.py

+26-2
Original file line numberDiff line numberDiff line change
@@ -4851,8 +4851,8 @@ def diff(
48514851
def pct_change(
48524852
self,
48534853
periods: int = 1,
4854-
fill_method: FillnaOptions = "ffill",
4855-
limit: int | None = None,
4854+
fill_method: FillnaOptions | lib.NoDefault = lib.no_default,
4855+
limit: int | None | lib.NoDefault = lib.no_default,
48564856
freq=None,
48574857
axis: Axis | lib.NoDefault = lib.no_default,
48584858
):
@@ -4902,6 +4902,30 @@ def pct_change(
49024902
catfish NaN NaN
49034903
goldfish 0.2 0.125
49044904
"""
4905+
# GH#53491
4906+
if fill_method is not lib.no_default or limit is not lib.no_default:
4907+
warnings.warn(
4908+
"The 'fill_method' and 'limit' keywords in "
4909+
f"{type(self).__name__}.pct_change are deprecated and will be "
4910+
"removed in a future version. Call "
4911+
f"{'bfill' if fill_method in ('backfill', 'bfill') else 'ffill'} "
4912+
"before calling pct_change instead.",
4913+
FutureWarning,
4914+
stacklevel=find_stack_level(),
4915+
)
4916+
if fill_method is lib.no_default:
4917+
if any(grp.isna().values.any() for _, grp in self):
4918+
warnings.warn(
4919+
"The default fill_method='ffill' in "
4920+
f"{type(self).__name__}.pct_change is deprecated and will be "
4921+
"removed in a future version. Call ffill before calling "
4922+
"pct_change to retain current behavior and silence this warning.",
4923+
FutureWarning,
4924+
stacklevel=find_stack_level(),
4925+
)
4926+
fill_method = "ffill"
4927+
if limit is lib.no_default:
4928+
limit = None
49054929

49064930
if axis is not lib.no_default:
49074931
axis = self.obj._get_axis_number(axis)

Diff for: pandas/tests/frame/methods/test_pct_change.py

+56-15
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
class TestDataFramePctChange:
1212
@pytest.mark.parametrize(
13-
"periods,fill_method,limit,exp",
13+
"periods, fill_method, limit, exp",
1414
[
1515
(1, "ffill", None, [np.nan, np.nan, np.nan, 1, 1, 1.5, 0, 0]),
1616
(1, "ffill", 1, [np.nan, np.nan, np.nan, 1, 1, 1.5, 0, np.nan]),
@@ -28,7 +28,12 @@ def test_pct_change_with_nas(
2828
vals = [np.nan, np.nan, 1, 2, 4, 10, np.nan, np.nan]
2929
obj = frame_or_series(vals)
3030

31-
res = obj.pct_change(periods=periods, fill_method=fill_method, limit=limit)
31+
msg = (
32+
"The 'fill_method' and 'limit' keywords in "
33+
f"{type(obj).__name__}.pct_change are deprecated"
34+
)
35+
with tm.assert_produces_warning(FutureWarning, match=msg):
36+
res = obj.pct_change(periods=periods, fill_method=fill_method, limit=limit)
3237
tm.assert_equal(res, frame_or_series(exp))
3338

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

48+
msg = (
49+
"The 'fill_method' and 'limit' keywords in "
50+
"DataFrame.pct_change are deprecated"
51+
)
52+
4353
for axis in range(2):
4454
expected = pnl.ffill(axis=axis) / pnl.ffill(axis=axis).shift(axis=axis) - 1
45-
result = pnl.pct_change(axis=axis, fill_method="pad")
4655

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

4960
def test_pct_change(self, datetime_frame):
50-
rs = datetime_frame.pct_change(fill_method=None)
61+
msg = (
62+
"The 'fill_method' and 'limit' keywords in "
63+
"DataFrame.pct_change are deprecated"
64+
)
65+
66+
with tm.assert_produces_warning(FutureWarning, match=msg):
67+
rs = datetime_frame.pct_change(fill_method=None)
5168
tm.assert_frame_equal(rs, datetime_frame / datetime_frame.shift(1) - 1)
5269

5370
rs = datetime_frame.pct_change(2)
5471
filled = datetime_frame.ffill()
5572
tm.assert_frame_equal(rs, filled / filled.shift(2) - 1)
5673

57-
rs = datetime_frame.pct_change(fill_method="bfill", limit=1)
74+
with tm.assert_produces_warning(FutureWarning, match=msg):
75+
rs = datetime_frame.pct_change(fill_method="bfill", limit=1)
5876
filled = datetime_frame.bfill(limit=1)
5977
tm.assert_frame_equal(rs, filled / filled.shift(1) - 1)
6078

@@ -69,7 +87,10 @@ def test_pct_change_shift_over_nas(self):
6987

7088
df = DataFrame({"a": s, "b": s})
7189

72-
chg = df.pct_change()
90+
msg = "The default fill_method='pad' in DataFrame.pct_change is deprecated"
91+
with tm.assert_produces_warning(FutureWarning, match=msg):
92+
chg = df.pct_change()
93+
7394
expected = Series([np.nan, 0.5, 0.0, 2.5 / 1.5 - 1, 0.2])
7495
edf = DataFrame({"a": expected, "b": expected})
7596
tm.assert_frame_equal(chg, edf)
@@ -88,18 +109,31 @@ def test_pct_change_shift_over_nas(self):
88109
def test_pct_change_periods_freq(
89110
self, datetime_frame, freq, periods, fill_method, limit
90111
):
91-
# GH#7292
92-
rs_freq = datetime_frame.pct_change(
93-
freq=freq, fill_method=fill_method, limit=limit
94-
)
95-
rs_periods = datetime_frame.pct_change(
96-
periods, fill_method=fill_method, limit=limit
112+
msg = (
113+
"The 'fill_method' and 'limit' keywords in "
114+
"DataFrame.pct_change are deprecated"
97115
)
116+
117+
# GH#7292
118+
with tm.assert_produces_warning(FutureWarning, match=msg):
119+
rs_freq = datetime_frame.pct_change(
120+
freq=freq, fill_method=fill_method, limit=limit
121+
)
122+
with tm.assert_produces_warning(FutureWarning, match=msg):
123+
rs_periods = datetime_frame.pct_change(
124+
periods, fill_method=fill_method, limit=limit
125+
)
98126
tm.assert_frame_equal(rs_freq, rs_periods)
99127

100128
empty_ts = DataFrame(index=datetime_frame.index, columns=datetime_frame.columns)
101-
rs_freq = empty_ts.pct_change(freq=freq, fill_method=fill_method, limit=limit)
102-
rs_periods = empty_ts.pct_change(periods, fill_method=fill_method, limit=limit)
129+
with tm.assert_produces_warning(FutureWarning, match=msg):
130+
rs_freq = empty_ts.pct_change(
131+
freq=freq, fill_method=fill_method, limit=limit
132+
)
133+
with tm.assert_produces_warning(FutureWarning, match=msg):
134+
rs_periods = empty_ts.pct_change(
135+
periods, fill_method=fill_method, limit=limit
136+
)
103137
tm.assert_frame_equal(rs_freq, rs_periods)
104138

105139

@@ -109,7 +143,14 @@ def test_pct_change_with_duplicated_indices(fill_method):
109143
data = DataFrame(
110144
{0: [np.nan, 1, 2, 3, 9, 18], 1: [0, 1, np.nan, 3, 9, 18]}, index=["a", "b"] * 3
111145
)
112-
result = data.pct_change(fill_method=fill_method)
146+
147+
msg = (
148+
"The 'fill_method' and 'limit' keywords in "
149+
"DataFrame.pct_change are deprecated"
150+
)
151+
with tm.assert_produces_warning(FutureWarning, match=msg):
152+
result = data.pct_change(fill_method=fill_method)
153+
113154
if fill_method is None:
114155
second_column = [np.nan, np.inf, np.nan, np.nan, 2.0, 1.0]
115156
else:

Diff for: pandas/tests/groupby/test_groupby_dropna.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -622,8 +622,15 @@ def test_categorical_transformers(
622622
"x", dropna=False, observed=observed, sort=sort, as_index=as_index
623623
)
624624
gb_dropna = df.groupby("x", dropna=True, observed=observed, sort=sort)
625-
result = getattr(gb_keepna, transformation_func)(*args)
625+
626+
msg = "The default fill_method='ffill' in DataFrameGroupBy.pct_change is deprecated"
627+
if transformation_func == "pct_change":
628+
with tm.assert_produces_warning(FutureWarning, match=msg):
629+
result = getattr(gb_keepna, "pct_change")(*args)
630+
else:
631+
result = getattr(gb_keepna, transformation_func)(*args)
626632
expected = getattr(gb_dropna, transformation_func)(*args)
633+
627634
for iloc, value in zip(
628635
df[df["x"].isnull()].index.tolist(), null_group_result.values.ravel()
629636
):

Diff for: pandas/tests/groupby/transform/test_transform.py

+28-5
Original file line numberDiff line numberDiff line change
@@ -408,11 +408,24 @@ def mock_op(x):
408408
test_op = lambda x: x.transform(transformation_func)
409409
mock_op = lambda x: getattr(x, transformation_func)()
410410

411-
result = test_op(df.groupby("A"))
411+
msg = "The default fill_method='pad' in DataFrame.pct_change is deprecated"
412+
groupby_msg = (
413+
"The default fill_method='ffill' in DataFrameGroupBy.pct_change is deprecated"
414+
)
415+
if transformation_func == "pct_change":
416+
with tm.assert_produces_warning(FutureWarning, match=groupby_msg):
417+
result = test_op(df.groupby("A"))
418+
else:
419+
result = test_op(df.groupby("A"))
420+
412421
# pass the group in same order as iterating `for ... in df.groupby(...)`
413422
# but reorder to match df's index since this is a transform
414423
groups = [df[["B"]].iloc[4:6], df[["B"]].iloc[6:], df[["B"]].iloc[:4]]
415-
expected = concat([mock_op(g) for g in groups]).sort_index()
424+
if transformation_func == "pct_change":
425+
with tm.assert_produces_warning(FutureWarning, match=msg):
426+
expected = concat([mock_op(g) for g in groups]).sort_index()
427+
else:
428+
expected = concat([mock_op(g) for g in groups]).sort_index()
416429
# sort_index does not preserve the freq
417430
expected = expected.set_axis(df.index)
418431

@@ -973,9 +986,14 @@ def test_pct_change(frame_or_series, freq, periods, fill_method, limit):
973986
else:
974987
expected = expected.to_frame("vals")
975988

976-
result = gb.pct_change(
977-
periods=periods, fill_method=fill_method, limit=limit, freq=freq
989+
msg = (
990+
"The 'fill_method' and 'limit' keywords in "
991+
f"{type(gb).__name__}.pct_change are deprecated"
978992
)
993+
with tm.assert_produces_warning(FutureWarning, match=msg):
994+
result = gb.pct_change(
995+
periods=periods, fill_method=fill_method, limit=limit, freq=freq
996+
)
979997
tm.assert_equal(result, expected)
980998

981999

@@ -1412,7 +1430,12 @@ def test_null_group_str_transformer(request, dropna, transformation_func):
14121430
# ngroup/cumcount always returns a Series as it counts the groups, not values
14131431
expected = expected["B"].rename(None)
14141432

1415-
result = gb.transform(transformation_func, *args)
1433+
msg = "The default fill_method='ffill' in DataFrameGroupBy.pct_change is deprecated"
1434+
if transformation_func == "pct_change" and not dropna:
1435+
with tm.assert_produces_warning(FutureWarning, match=msg):
1436+
result = gb.transform("pct_change", *args)
1437+
else:
1438+
result = gb.transform(transformation_func, *args)
14161439

14171440
tm.assert_equal(result, expected)
14181441

0 commit comments

Comments
 (0)