Skip to content

DEPR: Period[B] #53511

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 12 commits into from
Jul 24, 2023
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 @@ -348,6 +348,7 @@ Deprecations
- Deprecated unused "closed" and "normalize" keywords in the :class:`DatetimeIndex` constructor (:issue:`52628`)
- Deprecated unused "closed" keyword in the :class:`TimedeltaIndex` constructor (:issue:`52628`)
- 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`)
- Deprecated :class:`Period` and :class:`PeriodDtype` with ``BDay`` freq, use a :class:`DatetimeIndex` with ``BDay`` freq instead (:issue:`53446`)
- Deprecated :func:`value_counts`, use ``pd.Series(obj).value_counts()`` instead (:issue:`47862`)
- Deprecated :meth:`Series.first` and :meth:`DataFrame.first` (please create a mask and filter using ``.loc`` instead) (:issue:`45908`)
- Deprecated :meth:`Series.interpolate` and :meth:`DataFrame.interpolate` for object-dtype (:issue:`53631`)
Expand Down
17 changes: 16 additions & 1 deletion pandas/_libs/tslibs/period.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,10 @@ from pandas._libs.tslibs.offsets cimport (
to_offset,
)

from pandas._libs.tslibs.offsets import INVALID_FREQ_ERR_MSG
from pandas._libs.tslibs.offsets import (
INVALID_FREQ_ERR_MSG,
BDay,
)

cdef:
enum:
Expand Down Expand Up @@ -2812,6 +2815,18 @@ class Period(_Period):
dt.hour, dt.minute, dt.second,
dt.microsecond, 1000*nanosecond, base)

if isinstance(freq, BDay):
# GH#53446
import warnings

from pandas.util._exceptions import find_stack_level
warnings.warn(
"Period with BDay freq is deprecated and will be removed "
"in a future version. Use a DatetimeIndex with BDay freq instead.",
FutureWarning,
stacklevel=find_stack_level(),
)

return cls._from_ordinal(ordinal, freq)


Expand Down
3 changes: 2 additions & 1 deletion pandas/_testing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,8 @@ def makeTimedeltaIndex(

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


def makeMultiIndex(k: int = 10, names=None, **kwargs):
Expand Down
10 changes: 10 additions & 0 deletions pandas/core/dtypes/dtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
PeriodDtypeBase,
abbrev_to_npy_unit,
)
from pandas._libs.tslibs.offsets import BDay
from pandas.compat import pa_version_under7p0
from pandas.errors import PerformanceWarning
from pandas.util._exceptions import find_stack_level
Expand Down Expand Up @@ -966,6 +967,15 @@ def __new__(cls, freq):
if not isinstance(freq, BaseOffset):
freq = cls._parse_dtype_strict(freq)

if isinstance(freq, BDay):
# GH#53446
warnings.warn(
"PeriodDtype[B] is deprecated and will be removed in a future "
"version. Use a DatetimeIndex with freq='B' instead",
FutureWarning,
stacklevel=find_stack_level(),
)

try:
dtype_code = cls._cache_dtypes[freq]
except KeyError:
Expand Down
16 changes: 12 additions & 4 deletions pandas/plotting/_matplotlib/timeseries.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,10 +276,18 @@ def maybe_convert_index(ax: Axes, data):

freq_str = _get_period_alias(freq)

if isinstance(data.index, ABCDatetimeIndex):
data = data.tz_localize(None).to_period(freq=freq_str)
elif isinstance(data.index, ABCPeriodIndex):
data.index = data.index.asfreq(freq=freq_str)
import warnings

with warnings.catch_warnings():
# suppress Period[B] deprecation warning
# TODO: need to find an alternative to this before the deprecation
# is enforced!
warnings.simplefilter("ignore")

if isinstance(data.index, ABCDatetimeIndex):
data = data.tz_localize(None).to_period(freq=freq_str)
elif isinstance(data.index, ABCPeriodIndex):
data.index = data.index.asfreq(freq=freq_str)
return data


Expand Down
12 changes: 11 additions & 1 deletion pandas/tests/arrays/test_datetimelike.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import array
import re
import warnings

import numpy as np
import pytest
Expand Down Expand Up @@ -48,7 +49,10 @@ def period_index(freqstr):
the PeriodIndex behavior.
"""
# TODO: non-monotone indexes; NaTs, different start dates
pi = pd.period_range(start=Timestamp("2000-01-01"), periods=100, freq=freqstr)
with warnings.catch_warnings():
# suppress deprecation of Period[B]
warnings.simplefilter("ignore")
pi = pd.period_range(start=Timestamp("2000-01-01"), periods=100, freq=freqstr)
return pi


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

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

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


@pytest.mark.filterwarnings(r"ignore:Period with BDay freq is deprecated:FutureWarning")
@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
class TestPeriodArray(SharedTests):
index_cls = PeriodIndex
array_cls = PeriodArray
Expand Down
2 changes: 2 additions & 0 deletions pandas/tests/base/test_unique.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from pandas.tests.base.common import allow_na_ops


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


@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
@pytest.mark.parametrize("null_obj", [np.nan, None])
def test_unique_null(null_obj, index_or_series_obj):
obj = index_or_series_obj
Expand Down
2 changes: 2 additions & 0 deletions pandas/tests/base/test_value_counts.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from pandas.tests.base.common import allow_na_ops


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


@pytest.mark.parametrize("null_obj", [np.nan, None])
@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
def test_value_counts_null(null_obj, index_or_series_obj):
orig = index_or_series_obj
obj = orig.copy()
Expand Down
7 changes: 5 additions & 2 deletions pandas/tests/dtypes/test_dtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -445,10 +445,13 @@ def test_construction(self):
def test_cannot_use_custom_businessday(self):
# GH#52534
msg = "CustomBusinessDay cannot be used with Period or PeriodDtype"
msg2 = r"PeriodDtype\[B\] is deprecated"
with pytest.raises(TypeError, match=msg):
PeriodDtype("C")
with tm.assert_produces_warning(FutureWarning, match=msg2):
PeriodDtype("C")
with pytest.raises(TypeError, match=msg):
PeriodDtype(pd.offsets.CustomBusinessDay())
with tm.assert_produces_warning(FutureWarning, match=msg2):
PeriodDtype(pd.offsets.CustomBusinessDay())

def test_subclass(self):
a = PeriodDtype("period[D]")
Expand Down
18 changes: 9 additions & 9 deletions pandas/tests/frame/methods/test_shift.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,20 +249,20 @@ def test_shift_with_periodindex(self, frame_or_series):
else:
tm.assert_numpy_array_equal(unshifted.dropna().values, ps.values[:-1])

shifted2 = ps.shift(1, "B")
shifted3 = ps.shift(1, offsets.BDay())
shifted2 = ps.shift(1, "D")
shifted3 = ps.shift(1, offsets.Day())
tm.assert_equal(shifted2, shifted3)
tm.assert_equal(ps, shifted2.shift(-1, "B"))
tm.assert_equal(ps, shifted2.shift(-1, "D"))

msg = "does not match PeriodIndex freq"
with pytest.raises(ValueError, match=msg):
ps.shift(freq="D")
ps.shift(freq="W")

# legacy support
shifted4 = ps.shift(1, freq="B")
shifted4 = ps.shift(1, freq="D")
tm.assert_equal(shifted2, shifted4)

shifted5 = ps.shift(1, freq=offsets.BDay())
shifted5 = ps.shift(1, freq=offsets.Day())
tm.assert_equal(shifted5, shifted4)

def test_shift_other_axis(self):
Expand Down Expand Up @@ -492,10 +492,10 @@ def test_period_index_frame_shift_with_freq(self, frame_or_series):
unshifted = shifted.shift(-1, freq="infer")
tm.assert_equal(unshifted, ps)

shifted2 = ps.shift(freq="B")
shifted2 = ps.shift(freq="D")
tm.assert_equal(shifted, shifted2)

shifted3 = ps.shift(freq=offsets.BDay())
shifted3 = ps.shift(freq=offsets.Day())
tm.assert_equal(shifted, shifted3)

def test_datetime_frame_shift_with_freq(self, datetime_frame, frame_or_series):
Expand Down Expand Up @@ -524,7 +524,7 @@ def test_datetime_frame_shift_with_freq(self, datetime_frame, frame_or_series):
def test_period_index_frame_shift_with_freq_error(self, frame_or_series):
ps = tm.makePeriodFrame()
ps = tm.get_obj(ps, frame_or_series)
msg = "Given freq M does not match PeriodIndex freq B"
msg = "Given freq M does not match PeriodIndex freq D"
with pytest.raises(ValueError, match=msg):
ps.shift(freq="M")

Expand Down
1 change: 1 addition & 0 deletions pandas/tests/frame/methods/test_to_csv.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,7 @@ def test_to_csv_nrows(self, nrows):
"r_idx_type, c_idx_type", [("i", "i"), ("s", "s"), ("s", "dt"), ("p", "p")]
)
@pytest.mark.parametrize("ncols", [1, 2, 3, 4])
@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
def test_to_csv_idx_types(self, nrows, r_idx_type, c_idx_type, ncols):
df = tm.makeCustomDataframe(
nrows, ncols, r_idx_type=r_idx_type, c_idx_type=c_idx_type
Expand Down
2 changes: 2 additions & 0 deletions pandas/tests/frame/test_reductions.py
Original file line number Diff line number Diff line change
Expand Up @@ -974,6 +974,7 @@ def test_idxmin(self, float_frame, int_frame, skipna, axis):
tm.assert_series_equal(result, expected)

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

@pytest.mark.parametrize("axis", [0, 1])
@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
def test_idxmax_empty(self, index, skipna, axis):
# GH53265
if axis == 0:
Expand Down
1 change: 1 addition & 0 deletions pandas/tests/groupby/test_grouping.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ class TestGrouping:
tm.makePeriodIndex,
],
)
@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
def test_grouper_index_types(self, index):
# related GH5375
# groupby misbehaving when using a Floatlike index
Expand Down
1 change: 1 addition & 0 deletions pandas/tests/indexes/datetimes/methods/test_to_period.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ def test_to_period_infer(self):

tm.assert_index_equal(pi1, pi2)

@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
def test_period_dt64_round_trip(self):
dti = date_range("1/1/2000", "1/7/2002", freq="B")
pi = dti.to_period()
Expand Down
1 change: 1 addition & 0 deletions pandas/tests/indexes/multi/test_setops.py
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,7 @@ def test_union_with_duplicates_keep_ea_dtype(dupe_val, any_numeric_ea_dtype):
tm.assert_index_equal(result, expected)


@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
def test_union_duplicates(index, request):
# GH#38977
if index.empty or isinstance(index, (IntervalIndex, CategoricalIndex)):
Expand Down
2 changes: 1 addition & 1 deletion pandas/tests/indexes/period/methods/test_to_timestamp.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
class TestToTimestamp:
def test_to_timestamp_non_contiguous(self):
# GH#44100
dti = date_range("2021-10-18", periods=9, freq="B")
dti = date_range("2021-10-18", periods=9, freq="D")
pi = dti.to_period()

result = pi[::2].to_timestamp()
Expand Down
38 changes: 26 additions & 12 deletions pandas/tests/indexes/period/test_constructors.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,15 @@ def test_index_object_dtype(self, values_constructor):

def test_constructor_use_start_freq(self):
# GH #1118
p = Period("4/2/2012", freq="B")
expected = period_range(start="4/2/2012", periods=10, freq="B")

index = period_range(start=p, periods=10)
msg1 = "Period with BDay freq is deprecated"
with tm.assert_produces_warning(FutureWarning, match=msg1):
p = Period("4/2/2012", freq="B")
msg2 = r"PeriodDtype\[B\] is deprecated"
with tm.assert_produces_warning(FutureWarning, match=msg2):
expected = period_range(start="4/2/2012", periods=10, freq="B")

with tm.assert_produces_warning(FutureWarning, match=msg2):
index = period_range(start=p, periods=10)
tm.assert_index_equal(index, expected)

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

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

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

start = Period("02-Apr-2005", "B")
i1 = period_range(start=start, periods=20)
with tm.assert_produces_warning(FutureWarning, match=msg):
start = Period("02-Apr-2005", "B")
i1 = period_range(start=start, periods=20)
assert len(i1) == 20
assert i1.freq == start.freq
assert i1[0] == start
Expand All @@ -470,15 +478,17 @@ def test_constructor(self):
assert (i1 == i2).all()
assert i1.freq == i2.freq

end_intv = Period("2005-05-01", "B")
i1 = period_range(start=start, end=end_intv)
with tm.assert_produces_warning(FutureWarning, match=msg):
end_intv = Period("2005-05-01", "B")
i1 = period_range(start=start, end=end_intv)

# infer freq from first element
i2 = PeriodIndex([end_intv, Period("2005-05-05", "B")])
# infer freq from first element
i2 = PeriodIndex([end_intv, Period("2005-05-05", "B")])
assert len(i2) == 2
assert i2[0] == end_intv

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

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