Skip to content

Commit 3085f9f

Browse files
DEPR: remove the Period resampling deprecation (#62480)
1 parent e4ca405 commit 3085f9f

File tree

9 files changed

+93
-191
lines changed

9 files changed

+93
-191
lines changed

doc/source/whatsnew/v0.21.0.rst

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -635,22 +635,17 @@ Previous behavior:
635635
636636
New behavior:
637637

638-
.. code-block:: ipython
638+
.. ipython:: python
639639
640-
In [1]: pi = pd.period_range('2017-01', periods=12, freq='M')
640+
pi = pd.period_range('2017-01', periods=12, freq='M')
641641
642-
In [2]: s = pd.Series(np.arange(12), index=pi)
642+
s = pd.Series(np.arange(12), index=pi)
643643
644-
In [3]: resampled = s.resample('2Q').mean()
644+
resampled = s.resample('2Q').mean()
645645
646-
In [4]: resampled
647-
Out[4]:
648-
2017Q1 2.5
649-
2017Q3 8.5
650-
Freq: 2Q-DEC, dtype: float64
646+
resampled
651647
652-
In [5]: resampled.index
653-
Out[5]: PeriodIndex(['2017Q1', '2017Q3'], dtype='period[2Q-DEC]')
648+
resampled.index
654649
655650
Upsampling and calling ``.ohlc()`` previously returned a ``Series``, basically identical to calling ``.asfreq()``. OHLC upsampling now returns a DataFrame with columns ``open``, ``high``, ``low`` and ``close`` (:issue:`13083`). This is consistent with downsampling and ``DatetimeIndex`` behavior.
656651

doc/source/whatsnew/v2.2.0.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -664,7 +664,7 @@ Other Deprecations
664664
- Deprecated :meth:`DatetimeArray.__init__` and :meth:`TimedeltaArray.__init__`, use :func:`array` instead (:issue:`55623`)
665665
- Deprecated :meth:`Index.format`, use ``index.astype(str)`` or ``index.map(formatter)`` instead (:issue:`55413`)
666666
- Deprecated :meth:`Series.ravel`, the underlying array is already 1D, so ravel is not necessary (:issue:`52511`)
667-
- Deprecated :meth:`Series.resample` and :meth:`DataFrame.resample` with a :class:`PeriodIndex` (and the 'convention' keyword), convert to :class:`DatetimeIndex` (with ``.to_timestamp()``) before resampling instead (:issue:`53481`)
667+
- Deprecated :meth:`Series.resample` and :meth:`DataFrame.resample` with a :class:`PeriodIndex` (and the 'convention' keyword), convert to :class:`DatetimeIndex` (with ``.to_timestamp()``) before resampling instead (:issue:`53481`). Note: this deprecation was later undone in pandas 2.3.3 (:issue:`57033`)
668668
- Deprecated :meth:`Series.view`, use :meth:`Series.astype` instead to change the dtype (:issue:`20251`)
669669
- Deprecated :meth:`offsets.Tick.is_anchored`, use ``False`` instead (:issue:`55388`)
670670
- Deprecated ``core.internals`` members ``Block``, ``ExtensionBlock``, and ``DatetimeTZBlock``, use public APIs instead (:issue:`55139`)

doc/source/whatsnew/v2.3.3.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,14 @@ Bug fixes
5757

5858
- The :meth:`DataFrame.iloc` now works correctly with ``copy_on_write`` option when assigning values after subsetting the columns of a homogeneous DataFrame (:issue:`60309`)
5959

60+
Other changes
61+
~~~~~~~~~~~~~
62+
63+
- The deprecation of using :meth:`Series.resample` and :meth:`DataFrame.resample`
64+
with a :class:`PeriodIndex` (and the 'convention' keyword) has been undone.
65+
Resampling with a :class:`PeriodIndex` is supported again, but a subset of
66+
methods that return incorrect results will raise an error in pandas 3.0 (:issue:`57033`)
67+
6068

6169
.. ---------------------------------------------------------------------------
6270
.. _whatsnew_233.contributors:

pandas/core/generic.py

Lines changed: 50 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8811,7 +8811,7 @@ def resample(
88118811
rule,
88128812
closed: Literal["right", "left"] | None = None,
88138813
label: Literal["right", "left"] | None = None,
8814-
convention: Literal["start", "end", "s", "e"] | lib.NoDefault = lib.no_default,
8814+
convention: Literal["start", "end", "s", "e"] = "start",
88158815
on: Level | None = None,
88168816
level: Level | None = None,
88178817
origin: str | TimestampConvertibleTypes = "start_day",
@@ -8841,9 +8841,6 @@ def resample(
88418841
convention : {{'start', 'end', 's', 'e'}}, default 'start'
88428842
For `PeriodIndex` only, controls whether to use the start or
88438843
end of `rule`.
8844-
8845-
.. deprecated:: 2.2.0
8846-
Convert PeriodIndex to DatetimeIndex before resampling instead.
88478844
on : str, optional
88488845
For a DataFrame, column to use instead of index for resampling.
88498846
Column must be datetime-like.
@@ -8999,6 +8996,55 @@ def resample(
89998996
2000-01-01 00:06:00 26
90008997
Freq: 3min, dtype: int64
90018998
8999+
For a Series with a PeriodIndex, the keyword `convention` can be
9000+
used to control whether to use the start or end of `rule`.
9001+
9002+
Resample a year by quarter using 'start' `convention`. Values are
9003+
assigned to the first quarter of the period.
9004+
9005+
>>> s = pd.Series(
9006+
... [1, 2], index=pd.period_range("2012-01-01", freq="Y", periods=2)
9007+
... )
9008+
>>> s
9009+
2012 1
9010+
2013 2
9011+
Freq: Y-DEC, dtype: int64
9012+
>>> s.resample("Q", convention="start").asfreq()
9013+
2012Q1 1.0
9014+
2012Q2 NaN
9015+
2012Q3 NaN
9016+
2012Q4 NaN
9017+
2013Q1 2.0
9018+
2013Q2 NaN
9019+
2013Q3 NaN
9020+
2013Q4 NaN
9021+
Freq: Q-DEC, dtype: float64
9022+
9023+
Resample quarters by month using 'end' `convention`. Values are
9024+
assigned to the last month of the period.
9025+
9026+
>>> q = pd.Series(
9027+
... [1, 2, 3, 4], index=pd.period_range("2018-01-01", freq="Q", periods=4)
9028+
... )
9029+
>>> q
9030+
2018Q1 1
9031+
2018Q2 2
9032+
2018Q3 3
9033+
2018Q4 4
9034+
Freq: Q-DEC, dtype: int64
9035+
>>> q.resample("M", convention="end").asfreq()
9036+
2018-03 1.0
9037+
2018-04 NaN
9038+
2018-05 NaN
9039+
2018-06 2.0
9040+
2018-07 NaN
9041+
2018-08 NaN
9042+
2018-09 3.0
9043+
2018-10 NaN
9044+
2018-11 NaN
9045+
2018-12 4.0
9046+
Freq: M, dtype: float64
9047+
90029048
For DataFrame objects, the keyword `on` can be used to specify the
90039049
column instead of the index for resampling.
90049050
@@ -9135,19 +9181,6 @@ def resample(
91359181
"""
91369182
from pandas.core.resample import get_resampler
91379183

9138-
if convention is not lib.no_default:
9139-
# TODO: Enforce in 3.0 (#55968)
9140-
warnings.warn(
9141-
f"The 'convention' keyword in {type(self).__name__}.resample is "
9142-
"deprecated and will be removed in a future version. "
9143-
"Explicitly cast PeriodIndex to DatetimeIndex before resampling "
9144-
"instead.",
9145-
FutureWarning, # pdlint: ignore[warning_class]
9146-
stacklevel=find_stack_level(),
9147-
)
9148-
else:
9149-
convention = "start"
9150-
91519184
return get_resampler(
91529185
cast("Series | DataFrame", self),
91539186
freq=rule,

pandas/core/resample.py

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1932,13 +1932,6 @@ class PeriodIndexResampler(DatetimeIndexResampler):
19321932

19331933
@property
19341934
def _resampler_for_grouping(self):
1935-
# TODO: Enforce in 3.0 (#55968)
1936-
warnings.warn(
1937-
"Resampling a groupby with a PeriodIndex is deprecated. "
1938-
"Cast to DatetimeIndex before resampling instead.",
1939-
FutureWarning, # pdlint: ignore[warning_class]
1940-
stacklevel=find_stack_level(),
1941-
)
19421935
return PeriodIndexResamplerGroupby
19431936

19441937
def _get_binner_for_time(self):
@@ -2265,15 +2258,6 @@ def _get_resampler(self, obj: NDFrame) -> Resampler:
22652258
gpr_index=ax,
22662259
)
22672260
elif isinstance(ax, PeriodIndex):
2268-
if isinstance(ax, PeriodIndex):
2269-
# TODO: Enforce in 3.0 (#53481)
2270-
# GH#53481
2271-
warnings.warn(
2272-
"Resampling with a PeriodIndex is deprecated. "
2273-
"Cast index to DatetimeIndex before resampling instead.",
2274-
FutureWarning, # pdlint: ignore[warning_class]
2275-
stacklevel=find_stack_level(),
2276-
)
22772261
return PeriodIndexResampler(
22782262
obj,
22792263
timegrouper=self,

pandas/plotting/_matplotlib/timeseries.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,9 @@ def maybe_resample(series: Series, ax: Axes, kwargs: dict[str, Any]):
8282
)
8383
freq = ax_freq
8484
elif _is_sup(freq, ax_freq): # one is weekly
85-
# Resampling with PeriodDtype is deprecated, so we convert to
86-
# DatetimeIndex, resample, then convert back.
87-
ser_ts = series.to_timestamp()
88-
ser_d = ser_ts.resample("D").last().dropna()
89-
ser_freq = ser_d.resample(ax_freq).last().dropna()
90-
series = ser_freq.to_period(ax_freq)
85+
how = "last"
86+
series = getattr(series.resample("D"), how)().dropna()
87+
series = getattr(series.resample(ax_freq), how)().dropna()
9188
freq = ax_freq
9289
elif is_subperiod(freq, ax_freq) or _is_sub(freq, ax_freq):
9390
_upsample_others(ax, freq, kwargs)

pandas/tests/resample/test_base.py

Lines changed: 15 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,8 @@ def test_asfreq_fill_value(index):
104104
def test_resample_interpolate(index):
105105
# GH#12925
106106
df = DataFrame(range(len(index)), index=index)
107-
warn = None
108-
if isinstance(df.index, PeriodIndex):
109-
warn = FutureWarning
110-
msg = "Resampling with a PeriodIndex is deprecated"
111-
with tm.assert_produces_warning(warn, match=msg):
112-
result = df.resample("1min").asfreq().interpolate()
113-
expected = df.resample("1min").interpolate()
107+
result = df.resample("1min").asfreq().interpolate()
108+
expected = df.resample("1min").interpolate()
114109
tm.assert_frame_equal(result, expected)
115110

116111

@@ -199,13 +194,7 @@ def test_resample_empty_series(freq, index, resample_method):
199194
elif freq == "ME" and isinstance(ser.index, PeriodIndex):
200195
# index is PeriodIndex, so convert to corresponding Period freq
201196
freq = "M"
202-
203-
warn = None
204-
if isinstance(ser.index, PeriodIndex):
205-
warn = FutureWarning
206-
msg = "Resampling with a PeriodIndex is deprecated"
207-
with tm.assert_produces_warning(warn, match=msg):
208-
rs = ser.resample(freq)
197+
rs = ser.resample(freq)
209198
result = getattr(rs, resample_method)()
210199

211200
if resample_method == "ohlc":
@@ -261,9 +250,7 @@ def test_resample_nat_index_series(freq, resample_method):
261250

262251
ser = Series(range(5), index=PeriodIndex([NaT] * 5, freq=freq))
263252

264-
msg = "Resampling with a PeriodIndex is deprecated"
265-
with tm.assert_produces_warning(FutureWarning, match=msg):
266-
rs = ser.resample(freq)
253+
rs = ser.resample(freq)
267254
result = getattr(rs, resample_method)()
268255

269256
if resample_method == "ohlc":
@@ -302,13 +289,7 @@ def test_resample_count_empty_series(freq, index, resample_method):
302289
elif freq == "ME" and isinstance(ser.index, PeriodIndex):
303290
# index is PeriodIndex, so convert to corresponding Period freq
304291
freq = "M"
305-
306-
warn = None
307-
if isinstance(ser.index, PeriodIndex):
308-
warn = FutureWarning
309-
msg = "Resampling with a PeriodIndex is deprecated"
310-
with tm.assert_produces_warning(warn, match=msg):
311-
rs = ser.resample(freq)
292+
rs = ser.resample(freq)
312293

313294
result = getattr(rs, resample_method)()
314295

@@ -338,13 +319,7 @@ def test_resample_empty_dataframe(index, freq, resample_method):
338319
elif freq == "ME" and isinstance(df.index, PeriodIndex):
339320
# index is PeriodIndex, so convert to corresponding Period freq
340321
freq = "M"
341-
342-
warn = None
343-
if isinstance(df.index, PeriodIndex):
344-
warn = FutureWarning
345-
msg = "Resampling with a PeriodIndex is deprecated"
346-
with tm.assert_produces_warning(warn, match=msg):
347-
rs = df.resample(freq, group_keys=False)
322+
rs = df.resample(freq, group_keys=False)
348323
result = getattr(rs, resample_method)()
349324
if resample_method == "ohlc":
350325
# TODO: no tests with len(df.columns) > 0
@@ -386,14 +361,7 @@ def test_resample_count_empty_dataframe(freq, index):
386361
elif freq == "ME" and isinstance(empty_frame_dti.index, PeriodIndex):
387362
# index is PeriodIndex, so convert to corresponding Period freq
388363
freq = "M"
389-
390-
warn = None
391-
if isinstance(empty_frame_dti.index, PeriodIndex):
392-
warn = FutureWarning
393-
msg = "Resampling with a PeriodIndex is deprecated"
394-
with tm.assert_produces_warning(warn, match=msg):
395-
rs = empty_frame_dti.resample(freq)
396-
result = rs.count()
364+
result = empty_frame_dti.resample(freq).count()
397365

398366
index = _asfreq_compat(empty_frame_dti.index, freq)
399367

@@ -422,14 +390,7 @@ def test_resample_size_empty_dataframe(freq, index):
422390
elif freq == "ME" and isinstance(empty_frame_dti.index, PeriodIndex):
423391
# index is PeriodIndex, so convert to corresponding Period freq
424392
freq = "M"
425-
426-
msg = "Resampling with a PeriodIndex"
427-
warn = None
428-
if isinstance(empty_frame_dti.index, PeriodIndex):
429-
warn = FutureWarning
430-
with tm.assert_produces_warning(warn, match=msg):
431-
rs = empty_frame_dti.resample(freq)
432-
result = rs.size()
393+
result = empty_frame_dti.resample(freq).size()
433394

434395
index = _asfreq_compat(empty_frame_dti.index, freq)
435396

@@ -465,21 +426,12 @@ def test_resample_apply_empty_dataframe(index, freq, method):
465426
],
466427
)
467428
@pytest.mark.parametrize("dtype", [float, int, object, "datetime64[ns]"])
468-
@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
469429
def test_resample_empty_dtypes(index, dtype, resample_method):
470430
# Empty series were sometimes causing a segfault (for the functions
471431
# with Cython bounds-checking disabled) or an IndexError. We just run
472432
# them to ensure they no longer do. (GH #10228)
473-
warn = None
474-
if isinstance(index, PeriodIndex):
475-
# GH#53511
476-
index = PeriodIndex([], freq="B", name=index.name)
477-
warn = FutureWarning
478-
msg = "Resampling with a PeriodIndex is deprecated"
479-
480433
empty_series_dti = Series([], index, dtype)
481-
with tm.assert_produces_warning(warn, match=msg):
482-
rs = empty_series_dti.resample("D", group_keys=False)
434+
rs = empty_series_dti.resample("D", group_keys=False)
483435
try:
484436
getattr(rs, resample_method)()
485437
except DataError:
@@ -512,18 +464,8 @@ def test_apply_to_empty_series(index, freq):
512464
elif freq == "ME" and isinstance(ser.index, PeriodIndex):
513465
# index is PeriodIndex, so convert to corresponding Period freq
514466
freq = "M"
515-
516-
msg = "Resampling with a PeriodIndex"
517-
warn = None
518-
if isinstance(ser.index, PeriodIndex):
519-
warn = FutureWarning
520-
521-
with tm.assert_produces_warning(warn, match=msg):
522-
rs = ser.resample(freq, group_keys=False)
523-
524-
result = rs.apply(lambda x: 1)
525-
with tm.assert_produces_warning(warn, match=msg):
526-
expected = ser.resample(freq).apply("sum")
467+
result = ser.resample(freq, group_keys=False).apply(lambda x: 1)
468+
expected = ser.resample(freq).apply("sum")
527469

528470
tm.assert_series_equal(result, expected, check_dtype=False)
529471

@@ -541,16 +483,8 @@ def test_resampler_is_iterable(index):
541483
series = Series(range(len(index)), index=index)
542484
freq = "h"
543485
tg = Grouper(freq=freq, convention="start")
544-
msg = "Resampling with a PeriodIndex"
545-
warn = None
546-
if isinstance(series.index, PeriodIndex):
547-
warn = FutureWarning
548-
549-
with tm.assert_produces_warning(warn, match=msg):
550-
grouped = series.groupby(tg)
551-
552-
with tm.assert_produces_warning(warn, match=msg):
553-
resampled = series.resample(freq)
486+
grouped = series.groupby(tg)
487+
resampled = series.resample(freq)
554488
for (rk, rv), (gk, gv) in zip(resampled, grouped):
555489
assert rk == gk
556490
tm.assert_series_equal(rv, gv)
@@ -570,13 +504,8 @@ def test_resample_quantile(index):
570504
q = 0.75
571505
freq = "h"
572506

573-
msg = "Resampling with a PeriodIndex"
574-
warn = None
575-
if isinstance(ser.index, PeriodIndex):
576-
warn = FutureWarning
577-
with tm.assert_produces_warning(warn, match=msg):
578-
result = ser.resample(freq).quantile(q)
579-
expected = ser.resample(freq).agg(lambda x: x.quantile(q)).rename(ser.name)
507+
result = ser.resample(freq).quantile(q)
508+
expected = ser.resample(freq).agg(lambda x: x.quantile(q)).rename(ser.name)
580509
tm.assert_series_equal(result, expected)
581510

582511

pandas/tests/resample/test_datetime_index.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,6 @@ def test_resample_basic_grouper(unit):
169169
tm.assert_series_equal(result, expected)
170170

171171

172-
@pytest.mark.filterwarnings(
173-
"ignore:The 'convention' keyword in Series.resample:FutureWarning"
174-
)
175172
@pytest.mark.parametrize(
176173
"keyword,value",
177174
[("label", "righttt"), ("closed", "righttt"), ("convention", "starttt")],
@@ -1014,10 +1011,7 @@ def test_period_with_agg():
10141011
)
10151012

10161013
expected = s2.to_timestamp().resample("D").mean().to_period()
1017-
msg = "Resampling with a PeriodIndex is deprecated"
1018-
with tm.assert_produces_warning(FutureWarning, match=msg):
1019-
rs = s2.resample("D")
1020-
result = rs.agg(lambda x: x.mean())
1014+
result = s2.resample("D").agg(lambda x: x.mean())
10211015
tm.assert_series_equal(result, expected)
10221016

10231017

0 commit comments

Comments
 (0)