Skip to content

Commit 4c67076

Browse files
authored
DEPR: Period[B] (#53511)
* DEPR: Period[B] * filter warnings * xfail * filter warning * Change makePeriodIndex to D * Suppress more specific message
1 parent 1f288fa commit 4c67076

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+402
-155
lines changed

doc/source/whatsnew/v2.1.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,7 @@ Deprecations
348348
- Deprecated unused "closed" and "normalize" keywords in the :class:`DatetimeIndex` constructor (:issue:`52628`)
349349
- Deprecated unused "closed" keyword in the :class:`TimedeltaIndex` constructor (:issue:`52628`)
350350
- Deprecated logical operation between two non boolean :class:`Series` with different indexes always coercing the result to bool dtype. In a future version, this will maintain the return type of the inputs. (:issue:`52500`, :issue:`52538`)
351+
- Deprecated :class:`Period` and :class:`PeriodDtype` with ``BDay`` freq, use a :class:`DatetimeIndex` with ``BDay`` freq instead (:issue:`53446`)
351352
- Deprecated :func:`value_counts`, use ``pd.Series(obj).value_counts()`` instead (:issue:`47862`)
352353
- Deprecated :meth:`Series.first` and :meth:`DataFrame.first` (please create a mask and filter using ``.loc`` instead) (:issue:`45908`)
353354
- Deprecated :meth:`Series.interpolate` and :meth:`DataFrame.interpolate` for object-dtype (:issue:`53631`)

pandas/_libs/tslibs/period.pyx

+16-1
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,10 @@ from pandas._libs.tslibs.offsets cimport (
107107
to_offset,
108108
)
109109

110-
from pandas._libs.tslibs.offsets import INVALID_FREQ_ERR_MSG
110+
from pandas._libs.tslibs.offsets import (
111+
INVALID_FREQ_ERR_MSG,
112+
BDay,
113+
)
111114

112115
cdef:
113116
enum:
@@ -2812,6 +2815,18 @@ class Period(_Period):
28122815
dt.hour, dt.minute, dt.second,
28132816
dt.microsecond, 1000*nanosecond, base)
28142817

2818+
if isinstance(freq, BDay):
2819+
# GH#53446
2820+
import warnings
2821+
2822+
from pandas.util._exceptions import find_stack_level
2823+
warnings.warn(
2824+
"Period with BDay freq is deprecated and will be removed "
2825+
"in a future version. Use a DatetimeIndex with BDay freq instead.",
2826+
FutureWarning,
2827+
stacklevel=find_stack_level(),
2828+
)
2829+
28152830
return cls._from_ordinal(ordinal, freq)
28162831

28172832

pandas/_testing/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,8 @@ def makeTimedeltaIndex(
441441

442442
def makePeriodIndex(k: int = 10, name=None, **kwargs) -> PeriodIndex:
443443
dt = datetime(2000, 1, 1)
444-
return pd.period_range(start=dt, periods=k, freq="B", name=name, **kwargs)
444+
pi = pd.period_range(start=dt, periods=k, freq="D", name=name, **kwargs)
445+
return pi
445446

446447

447448
def makeMultiIndex(k: int = 10, names=None, **kwargs):

pandas/core/dtypes/dtypes.py

+10
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
PeriodDtypeBase,
4343
abbrev_to_npy_unit,
4444
)
45+
from pandas._libs.tslibs.offsets import BDay
4546
from pandas.compat import pa_version_under7p0
4647
from pandas.errors import PerformanceWarning
4748
from pandas.util._exceptions import find_stack_level
@@ -966,6 +967,15 @@ def __new__(cls, freq):
966967
if not isinstance(freq, BaseOffset):
967968
freq = cls._parse_dtype_strict(freq)
968969

