Skip to content

Commit c95716a

Browse files
CLN: enforce deprecation of frequencies deprecated for offsets (#57986)
* enforce deprecation of offset deprecated freqstr * fix tests * fix mypy error * add a note to v3.0.0 * remove c_REVERSE_OFFSET_REMOVED_FREQSTR, correct tests * fix test_to_period_offsets_not_supported * correct to_offset, fix tests * add dict PERIOD_TO_OFFSET_FREQSTR, corect meth to_offset, fix tests * add a comment * create dictionary PERIOD_AND_OFFSET_ALIASES * correct def to_offset * fixup * fixup * replace c_OFFSET_RENAMED_FREQSTR with c_PERIOD_TO_OFFSET_FREQSTR * add cimport c_PERIOD_TO_OFFSET_FREQSTR * add a helper function for error reporting * minor whatsnew fix --------- Co-authored-by: Marco Edward Gorelli <[email protected]> Co-authored-by: Marco Gorelli <[email protected]>
1 parent ee127fc commit c95716a

File tree

17 files changed

+274
-301
lines changed

17 files changed

+274
-301
lines changed

doc/source/whatsnew/v3.0.0.rst

+29-1
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,34 @@ Other Deprecations
280280

281281
Removal of prior version deprecations/changes
282282
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
283+
284+
Enforced deprecation of aliases ``M``, ``Q``, ``Y``, etc. in favour of ``ME``, ``QE``, ``YE``, etc. for offsets
285+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
286+
287+
Renamed the following offset aliases (:issue:`57986`):
288+
289+
+-------------------------------+------------------+------------------+
290+
| offset | removed alias | new alias |
291+
+===============================+==================+==================+
292+
|:class:`MonthEnd` | ``M`` | ``ME`` |
293+
+-------------------------------+------------------+------------------+
294+
|:class:`BusinessMonthEnd` | ``BM`` | ``BME`` |
295+
+-------------------------------+------------------+------------------+
296+
|:class:`SemiMonthEnd` | ``SM`` | ``SME`` |
297+
+-------------------------------+------------------+------------------+
298+
|:class:`CustomBusinessMonthEnd`| ``CBM`` | ``CBME`` |
299+
+-------------------------------+------------------+------------------+
300+
|:class:`QuarterEnd` | ``Q`` | ``QE`` |
301+
+-------------------------------+------------------+------------------+
302+
|:class:`BQuarterEnd` | ``BQ`` | ``BQE`` |
303+
+-------------------------------+------------------+------------------+
304+
|:class:`YearEnd` | ``Y`` | ``YE`` |
305+
+-------------------------------+------------------+------------------+
306+
|:class:`BYearEnd` | ``BY`` | ``BYE`` |
307+
+-------------------------------+------------------+------------------+
308+
309+
Other Removals
310+
^^^^^^^^^^^^^^
283311
- :class:`.DataFrameGroupBy.idxmin`, :class:`.DataFrameGroupBy.idxmax`, :class:`.SeriesGroupBy.idxmin`, and :class:`.SeriesGroupBy.idxmax` will now raise a ``ValueError`` when used with ``skipna=False`` and an NA value is encountered (:issue:`10694`)
284312
- :func:`concat` no longer ignores empty objects when determining output dtypes (:issue:`39122`)
285313
- :func:`concat` with all-NA entries no longer ignores the dtype of those entries when determining the result dtype (:issue:`40893`)
@@ -343,7 +371,7 @@ Removal of prior version deprecations/changes
343371
- Enforced deprecation of string ``A`` denoting frequency in :class:`YearEnd` and strings ``A-DEC``, ``A-JAN``, etc. denoting annual frequencies with various fiscal year ends (:issue:`57699`)
344372
- Enforced deprecation of string ``BAS`` denoting frequency in :class:`BYearBegin` and strings ``BAS-DEC``, ``BAS-JAN``, etc. denoting annual frequencies with various fiscal year starts (:issue:`57793`)
345373
- Enforced deprecation of string ``BA`` denoting frequency in :class:`BYearEnd` and strings ``BA-DEC``, ``BA-JAN``, etc. denoting annual frequencies with various fiscal year ends (:issue:`57793`)
346-
- Enforced deprecation of strings ``T``, ``L``, ``U``, and ``N`` denoting frequencies in :class:`Minute`, :class:`Second`, :class:`Milli`, :class:`Micro`, :class:`Nano` (:issue:`57627`)
374+
- Enforced deprecation of strings ``T``, ``L``, ``U``, and ``N`` denoting frequencies in :class:`Minute`, :class:`Milli`, :class:`Micro`, :class:`Nano` (:issue:`57627`)
347375
- Enforced deprecation of strings ``T``, ``L``, ``U``, and ``N`` denoting units in :class:`Timedelta` (:issue:`57627`)
348376
- Enforced deprecation of the behavior of :func:`concat` when ``len(keys) != len(objs)`` would truncate to the shorter of the two. Now this raises a ``ValueError`` (:issue:`43485`)
349377
- Enforced deprecation of values "pad", "ffill", "bfill", and "backfill" for :meth:`Series.interpolate` and :meth:`DataFrame.interpolate` (:issue:`57869`)

pandas/_libs/tslibs/dtypes.pxd

+3-2
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@ cdef NPY_DATETIMEUNIT get_supported_reso(NPY_DATETIMEUNIT reso)
1212
cdef bint is_supported_unit(NPY_DATETIMEUNIT reso)
1313

1414
cdef dict c_OFFSET_TO_PERIOD_FREQSTR
15-
cdef dict c_OFFSET_DEPR_FREQSTR
16-
cdef dict c_REVERSE_OFFSET_DEPR_FREQSTR
15+
cdef dict c_PERIOD_TO_OFFSET_FREQSTR
16+
cdef dict c_OFFSET_RENAMED_FREQSTR
1717
cdef dict c_DEPR_ABBREVS
18+
cdef dict c_PERIOD_AND_OFFSET_DEPR_FREQSTR
1819
cdef dict attrname_to_abbrevs
1920
cdef dict npy_unit_to_attrname
2021
cdef dict attrname_to_npy_unit

pandas/_libs/tslibs/dtypes.pyx

+40-5
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,10 @@ OFFSET_TO_PERIOD_FREQSTR: dict = {
176176
"EOM": "M",
177177
"BME": "M",
178178
"SME": "M",
179+
"BMS": "M",
180+
"CBME": "M",
181+
"CBMS": "M",
182+
"SMS": "M",
179183
"BQS": "Q",
180184
"QS": "Q",
181185
"BQE": "Q",
@@ -228,7 +232,6 @@ OFFSET_TO_PERIOD_FREQSTR: dict = {
228232
"YE-NOV": "Y-NOV",
229233
"W": "W",
230234
"ME": "M",
231-
"Y": "Y",
232235
"BYE": "Y",
233236
"BYE-DEC": "Y-DEC",
234237
"BYE-JAN": "Y-JAN",
@@ -245,7 +248,7 @@ OFFSET_TO_PERIOD_FREQSTR: dict = {
245248
"YS": "Y",
246249
"BYS": "Y",
247250
}
248-
cdef dict c_OFFSET_DEPR_FREQSTR = {
251+
cdef dict c_OFFSET_RENAMED_FREQSTR = {
249252
"M": "ME",
250253
"Q": "QE",
251254
"Q-DEC": "QE-DEC",
@@ -303,10 +306,37 @@ cdef dict c_OFFSET_DEPR_FREQSTR = {
303306
"BQ-OCT": "BQE-OCT",
304307
"BQ-NOV": "BQE-NOV",
305308
}
306-
cdef dict c_OFFSET_TO_PERIOD_FREQSTR = OFFSET_TO_PERIOD_FREQSTR
307-
cdef dict c_REVERSE_OFFSET_DEPR_FREQSTR = {
308-
v: k for k, v in c_OFFSET_DEPR_FREQSTR.items()
309+
PERIOD_TO_OFFSET_FREQSTR = {
310+
"M": "ME",
311+
"Q": "QE",
312+
"Q-DEC": "QE-DEC",
313+
"Q-JAN": "QE-JAN",
314+
"Q-FEB": "QE-FEB",
315+
"Q-MAR": "QE-MAR",
316+
"Q-APR": "QE-APR",
317+
"Q-MAY": "QE-MAY",
318+
"Q-JUN": "QE-JUN",
319+
"Q-JUL": "QE-JUL",
320+
"Q-AUG": "QE-AUG",
321+
"Q-SEP": "QE-SEP",
322+
"Q-OCT": "QE-OCT",
323+
"Q-NOV": "QE-NOV",
324+
"Y": "YE",
325+
"Y-DEC": "YE-DEC",
326+
"Y-JAN": "YE-JAN",
327+
"Y-FEB": "YE-FEB",
328+
"Y-MAR": "YE-MAR",
329+
"Y-APR": "YE-APR",
330+
"Y-MAY": "YE-MAY",
331+
"Y-JUN": "YE-JUN",
332+
"Y-JUL": "YE-JUL",
333+
"Y-AUG": "YE-AUG",
334+
"Y-SEP": "YE-SEP",
335+
"Y-OCT": "YE-OCT",
336+
"Y-NOV": "YE-NOV",
309337
}
338+
cdef dict c_OFFSET_TO_PERIOD_FREQSTR = OFFSET_TO_PERIOD_FREQSTR
339+
cdef dict c_PERIOD_TO_OFFSET_FREQSTR = PERIOD_TO_OFFSET_FREQSTR
310340

311341
# Map deprecated resolution abbreviations to correct resolution abbreviations
312342
cdef dict c_DEPR_ABBREVS = {
@@ -316,6 +346,11 @@ cdef dict c_DEPR_ABBREVS = {
316346
"S": "s",
317347
}
318348

349+
cdef dict c_PERIOD_AND_OFFSET_DEPR_FREQSTR = {
350+
"w": "W",
351+
"MIN": "min",
352+
}
353+
319354

320355
class FreqGroup(Enum):
321356
# Mirrors c_FreqGroup in the .pxd file

pandas/_libs/tslibs/offsets.pyx

+49-47
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,10 @@ from pandas._libs.tslibs.ccalendar cimport (
5757
from pandas._libs.tslibs.conversion cimport localize_pydatetime
5858
from pandas._libs.tslibs.dtypes cimport (
5959
c_DEPR_ABBREVS,
60-
c_OFFSET_DEPR_FREQSTR,
61-
c_REVERSE_OFFSET_DEPR_FREQSTR,
60+
c_OFFSET_RENAMED_FREQSTR,
61+
c_OFFSET_TO_PERIOD_FREQSTR,
62+
c_PERIOD_AND_OFFSET_DEPR_FREQSTR,
63+
c_PERIOD_TO_OFFSET_FREQSTR,
6264
periods_per_day,
6365
)
6466
from pandas._libs.tslibs.nattype cimport (
@@ -4711,6 +4713,34 @@ INVALID_FREQ_ERR_MSG = "Invalid frequency: {0}"
47114713
_offset_map = {}
47124714

47134715

4716+
def _validate_to_offset_alias(alias: str, is_period: bool) -> None:
4717+
if not is_period:
4718+
if alias.upper() in c_OFFSET_RENAMED_FREQSTR:
4719+
raise ValueError(
4720+
f"\'{alias}\' is no longer supported for offsets. Please "
4721+
f"use \'{c_OFFSET_RENAMED_FREQSTR.get(alias.upper())}\' "
4722+
f"instead."
4723+
)
4724+
if (alias.upper() != alias and
4725+
alias.lower() not in {"s", "ms", "us", "ns"} and
4726+
alias.upper().split("-")[0].endswith(("S", "E"))):
4727+
raise ValueError(INVALID_FREQ_ERR_MSG.format(alias))
4728+
if (is_period and
4729+
alias.upper() in c_OFFSET_TO_PERIOD_FREQSTR and
4730+
alias != "ms" and
4731+
alias.upper().split("-")[0].endswith(("S", "E"))):
4732+
if (alias.upper().startswith("B") or
4733+
alias.upper().startswith("S") or
4734+
alias.upper().startswith("C")):
4735+
raise ValueError(INVALID_FREQ_ERR_MSG.format(alias))
4736+
else:
4737+
alias_msg = "".join(alias.upper().split("E", 1))
4738+
raise ValueError(
4739+
f"for Period, please use \'{alias_msg}\' "
4740+
f"instead of \'{alias}\'"
4741+
)
4742+
4743+
47144744
# TODO: better name?
47154745
def _get_offset(name: str) -> BaseOffset:
47164746
"""
@@ -4850,54 +4880,26 @@ cpdef to_offset(freq, bint is_period=False):
48504880

48514881
tups = zip(split[0::4], split[1::4], split[2::4])
48524882
for n, (sep, stride, name) in enumerate(tups):
4853-
if not is_period and name.upper() in c_OFFSET_DEPR_FREQSTR:
4854-
warnings.warn(
4855-
f"\'{name}\' is deprecated and will be removed "
4856-
f"in a future version, please use "
4857-
f"\'{c_OFFSET_DEPR_FREQSTR.get(name.upper())}\' instead.",
4858-
FutureWarning,
4859-
stacklevel=find_stack_level(),
4860-
)
4861-
name = c_OFFSET_DEPR_FREQSTR[name.upper()]
4862-
if (not is_period and
4863-
name != name.upper() and
4864-
name.lower() not in {"s", "ms", "us", "ns"} and
4865-
name.upper().split("-")[0].endswith(("S", "E"))):
4866-
warnings.warn(
4867-
f"\'{name}\' is deprecated and will be removed "
4868-
f"in a future version, please use "
4869-
f"\'{name.upper()}\' instead.",
4870-
FutureWarning,
4871-
stacklevel=find_stack_level(),
4872-
)
4873-
name = name.upper()
4874-
if is_period and name.upper() in c_REVERSE_OFFSET_DEPR_FREQSTR:
4875-
if name.upper().startswith("Y"):
4876-
raise ValueError(
4877-
f"for Period, please use \'Y{name.upper()[2:]}\' "
4878-
f"instead of \'{name}\'"
4879-
)
4880-
if (name.upper().startswith("B") or
4881-
name.upper().startswith("S") or
4882-
name.upper().startswith("C")):
4883-
raise ValueError(INVALID_FREQ_ERR_MSG.format(name))
4884-
else:
4885-
raise ValueError(
4886-
f"for Period, please use "
4887-
f"\'{c_REVERSE_OFFSET_DEPR_FREQSTR.get(name.upper())}\' "
4888-
f"instead of \'{name}\'"
4889-
)
4890-
elif is_period and name.upper() in c_OFFSET_DEPR_FREQSTR:
4891-
if name.upper() != name:
4883+
_validate_to_offset_alias(name, is_period)
4884+
if is_period:
4885+
if name.upper() in c_PERIOD_TO_OFFSET_FREQSTR:
4886+
if name.upper() != name:
4887+
raise ValueError(
4888+
f"\'{name}\' is no longer supported, "
4889+
f"please use \'{name.upper()}\' instead.",
4890+
)
4891+
name = c_PERIOD_TO_OFFSET_FREQSTR.get(name.upper())
4892+
4893+
if name in c_PERIOD_AND_OFFSET_DEPR_FREQSTR:
48924894
warnings.warn(
4893-
f"\'{name}\' is deprecated and will be removed in "
4894-
f"a future version, please use \'{name.upper()}\' "
4895-
f"instead.",
4895+
f"\'{name}\' is deprecated and will be removed "
4896+
f"in a future version, please use "
4897+
f"\'{c_PERIOD_AND_OFFSET_DEPR_FREQSTR.get(name)}\' "
4898+
f" instead.",
48964899
FutureWarning,
48974900
stacklevel=find_stack_level(),
4898-
)
4899-
name = c_OFFSET_DEPR_FREQSTR.get(name.upper())
4900-
4901+
)
4902+
name = c_PERIOD_AND_OFFSET_DEPR_FREQSTR.get(name)
49014903
if sep != "" and not sep.isspace():
49024904
raise ValueError("separator must be spaces")
49034905
prefix = _lite_rule_alias.get(name) or name

pandas/tests/arrays/test_datetimes.py

+17-28
Original file line numberDiff line numberDiff line change
@@ -764,29 +764,14 @@ def test_iter_zoneinfo_fold(self, tz):
764764
assert left.utcoffset() == right2.utcoffset()
765765

766766
@pytest.mark.parametrize(
767-
"freq, freq_depr",
768-
[
769-
("2ME", "2M"),
770-
("2SME", "2SM"),
771-
("2SME", "2sm"),
772-
("2QE", "2Q"),
773-
("2QE-SEP", "2Q-SEP"),
774-
("1YE", "1Y"),
775-
("2YE-MAR", "2Y-MAR"),
776-
("2ME", "2m"),
777-
("2QE-SEP", "2q-sep"),
778-
("2YE", "2y"),
779-
],
767+
"freq",
768+
["2M", "2SM", "2sm", "2Q", "2Q-SEP", "1Y", "2Y-MAR", "2m", "2q-sep", "2y"],
780769
)
781-
def test_date_range_frequency_M_Q_Y_A_deprecated(self, freq, freq_depr):
782-
# GH#9586, GH#54275
783-
depr_msg = f"'{freq_depr[1:]}' is deprecated and will be removed "
784-
f"in a future version, please use '{freq[1:]}' instead."
770+
def test_date_range_frequency_M_Q_Y_raises(self, freq):
771+
msg = f"Invalid frequency: {freq}"
785772

786-
expected = pd.date_range("1/1/2000", periods=4, freq=freq)
787-
with tm.assert_produces_warning(FutureWarning, match=depr_msg):
788-
result = pd.date_range("1/1/2000", periods=4, freq=freq_depr)
789-
tm.assert_index_equal(result, expected)
773+
with pytest.raises(ValueError, match=msg):
774+
pd.date_range("1/1/2000", periods=4, freq=freq)
790775

791776
@pytest.mark.parametrize("freq_depr", ["2H", "2CBH", "2MIN", "2S", "2mS", "2Us"])
792777
def test_date_range_uppercase_frequency_deprecated(self, freq_depr):
@@ -800,7 +785,7 @@ def test_date_range_uppercase_frequency_deprecated(self, freq_depr):
800785
tm.assert_index_equal(result, expected)
801786

802787
@pytest.mark.parametrize(
803-
"freq_depr",
788+
"freq",
804789
[
805790
"2ye-mar",
806791
"2ys",
@@ -811,17 +796,21 @@ def test_date_range_uppercase_frequency_deprecated(self, freq_depr):
811796
"2bms",
812797
"2cbme",
813798
"2me",
814-
"2w",
815799
],
816800
)
817-
def test_date_range_lowercase_frequency_deprecated(self, freq_depr):
801+
def test_date_range_lowercase_frequency_raises(self, freq):
802+
msg = f"Invalid frequency: {freq}"
803+
804+
with pytest.raises(ValueError, match=msg):
805+
pd.date_range("1/1/2000", periods=4, freq=freq)
806+
807+
def test_date_range_lowercase_frequency_deprecated(self):
818808
# GH#9586, GH#54939
819-
depr_msg = f"'{freq_depr[1:]}' is deprecated and will be removed in a "
820-
f"future version, please use '{freq_depr.upper()[1:]}' instead."
809+
depr_msg = "'w' is deprecated and will be removed in a future version"
821810

822-
expected = pd.date_range("1/1/2000", periods=4, freq=freq_depr.upper())
811+
expected = pd.date_range("1/1/2000", periods=4, freq="2W")
823812
with tm.assert_produces_warning(FutureWarning, match=depr_msg):
824-
result = pd.date_range("1/1/2000", periods=4, freq=freq_depr)
813+
result = pd.date_range("1/1/2000", periods=4, freq="2w")
825814
tm.assert_index_equal(result, expected)
826815

827816
@pytest.mark.parametrize("freq", ["1A", "2A-MAR", "2a-mar"])

pandas/tests/frame/methods/test_asfreq.py

+8-10
Original file line numberDiff line numberDiff line change
@@ -236,32 +236,30 @@ def test_asfreq_2ME(self, freq, freq_half):
236236
"freq, freq_depr",
237237
[
238238
("2ME", "2M"),
239+
("2ME", "2m"),
239240
("2QE", "2Q"),
240241
("2QE-SEP", "2Q-SEP"),
241242
("1BQE", "1BQ"),
242243
("2BQE-SEP", "2BQ-SEP"),
243-
("1YE", "1Y"),
244+
("2BQE-SEP", "2bq-sep"),
245+
("1YE", "1y"),
244246
("2YE-MAR", "2Y-MAR"),
245247
],
246248
)
247-
def test_asfreq_frequency_M_Q_Y_deprecated(self, freq, freq_depr):
248-
# GH#9586, #55978
249-
depr_msg = f"'{freq_depr[1:]}' is deprecated and will be removed "
250-
f"in a future version, please use '{freq[1:]}' instead."
249+
def test_asfreq_frequency_M_Q_Y_raises(self, freq, freq_depr):
250+
msg = f"Invalid frequency: {freq_depr}"
251251

252252
index = date_range("1/1/2000", periods=4, freq=f"{freq[1:]}")
253253
df = DataFrame({"s": Series([0.0, 1.0, 2.0, 3.0], index=index)})
254-
expected = df.asfreq(freq=freq)
255-
with tm.assert_produces_warning(FutureWarning, match=depr_msg):
256-
result = df.asfreq(freq=freq_depr)
257-
tm.assert_frame_equal(result, expected)
254+
with pytest.raises(ValueError, match=msg):
255+
df.asfreq(freq=freq_depr)
258256

259257
@pytest.mark.parametrize(
260258
"freq, error_msg",
261259
[
262260
(
263261
"2MS",
264-
"MS is not supported as period frequency",
262+
"Invalid frequency: 2MS",
265263
),
266264
(
267265
offsets.MonthBegin(),

0 commit comments

Comments
 (0)