Skip to content

CLN: enforced the deprecation of strings 'H', 'BH', 'CBH' in favor of 'h', 'bh', 'cbh' #59143

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 8 commits into from
Jul 9, 2024
2 changes: 2 additions & 0 deletions doc/source/whatsnew/v3.0.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,8 @@ Other Removals
- 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`)
- 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`)
- 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`)
- Enforced deprecation of strings ``H``, ``BH``, and ``CBH`` denoting frequencies in :class:`Hour`, :class:`BusinessHour`, :class:`CustomBusinessHour` (:issue:`59143`)
- Enforced deprecation of strings ``H``, ``BH``, and ``CBH`` denoting units in :class:`Timedelta` (:issue:`59143`)
- Enforced deprecation of strings ``T``, ``L``, ``U``, and ``N`` denoting frequencies in :class:`Minute`, :class:`Milli`, :class:`Micro`, :class:`Nano` (:issue:`57627`)
- Enforced deprecation of strings ``T``, ``L``, ``U``, and ``N`` denoting units in :class:`Timedelta` (:issue:`57627`)
- 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`)
Expand Down
2 changes: 1 addition & 1 deletion pandas/_libs/tslibs/dtypes.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ cdef bint is_supported_unit(NPY_DATETIMEUNIT reso)
cdef dict c_OFFSET_TO_PERIOD_FREQSTR
cdef dict c_PERIOD_TO_OFFSET_FREQSTR
cdef dict c_OFFSET_RENAMED_FREQSTR
cdef dict c_DEPR_ABBREVS
cdef dict c_REMOVED_ABBREVS
cdef dict c_DEPR_UNITS
cdef dict c_PERIOD_AND_OFFSET_DEPR_FREQSTR
cdef dict attrname_to_abbrevs
Expand Down
34 changes: 8 additions & 26 deletions pandas/_libs/tslibs/dtypes.pyx
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
# period frequency constants corresponding to scikits timeseries
# originals
from enum import Enum
import warnings

from pandas.util._exceptions import find_stack_level