970+
if isinstance(freq, BDay):
971+
# GH#53446
972+
warnings.warn(
973+
"PeriodDtype[B] is deprecated and will be removed in a future "
974+
"version. Use a DatetimeIndex with freq='B' instead",
975+
FutureWarning,
976+
stacklevel=find_stack_level(),
977+
)
978+
969979
try:
970980
dtype_code = cls._cache_dtypes[freq]
971981
except KeyError:

pandas/plotting/_matplotlib/timeseries.py

+16-4
Original file line numberDiff line numberDiff line change
@@ -276,10 +276,22 @@ def maybe_convert_index(ax: Axes, data):
276276

277277
freq_str = _get_period_alias(freq)
278278

279-
if isinstance(data.index, ABCDatetimeIndex):
280-
data = data.tz_localize(None).to_period(freq=freq_str)
281-
elif isinstance(data.index, ABCPeriodIndex):
282-
data.index = data.index.asfreq(freq=freq_str)
279+
import warnings
280+
281+
with warnings.catch_warnings():
282+
# suppress Period[B] deprecation warning
283+
# TODO: need to find an alternative to this before the deprecation
284+
# is enforced!
285+
warnings.filterwarnings(
286+
"ignore",
287+
r"PeriodDtype\[B\] is deprecated",
288+
category=FutureWarning,
289+
)
290+
291+
if isinstance(data.index, ABCDatetimeIndex):
292+
data = data.tz_localize(None).to_period(freq=freq_str)
293+
elif isinstance(data.index, ABCPeriodIndex):
294+
data.index = data.index.asfreq(freq=freq_str)
283295
return data
284296

285297

pandas/tests/arrays/test_datetimelike.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import array
44
import re
5+
import warnings
56

