Skip to content

Commit

Permalink
DEPR: Period[B] (#53511)
Browse files Browse the repository at this point in the history
* DEPR: Period[B]

* filter warnings

* xfail

* filter warning

* Change makePeriodIndex to D

* Suppress more specific message
  • Loading branch information
jbrockmendel committed Jul 24, 2023
1 parent 1f288fa commit 4c67076
Show file tree
Hide file tree
Showing 42 changed files with 402 additions and 155 deletions.
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
20 changes: 16 additions & 4 deletions pandas/plotting/_matplotlib/timeseries.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,10 +276,22 @@ 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.filterwarnings(
"ignore",
r"PeriodDtype\[B\] is deprecated",
category=FutureWarning,
)

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

0 comments on commit 4c67076

Please sign in to comment.