from pandas._libs.tslibs.ccalendar cimport c_MONTH_NUMBERS
from pandas._libs.tslibs.np_datetime cimport (
Expand Down Expand Up @@ -339,7 +336,7 @@ cdef dict c_OFFSET_TO_PERIOD_FREQSTR = OFFSET_TO_PERIOD_FREQSTR
cdef dict c_PERIOD_TO_OFFSET_FREQSTR = PERIOD_TO_OFFSET_FREQSTR

# Map deprecated resolution abbreviations to correct resolution abbreviations
cdef dict c_DEPR_ABBREVS = {
cdef dict c_REMOVED_ABBREVS = {
"H": "h",
"BH": "bh",
"CBH": "cbh",
Expand All @@ -362,6 +359,8 @@ cdef dict c_PERIOD_AND_OFFSET_DEPR_FREQSTR = {
"MIN": "min",
}

INVALID_FREQ_ERR_MSG = "Invalid frequency: {0}"
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
INVALID_FREQ_ERR_MSG = "Invalid frequency: {0}"
cdef str INVALID_FREQ_ERR_MSG = "Invalid frequency: {0}"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think CI failures are unrelated to my changes.



class FreqGroup(Enum):
# Mirrors c_FreqGroup in the .pxd file
Expand Down Expand Up @@ -451,19 +450,10 @@ class Resolution(Enum):
>>> Resolution.get_reso_from_freqstr('h') == Resolution.RESO_HR
True
"""
cdef:
str abbrev
try:
if freq in c_DEPR_ABBREVS:
abbrev = c_DEPR_ABBREVS[freq]
warnings.warn(
f"\'{freq}\' is deprecated and will be removed in a future "
f"version. Please use \'{abbrev}\' "
f"instead of \'{freq}\'.",
FutureWarning,
stacklevel=find_stack_level(),
)
freq = abbrev
if freq in c_REMOVED_ABBREVS:

raise ValueError(INVALID_FREQ_ERR_MSG.format(freq))
attr_name = _abbrev_to_attrnames[freq]
except KeyError:
# For quarterly and yearly resolutions, we need to chop off
Expand All @@ -474,16 +464,8 @@ class Resolution(Enum):
if split_freq[1] not in _month_names:
# i.e. we want e.g. "Q-DEC", not "Q-INVALID"
raise
if split_freq[0] in c_DEPR_ABBREVS:
abbrev = c_DEPR_ABBREVS[split_freq[0]]
warnings.warn(
f"\'{split_freq[0]}\' is deprecated and will be removed in a "
f"future version. Please use \'{abbrev}\' "
f"instead of \'{split_freq[0]}\'.",
FutureWarning,
stacklevel=find_stack_level(),
)
split_freq[0] = abbrev
if split_freq[0] in c_REMOVED_ABBREVS:
raise ValueError(INVALID_FREQ_ERR_MSG.format(split_freq[0]))
attr_name = _abbrev_to_attrnames[split_freq[0]]

return cls.from_attrname(attr_name)
Expand Down
13 changes: 3 additions & 10 deletions pandas/_libs/tslibs/offsets.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@ from pandas._libs.tslibs.ccalendar cimport (
)
from pandas._libs.tslibs.conversion cimport localize_pydatetime
from pandas._libs.tslibs.dtypes cimport (
c_DEPR_ABBREVS,
c_OFFSET_RENAMED_FREQSTR,
c_OFFSET_TO_PERIOD_FREQSTR,
c_PERIOD_AND_OFFSET_DEPR_FREQSTR,
c_PERIOD_TO_OFFSET_FREQSTR,
c_REMOVED_ABBREVS,
periods_per_day,
)
from pandas._libs.tslibs.nattype cimport (
Expand Down Expand Up @@ -4908,15 +4908,8 @@ cpdef to_offset(freq, bint is_period=False):
if not stride:
stride = 1

if prefix in c_DEPR_ABBREVS:
warnings.warn(
f"\'{prefix}\' is deprecated and will be removed "
f"in a future version, please use "
f"\'{c_DEPR_ABBREVS.get(prefix)}\' instead.",
FutureWarning,
stacklevel=find_stack_level(),
)
prefix = c_DEPR_ABBREVS[prefix]
if prefix in c_REMOVED_ABBREVS:
raise ValueError(INVALID_FREQ_ERR_MSG.format(prefix))

if prefix in {"D", "h", "min", "s", "ms", "us", "ns"}:
# For these prefixes, we have something like "3h" or
Expand Down
9 changes: 8 additions & 1 deletion pandas/tests/arrays/test_datetimes.py
Original file line number Diff line number Diff line change
Expand Up @@ -760,7 +760,7 @@ def test_date_range_frequency_M_Q_Y_raises(self, freq):
with pytest.raises(ValueError, match=msg):
pd.date_range("1/1/2000", periods=4, freq=freq)

@pytest.mark.parametrize("freq_depr", ["2H", "2CBH", "2MIN", "2S", "2mS", "2Us"])
@pytest.mark.parametrize("freq_depr", ["2MIN", "2mS", "2Us"])
def test_date_range_uppercase_frequency_deprecated(self, freq_depr):
# GH#9586, GH#54939
depr_msg = f"'{freq_depr[1:]}' is deprecated and will be removed in a "
Expand Down Expand Up @@ -807,6 +807,13 @@ def test_date_range_frequency_A_raises(self, freq):
with pytest.raises(ValueError, match=msg):
pd.date_range("1/1/2000", periods=4, freq=freq)

@pytest.mark.parametrize("freq", ["2H", "2CBH", "2S"])
def test_date_range_uppercase_frequency_raises(self, freq):
msg = f"Invalid frequency: {freq}"

with pytest.raises(ValueError, match=msg):
pd.date_range("1/1/2000", periods=4, freq=freq)


def test_factorize_sort_without_freq():
dta = DatetimeArray._from_sequence([0, 2, 1], dtype="M8[ns]")
Expand Down
27 changes: 5 additions & 22 deletions pandas/tests/indexes/datetimes/test_datetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,29 +133,12 @@ def test_asarray_tz_aware(self):

tm.assert_numpy_array_equal(result, expected)

def test_CBH_deprecated(self):
msg = "'CBH' is deprecated and will be removed in a future version."

with tm.assert_produces_warning(FutureWarning, match=msg):
expected = date_range(
dt.datetime(2022, 12, 11), dt.datetime(2022, 12, 13), freq="CBH"
)
result = DatetimeIndex(
[
"2022-12-12 09:00:00",
"2022-12-12 10:00:00",
"2022-12-12 11:00:00",
"2022-12-12 12:00:00",
"2022-12-12 13:00:00",
"2022-12-12 14:00:00",
"2022-12-12 15:00:00",
"2022-12-12 16:00:00",
],
dtype="datetime64[ns]",
freq="cbh",
)
@pytest.mark.parametrize("freq", ["2H", "2BH", "2S"])
def test_CBH_deprecated(self, freq):
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
def test_CBH_deprecated(self, freq):
def test_CBH_raises(self, freq):

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks @mroeschke for reviewing this PR. I changed the name for the test.

msg = f"Invalid frequency: {freq}"

tm.assert_index_equal(result, expected)
with pytest.raises(ValueError, match=msg):
date_range(dt.datetime(2022, 12, 11), dt.datetime(2022, 12, 13), freq=freq)

@pytest.mark.parametrize("freq", ["2BM", "1bm", "2BQ", "1BQ-MAR", "2BY-JUN", "1by"])
def test_BM_BQ_BY_raises(self, freq):
Expand Down
8 changes: 4 additions & 4 deletions pandas/tests/indexes/period/test_period_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ def test_constructor_U(self):
with pytest.raises(ValueError, match="Invalid frequency: X"):
period_range("2007-1-1", periods=500, freq="X")

@pytest.mark.parametrize("freq_depr", ["2H", "2MIN", "2S", "2US", "2NS"])
@pytest.mark.parametrize("freq_depr", ["2MIN", "2US", "2NS"])
def test_uppercase_freq_deprecated_from_time_series(self, freq_depr):
# GH#52536, GH#54939
msg = f"'{freq_depr[1:]}' is deprecated and will be removed in a "
Expand All @@ -212,9 +212,9 @@ def test_uppercase_freq_deprecated_from_time_series(self, freq_depr):
with tm.assert_produces_warning(FutureWarning, match=msg):
period_range("2020-01-01 00:00:00 00:00", periods=2, freq=freq_depr)

@pytest.mark.parametrize("freq", ["2m", "2q-sep", "2y"])
def test_lowercase_freq_from_time_series_raises(self, freq):
# GH#52536, GH#54939
@pytest.mark.parametrize("freq", ["2m", "2q-sep", "2y", "2H", "2S"])
def test_incorrect_case_freq_from_time_series_raises(self, freq):
# GH#52536, GH#54939, GH#59143
msg = f"Invalid frequency: {freq}"

with pytest.raises(ValueError, match=msg):
Expand Down
44 changes: 10 additions & 34 deletions pandas/tests/indexes/timedeltas/test_timedelta_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

from pandas import (
Timedelta,
TimedeltaIndex,
timedelta_range,
to_timedelta,
)
Expand Down Expand Up @@ -70,14 +69,12 @@ def test_linspace_behavior(self, periods, freq):
expected = timedelta_range(start="0 days", end="4 days", freq=freq)
tm.assert_index_equal(result, expected)

def test_timedelta_range_H_deprecated(self):
def test_timedelta_range_H_raises(self):
# GH#52536
msg = "'H' is deprecated and will be removed in a future version."
msg = "Invalid frequency: H"

result = timedelta_range(start="0 days", end="4 days", periods=6)
with tm.assert_produces_warning(FutureWarning, match=msg):
expected = timedelta_range(start="0 days", end="4 days", freq="19H12min")
tm.assert_index_equal(result, expected)
with pytest.raises(ValueError, match=msg):
timedelta_range(start="0 days", end="4 days", freq="19H12min")

def test_timedelta_range_T_raises(self):
msg = "Invalid frequency: T"
Expand Down Expand Up @@ -130,33 +127,6 @@ def test_timedelta_range_infer_freq(self):
result = timedelta_range("0s", "1s", periods=31)
assert result.freq is None

@pytest.mark.parametrize(
"freq_depr, start, end, expected_values, expected_freq",
[
(
"3.5S",
"05:03:01",
"05:03:10",
["0 days 05:03:01", "0 days 05:03:04.500000", "0 days 05:03:08"],
"3500ms",
),
],
)
def test_timedelta_range_deprecated_freq(
self, freq_depr, start, end, expected_values, expected_freq
):
# GH#52536
msg = (
f"'{freq_depr[-1]}' is deprecated and will be removed in a future version."
)

with tm.assert_produces_warning(FutureWarning, match=msg):
result = timedelta_range(start=start, end=end, freq=freq_depr)
expected = TimedeltaIndex(
expected_values, dtype="timedelta64[ns]", freq=expected_freq
)
tm.assert_index_equal(result, expected)

@pytest.mark.parametrize(
"freq_depr, start, end",
[
Expand All @@ -170,9 +140,15 @@ def test_timedelta_range_deprecated_freq(
"5 hours",
"5 hours 8 minutes",
),
(
"3.5S",
"05:03:01",
"05:03:10",
),
],
)
def test_timedelta_range_removed_freq(self, freq_depr, start, end):
# GH#59143
msg = f"Invalid frequency: {freq_depr}"
with pytest.raises(ValueError, match=msg):
timedelta_range(start=start, end=end, freq=freq_depr)
5 changes: 1 addition & 4 deletions pandas/tests/scalar/period/test_asfreq.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,17 +111,14 @@ def test_conv_annual(self):
assert ival_A.asfreq("B", "E") == ival_A_to_B_end
assert ival_A.asfreq("D", "s") == ival_A_to_D_start
assert ival_A.asfreq("D", "E") == ival_A_to_D_end
msg_depr = "'H' is deprecated and will be removed in a future version."
with tm.assert_produces_warning(FutureWarning, match=msg_depr):
with pytest.raises(ValueError, match=msg):
assert ival_A.asfreq("H", "s") == ival_A_to_H_start
assert ival_A.asfreq("H", "E") == ival_A_to_H_end
assert ival_A.asfreq("min", "s") == ival_A_to_T_start
assert ival_A.asfreq("min", "E") == ival_A_to_T_end
with pytest.raises(ValueError, match=msg):
assert ival_A.asfreq("T", "s") == ival_A_to_T_start
assert ival_A.asfreq("T", "E") == ival_A_to_T_end
msg_depr = "'S' is deprecated and will be removed in a future version."
with tm.assert_produces_warning(FutureWarning, match=msg_depr):
assert ival_A.asfreq("S", "S") == ival_A_to_S_start
assert ival_A.asfreq("S", "E") == ival_A_to_S_end

Expand Down
10 changes: 4 additions & 6 deletions pandas/tests/tslibs/test_resolution.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
)
from pandas._libs.tslibs.dtypes import NpyDatetimeUnit

import pandas._testing as tm


def test_get_resolution_nano():
# don't return the fallback RESO_DAY
Expand Down Expand Up @@ -50,9 +48,9 @@ def test_get_attrname_from_abbrev(freqstr, expected):


@pytest.mark.parametrize("freq", ["H", "S"])
def test_units_H_S_deprecated_from_attrname_to_abbrevs(freq):
# GH#52536
msg = f"'{freq}' is deprecated and will be removed in a future version."
def test_unit_H_S_raises(freq):
# GH#59143
msg = f"Invalid frequency: {freq}"

with tm.assert_produces_warning(FutureWarning, match=msg):
with pytest.raises(ValueError, match=msg):
Resolution.get_reso_from_freqstr(freq)
20 changes: 9 additions & 11 deletions pandas/tests/tslibs/test_to_offset.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,21 +206,19 @@ def test_to_offset_lowercase_frequency_raises(freq_depr):
to_offset(freq_depr)


@pytest.mark.parametrize(
"freq_depr",
[
"2H",
"2BH",
"2MIN",
"2S",
"2Us",
"2NS",
],
)
@pytest.mark.parametrize("freq_depr", ["2MIN", "2Us", "2NS"])
def test_to_offset_uppercase_frequency_deprecated(freq_depr):
# GH#54939
depr_msg = f"'{freq_depr[1:]}' is deprecated and will be removed in a "
f"future version, please use '{freq_depr.lower()[1:]}' instead."

with tm.assert_produces_warning(FutureWarning, match=depr_msg):
to_offset(freq_depr)


@pytest.mark.parametrize("freq", ["2H", "2BH", "2S"])
def test_to_offset_uppercase_frequency_raises(freq):
msg = f"Invalid frequency: {freq}"

with pytest.raises(ValueError, match=msg):
to_offset(freq)