67
import numpy as np
78
import pytest
@@ -48,7 +49,10 @@ def period_index(freqstr):
4849
the PeriodIndex behavior.
4950
"""
5051
# TODO: non-monotone indexes; NaTs, different start dates
51-
pi = pd.period_range(start=Timestamp("2000-01-01"), periods=100, freq=freqstr)
52+
with warnings.catch_warnings():
53+
# suppress deprecation of Period[B]
54+
warnings.simplefilter("ignore")
55+
pi = pd.period_range(start=Timestamp("2000-01-01"), periods=100, freq=freqstr)
5256
return pi
5357

5458

@@ -192,6 +196,9 @@ def test_take_fill(self, arr1d):
192196
result = arr.take([-1, 1], allow_fill=True, fill_value=NaT)
193197
assert result[0] is NaT
194198

199+
@pytest.mark.filterwarnings(
200+
"ignore:Period with BDay freq is deprecated:FutureWarning"
201+
)
195202
def test_take_fill_str(self, arr1d):
196203
# Cast str fill_value matching other fill_value-taking methods
197204
result = arr1d.take([-1, 1], allow_fill=True, fill_value=str(arr1d[-1]))
@@ -745,6 +752,7 @@ def test_astype_object(self, arr1d):
745752
assert asobj.dtype == "O"
746753
assert list(asobj) == list(dti)
747754

755+
@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
748756
def test_to_period(self, datetime_index, freqstr):
749757
dti = datetime_index
750758
arr = DatetimeArray(dti)
@@ -999,6 +1007,8 @@ def test_take_fill_valid(self, timedelta_index, fixed_now_ts):
9991007
arr.take([-1, 1], allow_fill=True, fill_value=value)
10001008

10011009

1010+
@pytest.mark.filterwarnings(r"ignore:Period with BDay freq is deprecated:FutureWarning")
1011+
@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
10021012
class TestPeriodArray(SharedTests):
10031013
index_cls = PeriodIndex
10041014
array_cls = PeriodArray

pandas/tests/base/test_unique.py

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from pandas.tests.base.common import allow_na_ops
77

88

9+
@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
910
def test_unique(index_or_series_obj):
1011
obj = index_or_series_obj
1112
obj = np.repeat(obj, range(1, len(obj) + 1))
@@ -27,6 +28,7 @@ def test_unique(index_or_series_obj):
2728
tm.assert_numpy_array_equal(result, expected)
2829

2930

31+
@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
3032
@pytest.mark.parametrize("null_obj", [np.nan, None])
3133
def test_unique_null(null_obj, index_or_series_obj):
3234
obj = index_or_series_obj

pandas/tests/base/test_value_counts.py

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from pandas.tests.base.common import allow_na_ops
2020

2121

22+
@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
2223
def test_value_counts(index_or_series_obj):
2324
obj = index_or_series_obj
2425
obj = np.repeat(obj, range(1, len(obj) + 1))
@@ -54,6 +55,7 @@ def test_value_counts(index_or_series_obj):
5455

5556

5657
@pytest.mark.parametrize("null_obj", [np.nan, None])
58+
@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
5759
def test_value_counts_null(null_obj, index_or_series_obj):
5860
orig = index_or_series_obj
5961
obj = orig.copy()

pandas/tests/dtypes/test_dtypes.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -445,10 +445,13 @@ def test_construction(self):
445445
def test_cannot_use_custom_businessday(self):
446446
# GH#52534
447447
msg = "CustomBusinessDay cannot be used with Period or PeriodDtype"
448+
msg2 = r"PeriodDtype\[B\] is deprecated"
448449
with pytest.raises(TypeError, match=msg):
449-
PeriodDtype("C")
450+
with tm.assert_produces_warning(FutureWarning, match=msg2):
451+
PeriodDtype("C")
450452
with pytest.raises(TypeError, match=msg):
451-
PeriodDtype(pd.offsets.CustomBusinessDay())
453+
with tm.assert_produces_warning(FutureWarning, match=msg2):
454+
PeriodDtype(pd.offsets.CustomBusinessDay())
452455

453456
def test_subclass(self):
454457
a = PeriodDtype("period[D]")

pandas/tests/frame/methods/test_shift.py

+9-9
Original file line numberDiff line numberDiff line change
@@ -249,20 +249,20 @@ def test_shift_with_periodindex(self, frame_or_series):
249249
else:
250250
tm.assert_numpy_array_equal(unshifted.dropna().values, ps.values[:-1])
251251

252-
shifted2 = ps.shift(1, "B")
253-
shifted3 = ps.shift(1, offsets.BDay())
252+
shifted2 = ps.shift(1, "D")
253+
shifted3 = ps.shift(1, offsets.Day())
254254
tm.assert_equal(shifted2, shifted3)
255-
tm.assert_equal(ps, shifted2.shift(-1, "B"))
255+
tm.assert_equal(ps, shifted2.shift(-1, "D"))
256256

257257
msg = "does not match PeriodIndex freq"
258258
with pytest.raises(ValueError, match=msg):
259-
ps.shift(freq="D")
259+
ps.shift(freq="W")
260260

261261
# legacy support
262-
shifted4 = ps.shift(1, freq="B")
262+
shifted4 = ps.shift(1, freq="D")
263263
tm.assert_equal(shifted2, shifted4)
264264

265-
shifted5 = ps.shift(1, freq=offsets.BDay())
265+
shifted5 = ps.shift(1, freq=offsets.Day())
266266
tm.assert_equal(shifted5, shifted4)
267267

268268
def test_shift_other_axis(self):
@@ -492,10 +492,10 @@ def test_period_index_frame_shift_with_freq(self, frame_or_series):
492492
unshifted = shifted.shift(-1, freq="infer")
493493
tm.assert_equal(unshifted, ps)
494494

495-
shifted2 = ps.shift(freq="B")
495+
shifted2 = ps.shift(freq="D")
496496
tm.assert_equal(shifted, shifted2)
497497

498-
shifted3 = ps.shift(freq=offsets.BDay())
498+
shifted3 = ps.shift(freq=offsets.Day())
499499
tm.assert_equal(shifted, shifted3)
500500

501501
def test_datetime_frame_shift_with_freq(self, datetime_frame, frame_or_series):
@@ -524,7 +524,7 @@ def test_datetime_frame_shift_with_freq(self, datetime_frame, frame_or_series):
524524
def test_period_index_frame_shift_with_freq_error(self, frame_or_series):
525525
ps = tm.makePeriodFrame()
526526
ps = tm.get_obj(ps, frame_or_series)
527-
msg = "Given freq M does not match PeriodIndex freq B"
527+
msg = "Given freq M does not match PeriodIndex freq D"
528528
with pytest.raises(ValueError, match=msg):
529529
ps.shift(freq="M")
530530

pandas/tests/frame/methods/test_to_csv.py

+1
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,7 @@ def test_to_csv_nrows(self, nrows):
341341
"r_idx_type, c_idx_type", [("i", "i"), ("s", "s"), ("s", "dt"), ("p", "p")]
342342
)
343343
@pytest.mark.parametrize("ncols", [1, 2, 3, 4])
344+
@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
344345
def test_to_csv_idx_types(self, nrows, r_idx_type, c_idx_type, ncols):
345346
df = tm.makeCustomDataframe(
346347
nrows, ncols, r_idx_type=r_idx_type, c_idx_type=c_idx_type

pandas/tests/frame/test_reductions.py

+2
Original file line numberDiff line numberDiff line change
@@ -974,6 +974,7 @@ def test_idxmin(self, float_frame, int_frame, skipna, axis):
974974
tm.assert_series_equal(result, expected)
975975

976976
@pytest.mark.parametrize("axis", [0, 1])
977+
@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
977978
def test_idxmin_empty(self, index, skipna, axis):
978979
# GH53265
979980
if axis == 0:
@@ -1014,6 +1015,7 @@ def test_idxmax(self, float_frame, int_frame, skipna, axis):
10141015
tm.assert_series_equal(result, expected)
10151016

10161017
@pytest.mark.parametrize("axis", [0, 1])
1018+
@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
10171019
def test_idxmax_empty(self, index, skipna, axis):
10181020
# GH53265
10191021
if axis == 0:

pandas/tests/groupby/test_grouping.py

+1
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ class TestGrouping:
161161
tm.makePeriodIndex,
162162
],
163163
)
164+
@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
164165
def test_grouper_index_types(self, index):
165166
# related GH5375
166167
# groupby misbehaving when using a Floatlike index

pandas/tests/indexes/datetimes/methods/test_to_period.py

+1
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ def test_to_period_infer(self):
107107

108108
tm.assert_index_equal(pi1, pi2)
109109

110+
@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
110111
def test_period_dt64_round_trip(self):
111112
dti = date_range("1/1/2000", "1/7/2002", freq="B")
112113
pi = dti.to_period()

pandas/tests/indexes/multi/test_setops.py

+1
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,7 @@ def test_union_with_duplicates_keep_ea_dtype(dupe_val, any_numeric_ea_dtype):
622622
tm.assert_index_equal(result, expected)
623623

624624

625+
@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
625626
def test_union_duplicates(index, request):
626627
# GH#38977
627628
if index.empty or isinstance(index, (IntervalIndex, CategoricalIndex)):

pandas/tests/indexes/period/methods/test_to_timestamp.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
class TestToTimestamp:
1919
def test_to_timestamp_non_contiguous(self):
2020
# GH#44100
21-
dti = date_range("2021-10-18", periods=9, freq="B")
21+
dti = date_range("2021-10-18", periods=9, freq="D")
2222
pi = dti.to_period()
2323

2424
result = pi[::2].to_timestamp()

pandas/tests/indexes/period/test_constructors.py

+26-12
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,15 @@ def test_index_object_dtype(self, values_constructor):
6161

6262
def test_constructor_use_start_freq(self):
6363
# GH #1118
64-
p = Period("4/2/2012", freq="B")
65-
expected = period_range(start="4/2/2012", periods=10, freq="B")
66-
67-
index = period_range(start=p, periods=10)
64+
msg1 = "Period with BDay freq is deprecated"
65+
with tm.assert_produces_warning(FutureWarning, match=msg1):
66+
p = Period("4/2/2012", freq="B")
67+
msg2 = r"PeriodDtype\[B\] is deprecated"
68+
with tm.assert_produces_warning(FutureWarning, match=msg2):
69+
expected = period_range(start="4/2/2012", periods=10, freq="B")
70+
71+
with tm.assert_produces_warning(FutureWarning, match=msg2):
72+
index = period_range(start=p, periods=10)
6873
tm.assert_index_equal(index, expected)
6974

7075
def test_constructor_field_arrays(self):
@@ -440,7 +445,9 @@ def test_constructor(self):
440445
pi = period_range(freq="D", start="1/1/2001", end="12/31/2009")
441446
assert len(pi) == 365 * 9 + 2
442447

443-
pi = period_range(freq="B", start="1/1/2001", end="12/31/2009")
448+
msg = "Period with BDay freq is deprecated"
449+
with tm.assert_produces_warning(FutureWarning, match=msg):
450+
pi = period_range(freq="B", start="1/1/2001", end="12/31/2009")
444451
assert len(pi) == 261 * 9
445452

446453
pi = period_range(freq="H", start="1/1/2001", end="12/31/2001 23:00")
@@ -452,8 +459,9 @@ def test_constructor(self):
452459
pi = period_range(freq="S", start="1/1/2001", end="1/1/2001 23:59:59")
453460
assert len(pi) == 24 * 60 * 60
454461

455-
start = Period("02-Apr-2005", "B")
456-
i1 = period_range(start=start, periods=20)
462+
with tm.assert_produces_warning(FutureWarning, match=msg):
463+
start = Period("02-Apr-2005", "B")
464+
i1 = period_range(start=start, periods=20)
457465
assert len(i1) == 20
458466
assert i1.freq == start.freq
459467
assert i1[0] == start
@@ -470,15 +478,17 @@ def test_constructor(self):
470478
assert (i1 == i2).all()
471479
assert i1.freq == i2.freq
472480

473-
end_intv = Period("2005-05-01", "B")
474-
i1 = period_range(start=start, end=end_intv)
481+
with tm.assert_produces_warning(FutureWarning, match=msg):
482+
end_intv = Period("2005-05-01", "B")
483+
i1 = period_range(start=start, end=end_intv)
475484

476-
# infer freq from first element
477-
i2 = PeriodIndex([end_intv, Period("2005-05-05", "B")])
485+
# infer freq from first element
486+
i2 = PeriodIndex([end_intv, Period("2005-05-05", "B")])
478487
assert len(i2) == 2
479488
assert i2[0] == end_intv
480489

481-
i2 = PeriodIndex(np.array([end_intv, Period("2005-05-05", "B")]))
490+
with tm.assert_produces_warning(FutureWarning, match=msg):
491+
i2 = PeriodIndex(np.array([end_intv, Period("2005-05-05", "B")]))
482492
assert len(i2) == 2
483493
assert i2[0] == end_intv
484494

@@ -498,6 +508,10 @@ def test_constructor(self):
498508
@pytest.mark.parametrize(
499509
"freq", ["M", "Q", "A", "D", "B", "T", "S", "L", "U", "N", "H"]
500510
)
511+
@pytest.mark.filterwarnings(
512+
r"ignore:Period with BDay freq is deprecated:FutureWarning"
513+
)
514+
@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
501515
def test_recreate_from_data(self, freq):
502516
org = period_range(start="2001/04/01", freq=freq, periods=1)
503517
idx = PeriodIndex(org.values, freq=freq)

0 commit comments

Comments
 (0)