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 @@ -269,6 +269,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 :meth:`Series.first` and :meth:`DataFrame.first` (please create a mask and filter using ``.loc`` instead) (:issue:`45908`)
- Deprecated allowing ``downcast`` keyword other than ``None``, ``False``, "infer", or a dict with these as values in :meth:`Series.fillna`, :meth:`DataFrame.fillna` (:issue:`40988`)
- Deprecated allowing arbitrary ``fill_value`` in :class:`SparseDtype`, in a future version the ``fill_value`` will need to be compatible with the ``dtype.subtype``, either a scalar that can be held by that subtype or ``NaN`` for integer or bool subtypes (:issue:`23124`)
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 @@ -2799,6 +2802,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
6 changes: 5 additions & 1 deletion pandas/_testing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
Iterable,
cast,
)
import warnings

import numpy as np

Expand Down Expand Up @@ -443,7 +444,10 @@ 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)
with warnings.catch_warnings():
warnings.simplefilter("ignore")
pi = pd.period_range(start=dt, periods=k, freq="B", name=name, **kwargs)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to use another freq like "D"?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doing that breaks a handful of tests. I could go either way on changing it now vs after enforcement.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would slightly prefer changing now to hopefully prevent potentially having to change more tests in the future

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough, updated

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 @@ -43,6 +43,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 @@ -935,6 +936,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:
return 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 @@ -444,10 +444,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
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 @@ -968,6 +968,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 @@ -1008,6 +1009,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
3 changes: 3 additions & 0 deletions pandas/tests/indexes/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ def test_repr_max_seq_item_setting(self, simple_index):
repr(idx)
assert "..." not in str(idx)

@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
def test_ensure_copied_data(self, index):
# Check the "copy" argument of each Index.__new__ is honoured
# GH12309
Expand Down Expand Up @@ -406,6 +407,7 @@ def test_delete_base(self, index):
with pytest.raises(IndexError, match=msg):
index.delete(length)

@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
def test_equals(self, index):
if isinstance(index, IntervalIndex):
# IntervalIndex tested separately, the index.equals(index.astype(object))
Expand Down Expand Up @@ -593,6 +595,7 @@ def test_map(self, simple_index):
lambda values, index: Series(values, index),
],
)
@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
def test_map_dictlike(self, mapper, simple_index):
idx = simple_index
if isinstance(idx, CategoricalIndex):
Expand Down
1 change: 1 addition & 0 deletions pandas/tests/indexes/datetimelike.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ def test_map_callable(self, simple_index):
lambda values, index: pd.Series(values, index, dtype=object),
],
)
@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning")
def test_map_dictlike(self, mapper, simple_index):
index = simple_index
expected = index + index.freq
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 @@ -99,6 +99,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 @@ -624,6 +624,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 @@ -433,7 +438,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 @@ -445,8 +452,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 @@ -463,15 +471,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 @@ -491,6 +501,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