From 10f8ba61297ff01f39b0c4a4b14cfd4888296995 Mon Sep 17 00:00:00 2001 From: Natalia Mokeeva Date: Sun, 24 Mar 2024 23:53:28 +0100 Subject: [PATCH 01/17] enforce deprecation of offset deprecated freqstr --- pandas/_libs/tslibs/dtypes.pxd | 4 +- pandas/_libs/tslibs/dtypes.pyx | 6 +-- pandas/_libs/tslibs/offsets.pyx | 32 ++++++---------- pandas/tests/arrays/test_datetimes.py | 46 +++++++++-------------- pandas/tests/frame/methods/test_asfreq.py | 12 ++---- 5 files changed, 38 insertions(+), 62 deletions(-) diff --git a/pandas/_libs/tslibs/dtypes.pxd b/pandas/_libs/tslibs/dtypes.pxd index 33f6789f3b402..3612a94431955 100644 --- a/pandas/_libs/tslibs/dtypes.pxd +++ b/pandas/_libs/tslibs/dtypes.pxd @@ -12,8 +12,8 @@ cdef NPY_DATETIMEUNIT get_supported_reso(NPY_DATETIMEUNIT reso) cdef bint is_supported_unit(NPY_DATETIMEUNIT reso) cdef dict c_OFFSET_TO_PERIOD_FREQSTR -cdef dict c_OFFSET_DEPR_FREQSTR -cdef dict c_REVERSE_OFFSET_DEPR_FREQSTR +cdef dict c_OFFSET_REMOVED_FREQSTR +cdef dict c_REVERSE_OFFSET_REMOVED_FREQSTR cdef dict c_DEPR_ABBREVS cdef dict attrname_to_abbrevs cdef dict npy_unit_to_attrname diff --git a/pandas/_libs/tslibs/dtypes.pyx b/pandas/_libs/tslibs/dtypes.pyx index 5bfbe211bfd14..23b8836b50c5a 100644 --- a/pandas/_libs/tslibs/dtypes.pyx +++ b/pandas/_libs/tslibs/dtypes.pyx @@ -245,7 +245,7 @@ OFFSET_TO_PERIOD_FREQSTR: dict = { "YS": "Y", "BYS": "Y", } -cdef dict c_OFFSET_DEPR_FREQSTR = { +cdef dict c_OFFSET_REMOVED_FREQSTR = { "M": "ME", "Q": "QE", "Q-DEC": "QE-DEC", @@ -304,8 +304,8 @@ cdef dict c_OFFSET_DEPR_FREQSTR = { "BQ-NOV": "BQE-NOV", } cdef dict c_OFFSET_TO_PERIOD_FREQSTR = OFFSET_TO_PERIOD_FREQSTR -cdef dict c_REVERSE_OFFSET_DEPR_FREQSTR = { - v: k for k, v in c_OFFSET_DEPR_FREQSTR.items() +cdef dict c_REVERSE_OFFSET_REMOVED_FREQSTR = { + v: k for k, v in c_OFFSET_REMOVED_FREQSTR.items() } # Map deprecated resolution abbreviations to correct resolution abbreviations diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index fd18ae5908f10..b209a682120f2 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -57,8 +57,8 @@ 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_DEPR_FREQSTR, - c_REVERSE_OFFSET_DEPR_FREQSTR, + c_OFFSET_REMOVED_FREQSTR, + c_REVERSE_OFFSET_REMOVED_FREQSTR, periods_per_day, ) from pandas._libs.tslibs.nattype cimport ( @@ -4847,28 +4847,18 @@ cpdef to_offset(freq, bint is_period=False): tups = zip(split[0::4], split[1::4], split[2::4]) for n, (sep, stride, name) in enumerate(tups): - if not is_period and name.upper() in c_OFFSET_DEPR_FREQSTR: - warnings.warn( - f"\'{name}\' is deprecated and will be removed " - f"in a future version, please use " - f"\'{c_OFFSET_DEPR_FREQSTR.get(name.upper())}\' instead.", - FutureWarning, - stacklevel=find_stack_level(), + if not is_period and name.upper() in c_OFFSET_REMOVED_FREQSTR: + raise ValueError( + f"\'{name}\' is no longer supported for offsets." ) - name = c_OFFSET_DEPR_FREQSTR[name.upper()] if (not is_period and name != name.upper() and name.lower() not in {"s", "ms", "us", "ns"} and name.upper().split("-")[0].endswith(("S", "E"))): - warnings.warn( - f"\'{name}\' is deprecated and will be removed " - f"in a future version, please use " - f"\'{name.upper()}\' instead.", - FutureWarning, - stacklevel=find_stack_level(), + raise ValueError( + f"\'{name}\' is no longer supported for offsets." ) - name = name.upper() - if is_period and name.upper() in c_REVERSE_OFFSET_DEPR_FREQSTR: + if is_period and name.upper() in c_REVERSE_OFFSET_REMOVED_FREQSTR: if name.upper().startswith("Y"): raise ValueError( f"for Period, please use \'Y{name.upper()[2:]}\' " @@ -4881,10 +4871,10 @@ cpdef to_offset(freq, bint is_period=False): else: raise ValueError( f"for Period, please use " - f"\'{c_REVERSE_OFFSET_DEPR_FREQSTR.get(name.upper())}\' " + f"\'{c_REVERSE_OFFSET_REMOVED_FREQSTR.get(name.upper())}\' " f"instead of \'{name}\'" ) - elif is_period and name.upper() in c_OFFSET_DEPR_FREQSTR: + elif is_period and name.upper() in c_OFFSET_REMOVED_FREQSTR: if name.upper() != name: warnings.warn( f"\'{name}\' is deprecated and will be removed in " @@ -4893,7 +4883,7 @@ cpdef to_offset(freq, bint is_period=False): FutureWarning, stacklevel=find_stack_level(), ) - name = c_OFFSET_DEPR_FREQSTR.get(name.upper()) + name = c_OFFSET_REMOVED_FREQSTR.get(name.upper()) if sep != "" and not sep.isspace(): raise ValueError("separator must be spaces") diff --git a/pandas/tests/arrays/test_datetimes.py b/pandas/tests/arrays/test_datetimes.py index 8650be62ae7eb..824d787a89891 100644 --- a/pandas/tests/arrays/test_datetimes.py +++ b/pandas/tests/arrays/test_datetimes.py @@ -764,29 +764,14 @@ def test_iter_zoneinfo_fold(self, tz): assert left.utcoffset() == right2.utcoffset() @pytest.mark.parametrize( - "freq, freq_depr", - [ - ("2ME", "2M"), - ("2SME", "2SM"), - ("2SME", "2sm"), - ("2QE", "2Q"), - ("2QE-SEP", "2Q-SEP"), - ("1YE", "1Y"), - ("2YE-MAR", "2Y-MAR"), - ("2ME", "2m"), - ("2QE-SEP", "2q-sep"), - ("2YE", "2y"), - ], + "freq", + ["2M", "2SM", "2sm", "2Q", "2Q-SEP", "1Y", "2Y-MAR", "2m", "2q-sep", "2y"], ) - def test_date_range_frequency_M_Q_Y_A_deprecated(self, freq, freq_depr): - # GH#9586, GH#54275 - depr_msg = f"'{freq_depr[1:]}' is deprecated and will be removed " - f"in a future version, please use '{freq[1:]}' instead." + def test_date_range_frequency_M_Q_Y_A_raises(self, freq): + msg = f"Invalid frequency: {freq}" - expected = pd.date_range("1/1/2000", periods=4, freq=freq) - with tm.assert_produces_warning(FutureWarning, match=depr_msg): - result = pd.date_range("1/1/2000", periods=4, freq=freq_depr) - tm.assert_index_equal(result, expected) + 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"]) def test_date_range_uppercase_frequency_deprecated(self, freq_depr): @@ -800,7 +785,7 @@ def test_date_range_uppercase_frequency_deprecated(self, freq_depr): tm.assert_index_equal(result, expected) @pytest.mark.parametrize( - "freq_depr", + "freq", [ "2ye-mar", "2ys", @@ -811,17 +796,22 @@ def test_date_range_uppercase_frequency_deprecated(self, freq_depr): "2bms", "2cbme", "2me", - "2w", ], ) - def test_date_range_lowercase_frequency_deprecated(self, freq_depr): + def test_date_range_lowercase_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_date_range_lowercase_frequency_deprecated(self): # GH#9586, GH#54939 - depr_msg = f"'{freq_depr[1:]}' is deprecated and will be removed in a " - f"future version, please use '{freq_depr.upper()[1:]}' instead." + depr_msg = "'w' is deprecated and will be removed in a " + "future version, please use 'W' instead." - expected = pd.date_range("1/1/2000", periods=4, freq=freq_depr.upper()) + expected = pd.date_range("1/1/2000", periods=4, freq="2W") with tm.assert_produces_warning(FutureWarning, match=depr_msg): - result = pd.date_range("1/1/2000", periods=4, freq=freq_depr) + result = pd.date_range("1/1/2000", periods=4, freq="2w") tm.assert_index_equal(result, expected) @pytest.mark.parametrize("freq", ["1A", "2A-MAR", "2a-mar"]) diff --git a/pandas/tests/frame/methods/test_asfreq.py b/pandas/tests/frame/methods/test_asfreq.py index fb288e19c6e82..c7351ec910529 100644 --- a/pandas/tests/frame/methods/test_asfreq.py +++ b/pandas/tests/frame/methods/test_asfreq.py @@ -244,17 +244,13 @@ def test_asfreq_2ME(self, freq, freq_half): ("2YE-MAR", "2Y-MAR"), ], ) - def test_asfreq_frequency_M_Q_Y_deprecated(self, freq, freq_depr): - # GH#9586, #55978 - depr_msg = f"'{freq_depr[1:]}' is deprecated and will be removed " - f"in a future version, please use '{freq[1:]}' instead." + def test_asfreq_frequency_M_Q_Y_raises(self, freq, freq_depr): + msg = f"Invalid frequency: {freq_depr}" index = date_range("1/1/2000", periods=4, freq=f"{freq[1:]}") df = DataFrame({"s": Series([0.0, 1.0, 2.0, 3.0], index=index)}) - expected = df.asfreq(freq=freq) - with tm.assert_produces_warning(FutureWarning, match=depr_msg): - result = df.asfreq(freq=freq_depr) - tm.assert_frame_equal(result, expected) + with pytest.raises(ValueError, match=msg): + df.asfreq(freq=freq_depr) @pytest.mark.parametrize( "freq, error_msg", From 428fd063e9382980f99552d8a3de69d029640dd0 Mon Sep 17 00:00:00 2001 From: Natalia Mokeeva Date: Mon, 25 Mar 2024 14:00:06 +0100 Subject: [PATCH 02/17] fix tests --- pandas/tests/arrays/test_datetimes.py | 5 +- pandas/tests/frame/methods/test_asfreq.py | 4 +- .../datetimes/methods/test_to_period.py | 22 ++------ .../indexes/datetimes/test_date_range.py | 55 ++++--------------- .../tests/indexes/datetimes/test_datetime.py | 27 ++------- pandas/tests/resample/test_datetime_index.py | 44 ++++----------- pandas/tests/tslibs/test_to_offset.py | 19 +++++-- 7 files changed, 50 insertions(+), 126 deletions(-) diff --git a/pandas/tests/arrays/test_datetimes.py b/pandas/tests/arrays/test_datetimes.py index 824d787a89891..63d60c78da482 100644 --- a/pandas/tests/arrays/test_datetimes.py +++ b/pandas/tests/arrays/test_datetimes.py @@ -767,7 +767,7 @@ def test_iter_zoneinfo_fold(self, tz): "freq", ["2M", "2SM", "2sm", "2Q", "2Q-SEP", "1Y", "2Y-MAR", "2m", "2q-sep", "2y"], ) - def test_date_range_frequency_M_Q_Y_A_raises(self, freq): + def test_date_range_frequency_M_Q_Y_raises(self, freq): msg = f"Invalid frequency: {freq}" with pytest.raises(ValueError, match=msg): @@ -806,8 +806,7 @@ def test_date_range_lowercase_frequency_raises(self, freq): def test_date_range_lowercase_frequency_deprecated(self): # GH#9586, GH#54939 - depr_msg = "'w' is deprecated and will be removed in a " - "future version, please use 'W' instead." + depr_msg = "'w' is deprecated and will be removed in a future version" expected = pd.date_range("1/1/2000", periods=4, freq="2W") with tm.assert_produces_warning(FutureWarning, match=depr_msg): diff --git a/pandas/tests/frame/methods/test_asfreq.py b/pandas/tests/frame/methods/test_asfreq.py index c7351ec910529..88cbef4f55b2a 100644 --- a/pandas/tests/frame/methods/test_asfreq.py +++ b/pandas/tests/frame/methods/test_asfreq.py @@ -236,11 +236,13 @@ def test_asfreq_2ME(self, freq, freq_half): "freq, freq_depr", [ ("2ME", "2M"), + ("2ME", "2m"), ("2QE", "2Q"), ("2QE-SEP", "2Q-SEP"), ("1BQE", "1BQ"), ("2BQE-SEP", "2BQ-SEP"), - ("1YE", "1Y"), + ("2BQE-SEP", "2bq-sep"), + ("1YE", "1y"), ("2YE-MAR", "2Y-MAR"), ], ) diff --git a/pandas/tests/indexes/datetimes/methods/test_to_period.py b/pandas/tests/indexes/datetimes/methods/test_to_period.py index 05e9a294d74a6..20bcf52eb0c2f 100644 --- a/pandas/tests/indexes/datetimes/methods/test_to_period.py +++ b/pandas/tests/indexes/datetimes/methods/test_to_period.py @@ -90,24 +90,14 @@ def test_dti_to_period_2monthish(self, freq_offset, freq_period): tm.assert_index_equal(pi, period_range("2020-01", "2020-05", freq=freq_period)) @pytest.mark.parametrize( - "freq, freq_depr", - [ - ("2ME", "2M"), - ("2QE", "2Q"), - ("2QE-SEP", "2Q-SEP"), - ("1YE", "1Y"), - ("2YE-MAR", "2Y-MAR"), - ], + "freq", ["2ME", "1me", "2QE", "2QE-SEP", "1YE", "ye", "2YE-MAR"] ) - def test_to_period_frequency_M_Q_Y_deprecated(self, freq, freq_depr): - # GH#9586 - msg = f"'{freq_depr[1:]}' is deprecated and will be removed " - f"in a future version, please use '{freq[1:]}' instead." + def test_to_period_frequency_M_Q_Y_raises(self, freq): + msg = f"Invalid frequency: {freq}" - rng = date_range("01-Jan-2012", periods=8, freq=freq) - prng = rng.to_period() - with tm.assert_produces_warning(FutureWarning, match=msg): - assert prng.freq == freq_depr + rng = date_range("01-Jan-2012", periods=8, freq="ME") + with pytest.raises(ValueError, match=msg): + rng.to_period(freq) def test_to_period_infer(self): # https://github.com/pandas-dev/pandas/issues/33358 diff --git a/pandas/tests/indexes/datetimes/test_date_range.py b/pandas/tests/indexes/datetimes/test_date_range.py index 43fcfd1e59670..77bd27e7006de 100644 --- a/pandas/tests/indexes/datetimes/test_date_range.py +++ b/pandas/tests/indexes/datetimes/test_date_range.py @@ -146,24 +146,12 @@ def test_date_range_fractional_period(self): exp = date_range("1/1/2000", periods=10) tm.assert_index_equal(rng, exp) - @pytest.mark.parametrize( - "freq,freq_depr", - [ - ("2ME", "2M"), - ("2SME", "2SM"), - ("2BQE", "2BQ"), - ("2BYE", "2BY"), - ], - ) - def test_date_range_frequency_M_SM_BQ_BY_deprecated(self, freq, freq_depr): - # GH#52064 - depr_msg = f"'{freq_depr[1:]}' is deprecated and will be removed " - f"in a future version, please use '{freq[1:]}' instead." - - expected = date_range("1/1/2000", periods=4, freq=freq) - with tm.assert_produces_warning(FutureWarning, match=depr_msg): - result = date_range("1/1/2000", periods=4, freq=freq_depr) - tm.assert_index_equal(result, expected) + @pytest.mark.parametrize("freq", ["2M", "1m", "2SM", "2BQ", "1bq", "2BY"]) + def test_date_range_frequency_M_SM_BQ_BY_raises(self, freq): + msg = f"Invalid frequency: {freq}" + + with pytest.raises(ValueError, match=msg): + date_range("1/1/2000", periods=4, freq=freq) def test_date_range_tuple_freq_raises(self): # GH#34703 @@ -779,34 +767,13 @@ def test_frequency_H_T_S_L_U_N_raises(self, freq): date_range("1/1/2000", periods=2, freq=freq) @pytest.mark.parametrize( - "freq,freq_depr", - [ - ("YE", "Y"), - ("YE-MAY", "Y-MAY"), - ], + "freq_depr", ["m", "bm", "CBM", "SM", "BQ", "q-feb", "y-may", "Y-MAY"] ) - def test_frequencies_Y_renamed(self, freq, freq_depr): - # GH#9586, GH#54275 - freq_msg = re.split("[0-9]*", freq, maxsplit=1)[1] - freq_depr_msg = re.split("[0-9]*", freq_depr, maxsplit=1)[1] - msg = f"'{freq_depr_msg}' is deprecated and will be removed " - f"in a future version, please use '{freq_msg}' instead." + def test_frequency_raises(self, freq_depr): + msg = f"Invalid frequency: {freq_depr}" - expected = date_range("1/1/2000", periods=2, freq=freq) - with tm.assert_produces_warning(FutureWarning, match=msg): - result = date_range("1/1/2000", periods=2, freq=freq_depr) - tm.assert_index_equal(result, expected) - - def test_to_offset_with_lowercase_deprecated_freq(self) -> None: - # https://github.com/pandas-dev/pandas/issues/56847 - msg = ( - "'m' is deprecated and will be removed in a future version, please use " - "'ME' instead." - ) - with tm.assert_produces_warning(FutureWarning, match=msg): - result = date_range("2010-01-01", periods=2, freq="m") - expected = DatetimeIndex(["2010-01-31", "2010-02-28"], freq="ME") - tm.assert_index_equal(result, expected) + with pytest.raises(ValueError, match=msg): + date_range("1/1/2000", periods=2, freq=freq_depr) def test_date_range_bday(self): sdate = datetime(1999, 12, 25) diff --git a/pandas/tests/indexes/datetimes/test_datetime.py b/pandas/tests/indexes/datetimes/test_datetime.py index 84a616f05cd63..cc2b802de2a16 100644 --- a/pandas/tests/indexes/datetimes/test_datetime.py +++ b/pandas/tests/indexes/datetimes/test_datetime.py @@ -157,29 +157,12 @@ def test_CBH_deprecated(self): tm.assert_index_equal(result, expected) - @pytest.mark.parametrize( - "freq, expected_values, freq_depr", - [ - ("2BYE-JUN", ["2016-06-30"], "2BY-JUN"), - ("2BME", ["2016-02-29", "2016-04-29", "2016-06-30"], "2BM"), - ("2BQE", ["2016-03-31"], "2BQ"), - ("1BQE-MAR", ["2016-03-31", "2016-06-30"], "1BQ-MAR"), - ], - ) - def test_BM_BQ_BY_deprecated(self, freq, expected_values, freq_depr): - # GH#52064 - msg = f"'{freq_depr[1:]}' is deprecated and will be removed " - f"in a future version, please use '{freq[1:]}' instead." - - with tm.assert_produces_warning(FutureWarning, match=msg): - expected = date_range(start="2016-02-21", end="2016-08-21", freq=freq_depr) - result = DatetimeIndex( - data=expected_values, - dtype="datetime64[ns]", - freq=freq, - ) + @pytest.mark.parametrize("freq", ["2BM", "1bm", "2BQ", "1BQ-MAR", "2BY-JUN", "1by"]) + def test_BM_BQ_BY_raises(self, freq): + msg = f"Invalid frequency: {freq}" - tm.assert_index_equal(result, expected) + with pytest.raises(ValueError, match=msg): + date_range(start="2016-02-21", end="2016-08-21", freq=freq) @pytest.mark.parametrize("freq", ["2BA-MAR", "1BAS-MAY", "2AS-AUG"]) def test_BA_BAS_raises(self, freq): diff --git a/pandas/tests/resample/test_datetime_index.py b/pandas/tests/resample/test_datetime_index.py index fecd24c9a4b40..3fb0dd70d461a 100644 --- a/pandas/tests/resample/test_datetime_index.py +++ b/pandas/tests/resample/test_datetime_index.py @@ -2039,46 +2039,22 @@ def test_resample_empty_series_with_tz(): tm.assert_series_equal(result, expected) -@pytest.mark.parametrize( - "freq, freq_depr", - [ - ("2ME", "2M"), - ("2QE", "2Q"), - ("2QE-SEP", "2Q-SEP"), - ("1YE", "1Y"), - ("2YE-MAR", "2Y-MAR"), - ], -) -def test_resample_M_Q_Y_deprecated(freq, freq_depr): - # GH#9586 - depr_msg = f"'{freq_depr[1:]}' is deprecated and will be removed " - f"in a future version, please use '{freq[1:]}' instead." +@pytest.mark.parametrize("freq", ["2M", "2m", "2Q", "2Q-SEP", "2q-sep", "1Y", "2Y-MAR"]) +def test_resample_M_Q_Y_raises(freq): + msg = f"Invalid frequency: {freq}" s = Series(range(10), index=date_range("20130101", freq="d", periods=10)) - expected = s.resample(freq).mean() - with tm.assert_produces_warning(FutureWarning, match=depr_msg): - result = s.resample(freq_depr).mean() - tm.assert_series_equal(result, expected) + with pytest.raises(ValueError, match=msg): + s.resample(freq).mean() -@pytest.mark.parametrize( - "freq, freq_depr", - [ - ("2BME", "2BM"), - ("2BQE", "2BQ"), - ("2BQE-MAR", "2BQ-MAR"), - ], -) -def test_resample_BM_BQ_deprecated(freq, freq_depr): - # GH#52064 - depr_msg = f"'{freq_depr[1:]}' is deprecated and will be removed " - f"in a future version, please use '{freq[1:]}' instead." +@pytest.mark.parametrize("freq", ["2BM", "1bm", "1BQ", "2BQ-MAR", "2bq=-mar"]) +def test_resample_BM_BQ_raises(freq): + msg = f"Invalid frequency: {freq}" s = Series(range(10), index=date_range("20130101", freq="d", periods=10)) - expected = s.resample(freq).mean() - with tm.assert_produces_warning(FutureWarning, match=depr_msg): - result = s.resample(freq_depr).mean() - tm.assert_series_equal(result, expected) + with pytest.raises(ValueError, match=msg): + s.resample(freq).mean() def test_resample_ms_closed_right(unit): diff --git a/pandas/tests/tslibs/test_to_offset.py b/pandas/tests/tslibs/test_to_offset.py index ad4e9e2bcf38a..5551492c73723 100644 --- a/pandas/tests/tslibs/test_to_offset.py +++ b/pandas/tests/tslibs/test_to_offset.py @@ -176,6 +176,15 @@ def test_anchored_shortcuts(shortcut, expected): assert result == expected +def test_to_offset_lowercase_frequency_w_deprecated(): + # GH#54939 + msg = "'w' is deprecated and will be removed in a " + "future version, please use 'W' instead." + + with tm.assert_produces_warning(FutureWarning, match=msg): + to_offset("2w") + + @pytest.mark.parametrize( "freq_depr", [ @@ -185,18 +194,16 @@ def test_anchored_shortcuts(shortcut, expected): "2qs-feb", "2bqs", "2sms", + "1sme", "2bms", "2cbme", "2me", - "2w", ], ) -def test_to_offset_lowercase_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.upper()[1:]}' instead." +def test_to_offset_lowercase_frequency_raises(freq_depr): + msg = f"Invalid frequency: {freq_depr}" - with tm.assert_produces_warning(FutureWarning, match=depr_msg): + with pytest.raises(ValueError, match=msg): to_offset(freq_depr) From 363395a9f5594f315a65b2db717f75a04b1317ec Mon Sep 17 00:00:00 2001 From: Natalia Mokeeva Date: Mon, 25 Mar 2024 14:48:15 +0100 Subject: [PATCH 03/17] fix mypy error --- pandas/tests/tslibs/test_to_offset.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pandas/tests/tslibs/test_to_offset.py b/pandas/tests/tslibs/test_to_offset.py index 5551492c73723..07bdfca8f2f2d 100644 --- a/pandas/tests/tslibs/test_to_offset.py +++ b/pandas/tests/tslibs/test_to_offset.py @@ -178,8 +178,7 @@ def test_anchored_shortcuts(shortcut, expected): def test_to_offset_lowercase_frequency_w_deprecated(): # GH#54939 - msg = "'w' is deprecated and will be removed in a " - "future version, please use 'W' instead." + msg = "'w' is deprecated and will be removed in a future version" with tm.assert_produces_warning(FutureWarning, match=msg): to_offset("2w") From d6092b31de4612f45bb98aa289491ec019a0eb4a Mon Sep 17 00:00:00 2001 From: Natalia Mokeeva Date: Mon, 25 Mar 2024 15:02:55 +0100 Subject: [PATCH 04/17] add a note to v3.0.0 --- doc/source/whatsnew/v3.0.0.rst | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index f748f6e23e003..ee01ac878dc8e 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -189,6 +189,34 @@ Other Deprecations Removal of prior version deprecations/changes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Enforced deprecation of aliases ``M``, ``Q``, ``Y``, etc. in favour of ``ME``, ``QE``, ``YE``, etc. for offsets +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Removed the following frequency aliases (:issue:`57986`): + ++-------------------------------+------------------+------------------+ +|offsets |removed aliases |new aliases | ++===============================+==================+==================+ +|:class:`MonthEnd` | ``M`` | ``ME`` | ++-------------------------------+------------------+------------------+ +|:class:`BusinessMonthEnd` | ``BM`` | ``BME`` | ++-------------------------------+------------------+------------------+ +|:class:`SemiMonthEnd` | ``SM`` | ``SME`` | ++-------------------------------+------------------+------------------+ +|:class:`CustomBusinessMonthEnd`| ``CBM`` | ``CBME`` | ++-------------------------------+------------------+------------------+ +|:class:`QuarterEnd` | ``Q`` | ``QE`` | ++-------------------------------+------------------+------------------+ +|:class:`BQuarterEnd` | ``BQ`` | ``BQE`` | ++-------------------------------+------------------+------------------+ +|:class:`YearEnd` | ``Y`` | ``YE`` | ++-------------------------------+------------------+------------------+ +|:class:`BYearEnd` | ``BY`` | ``BYE`` | ++-------------------------------+------------------+------------------+ + +Other Removal +^^^^^^^^^^^^^ - :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`) - :func:`read_excel`, :func:`read_json`, :func:`read_html`, and :func:`read_xml` no longer accept raw string or byte representation of the data. That type of data must be wrapped in a :py:class:`StringIO` or :py:class:`BytesIO` (:issue:`53767`) - :meth:`DataFrame.groupby` with ``as_index=False`` and aggregation methods will no longer exclude from the result the groupings that do not arise from the input (:issue:`49519`) @@ -208,7 +236,7 @@ Removal of prior version deprecations/changes - 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 ``T``, ``L``, ``U``, and ``N`` denoting frequencies in :class:`Minute`, :class:`Second`, :class:`Milli`, :class:`Micro`, :class:`Nano` (:issue:`57627`) +- 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`) - Enforced deprecation of values "pad", "ffill", "bfill", and "backfill" for :meth:`Series.interpolate` and :meth:`DataFrame.interpolate` (:issue:`57869`) From f5fd5ffbf830db6254562148bbb653d0610d2ba1 Mon Sep 17 00:00:00 2001 From: Natalia Mokeeva Date: Thu, 28 Mar 2024 14:24:05 +0100 Subject: [PATCH 05/17] remove c_REVERSE_OFFSET_REMOVED_FREQSTR, correct tests --- pandas/_libs/tslibs/dtypes.pxd | 1 - pandas/_libs/tslibs/dtypes.pyx | 3 --- pandas/_libs/tslibs/offsets.pyx | 12 ++---------- pandas/tests/indexes/period/test_constructors.py | 2 +- pandas/tests/resample/test_period_index.py | 2 +- pandas/tests/scalar/period/test_period.py | 2 +- 6 files changed, 5 insertions(+), 17 deletions(-) diff --git a/pandas/_libs/tslibs/dtypes.pxd b/pandas/_libs/tslibs/dtypes.pxd index 3612a94431955..622e08c274f0f 100644 --- a/pandas/_libs/tslibs/dtypes.pxd +++ b/pandas/_libs/tslibs/dtypes.pxd @@ -13,7 +13,6 @@ cdef bint is_supported_unit(NPY_DATETIMEUNIT reso) cdef dict c_OFFSET_TO_PERIOD_FREQSTR cdef dict c_OFFSET_REMOVED_FREQSTR -cdef dict c_REVERSE_OFFSET_REMOVED_FREQSTR cdef dict c_DEPR_ABBREVS cdef dict attrname_to_abbrevs cdef dict npy_unit_to_attrname diff --git a/pandas/_libs/tslibs/dtypes.pyx b/pandas/_libs/tslibs/dtypes.pyx index 23b8836b50c5a..5b54bf41b0c91 100644 --- a/pandas/_libs/tslibs/dtypes.pyx +++ b/pandas/_libs/tslibs/dtypes.pyx @@ -304,9 +304,6 @@ cdef dict c_OFFSET_REMOVED_FREQSTR = { "BQ-NOV": "BQE-NOV", } cdef dict c_OFFSET_TO_PERIOD_FREQSTR = OFFSET_TO_PERIOD_FREQSTR -cdef dict c_REVERSE_OFFSET_REMOVED_FREQSTR = { - v: k for k, v in c_OFFSET_REMOVED_FREQSTR.items() -} # Map deprecated resolution abbreviations to correct resolution abbreviations cdef dict c_DEPR_ABBREVS = { diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index b209a682120f2..f5a798351a414 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -58,7 +58,6 @@ from pandas._libs.tslibs.conversion cimport localize_pydatetime from pandas._libs.tslibs.dtypes cimport ( c_DEPR_ABBREVS, c_OFFSET_REMOVED_FREQSTR, - c_REVERSE_OFFSET_REMOVED_FREQSTR, periods_per_day, ) from pandas._libs.tslibs.nattype cimport ( @@ -4858,21 +4857,14 @@ cpdef to_offset(freq, bint is_period=False): raise ValueError( f"\'{name}\' is no longer supported for offsets." ) - if is_period and name.upper() in c_REVERSE_OFFSET_REMOVED_FREQSTR: - if name.upper().startswith("Y"): - raise ValueError( - f"for Period, please use \'Y{name.upper()[2:]}\' " - f"instead of \'{name}\'" - ) + if is_period and name.upper().split("-")[0].endswith(("E")): if (name.upper().startswith("B") or name.upper().startswith("S") or name.upper().startswith("C")): raise ValueError(INVALID_FREQ_ERR_MSG.format(name)) else: raise ValueError( - f"for Period, please use " - f"\'{c_REVERSE_OFFSET_REMOVED_FREQSTR.get(name.upper())}\' " - f"instead of \'{name}\'" + f"{name} is not supported as period frequency" ) elif is_period and name.upper() in c_OFFSET_REMOVED_FREQSTR: if name.upper() != name: diff --git a/pandas/tests/indexes/period/test_constructors.py b/pandas/tests/indexes/period/test_constructors.py index ec2216c102c3f..e5a6ce386c135 100644 --- a/pandas/tests/indexes/period/test_constructors.py +++ b/pandas/tests/indexes/period/test_constructors.py @@ -33,7 +33,7 @@ class TestPeriodIndexDisallowedFreqs: ) def test_period_index_offsets_frequency_error_message(self, freq, freq_depr): # GH#52064 - msg = f"for Period, please use '{freq[1:]}' instead of '{freq_depr[1:]}'" + msg = f"{freq_depr[1:]} is not supported as period frequency" with pytest.raises(ValueError, match=msg): PeriodIndex(["2020-01-01", "2020-01-02"], freq=freq_depr) diff --git a/pandas/tests/resample/test_period_index.py b/pandas/tests/resample/test_period_index.py index dd058ada60974..89d09c70dbb7e 100644 --- a/pandas/tests/resample/test_period_index.py +++ b/pandas/tests/resample/test_period_index.py @@ -1055,7 +1055,7 @@ def test_asfreq_invalid_period_offset(self, offset, frame_or_series): ) def test_resample_frequency_ME_QE_YE_error_message(frame_or_series, freq, freq_depr): # GH#9586 - msg = f"for Period, please use '{freq[1:]}' instead of '{freq_depr[1:]}'" + msg = f"{freq_depr[1:]} is not supported as period frequency" obj = frame_or_series(range(5), index=period_range("2020-01-01", periods=5)) with pytest.raises(ValueError, match=msg): diff --git a/pandas/tests/scalar/period/test_period.py b/pandas/tests/scalar/period/test_period.py index 2c3a0816737fc..c77e04dbc2b16 100644 --- a/pandas/tests/scalar/period/test_period.py +++ b/pandas/tests/scalar/period/test_period.py @@ -60,7 +60,7 @@ def test_invalid_frequency_error_message(self): Period("2012-01-02", freq="WOM-1MON") def test_invalid_frequency_period_error_message(self): - msg = "for Period, please use 'M' instead of 'ME'" + msg = "ME is not supported as period frequency" with pytest.raises(ValueError, match=msg): Period("2012-01-02", freq="ME") From 6a124ae5fa8734e816879fefa305e81130ba6726 Mon Sep 17 00:00:00 2001 From: Natalia Mokeeva Date: Thu, 28 Mar 2024 15:00:04 +0100 Subject: [PATCH 06/17] fix test_to_period_offsets_not_supported --- .../indexes/datetimes/methods/test_to_period.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/pandas/tests/indexes/datetimes/methods/test_to_period.py b/pandas/tests/indexes/datetimes/methods/test_to_period.py index 20bcf52eb0c2f..3a4f9acebe2ca 100644 --- a/pandas/tests/indexes/datetimes/methods/test_to_period.py +++ b/pandas/tests/indexes/datetimes/methods/test_to_period.py @@ -204,10 +204,16 @@ def test_to_period_nofreq(self): assert idx.freqstr is None tm.assert_index_equal(idx.to_period(), expected) - @pytest.mark.parametrize("freq", ["2BMS", "1SME-15"]) - def test_to_period_offsets_not_supported(self, freq): + @pytest.mark.parametrize( + "freq, msg", + [ + ("2BME", "Invalid frequency: 2BME"), + ("1SME-15", "Invalid frequency: SME-15"), + ("2BMS", "BMS is not supported as period frequency"), + ], + ) + def test_to_period_offsets_not_supported(self, freq, msg): # GH#56243 - msg = f"{freq[1:]} is not supported as period frequency" ts = date_range("1/1/2012", periods=4, freq=freq) with pytest.raises(ValueError, match=msg): ts.to_period() From 19f35cc4a09cf836e3e9e7a01e8cf6a2c990c501 Mon Sep 17 00:00:00 2001 From: Natalia Mokeeva Date: Mon, 1 Apr 2024 21:48:59 +0200 Subject: [PATCH 07/17] correct to_offset, fix tests --- pandas/_libs/tslibs/offsets.pyx | 31 ++++---- .../datetimes/methods/test_to_period.py | 2 +- .../tests/indexes/period/test_constructors.py | 21 ++++-- .../tests/indexes/period/test_period_range.py | 21 ++++-- pandas/tests/resample/test_period_index.py | 75 ++++++++----------- 5 files changed, 81 insertions(+), 69 deletions(-) diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index f5a798351a414..1971830080bdc 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -58,6 +58,7 @@ from pandas._libs.tslibs.conversion cimport localize_pydatetime from pandas._libs.tslibs.dtypes cimport ( c_DEPR_ABBREVS, c_OFFSET_REMOVED_FREQSTR, + c_OFFSET_TO_PERIOD_FREQSTR, periods_per_day, ) from pandas._libs.tslibs.nattype cimport ( @@ -4848,33 +4849,33 @@ cpdef to_offset(freq, bint is_period=False): for n, (sep, stride, name) in enumerate(tups): if not is_period and name.upper() in c_OFFSET_REMOVED_FREQSTR: raise ValueError( - f"\'{name}\' is no longer supported for offsets." + f"\'{name}\' is no longer supported for offsets. Please use " + f"\'{c_OFFSET_REMOVED_FREQSTR.get(name.upper())}\' instead." ) if (not is_period and name != name.upper() and name.lower() not in {"s", "ms", "us", "ns"} and name.upper().split("-")[0].endswith(("S", "E"))): - raise ValueError( - f"\'{name}\' is no longer supported for offsets." - ) - if is_period and name.upper().split("-")[0].endswith(("E")): - if (name.upper().startswith("B") or - name.upper().startswith("S") or - name.upper().startswith("C")): + if name.upper() in c_OFFSET_TO_PERIOD_FREQSTR: + raise ValueError( + f"\'{name}\' is no longer supported for offsets. Please " + f"use \'{name.upper()}\' instead." + ) + else: + raise ValueError(INVALID_FREQ_ERR_MSG.format(name)) + if (is_period and + name.upper().split("-")[0].endswith(("E")) and + name.upper() in c_OFFSET_TO_PERIOD_FREQSTR): + if name.upper().startswith("B"): raise ValueError(INVALID_FREQ_ERR_MSG.format(name)) else: raise ValueError( f"{name} is not supported as period frequency" ) + elif is_period and name.upper() in c_OFFSET_REMOVED_FREQSTR: if name.upper() != name: - warnings.warn( - f"\'{name}\' is deprecated and will be removed in " - f"a future version, please use \'{name.upper()}\' " - f"instead.", - FutureWarning, - stacklevel=find_stack_level(), - ) + raise ValueError(INVALID_FREQ_ERR_MSG.format(name)) name = c_OFFSET_REMOVED_FREQSTR.get(name.upper()) if sep != "" and not sep.isspace(): diff --git a/pandas/tests/indexes/datetimes/methods/test_to_period.py b/pandas/tests/indexes/datetimes/methods/test_to_period.py index 3a4f9acebe2ca..9b5cab1000ce0 100644 --- a/pandas/tests/indexes/datetimes/methods/test_to_period.py +++ b/pandas/tests/indexes/datetimes/methods/test_to_period.py @@ -208,7 +208,7 @@ def test_to_period_nofreq(self): "freq, msg", [ ("2BME", "Invalid frequency: 2BME"), - ("1SME-15", "Invalid frequency: SME-15"), + ("1SME-15", "SME-15 is not supported as period frequency"), ("2BMS", "BMS is not supported as period frequency"), ], ) diff --git a/pandas/tests/indexes/period/test_constructors.py b/pandas/tests/indexes/period/test_constructors.py index e5a6ce386c135..3f7bf42c2ce11 100644 --- a/pandas/tests/indexes/period/test_constructors.py +++ b/pandas/tests/indexes/period/test_constructors.py @@ -41,15 +41,26 @@ def test_period_index_offsets_frequency_error_message(self, freq, freq_depr): with pytest.raises(ValueError, match=msg): period_range(start="2020-01-01", end="2020-01-02", freq=freq_depr) - @pytest.mark.parametrize("freq_depr", ["2SME", "2sme", "2CBME", "2BYE", "2Bye"]) - def test_period_index_frequency_invalid_freq(self, freq_depr): + @pytest.mark.parametrize( + "freq, invalid", + [ + ("2SME", True), + ("2sme", True), + ("2BYE", True), + ("2Bye", True), + ("2CBME", False), + ], + ) + def test_period_index_frequency_invalid_freq(self, freq, invalid): # GH#9586 - msg = f"Invalid frequency: {freq_depr[1:]}" + msg = f"Invalid frequency: {freq}" + if not invalid: + msg = f"{freq[1:]} is not supported as period frequency" with pytest.raises(ValueError, match=msg): - period_range("2020-01", "2020-05", freq=freq_depr) + period_range("2020-01", "2020-05", freq=freq) with pytest.raises(ValueError, match=msg): - PeriodIndex(["2020-01", "2020-05"], freq=freq_depr) + PeriodIndex(["2020-01", "2020-05"], freq=freq) @pytest.mark.parametrize("freq", ["2BQE-SEP", "2BYE-MAR", "2BME"]) def test_period_index_from_datetime_index_invalid_freq(self, freq): diff --git a/pandas/tests/indexes/period/test_period_range.py b/pandas/tests/indexes/period/test_period_range.py index fb200d071951e..81a88b441fe3d 100644 --- a/pandas/tests/indexes/period/test_period_range.py +++ b/pandas/tests/indexes/period/test_period_range.py @@ -214,14 +214,23 @@ 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_depr", ["2m", "2q-sep", "2y", "2w"]) - def test_lowercase_freq_deprecated_from_time_series(self, freq_depr): + @pytest.mark.parametrize( + "freq, removed", + [("2m", True), ("2q-sep", True), ("2y", True), ("2w", False)], + ) + def test_lowercase_freq_from_time_series_raises(self, freq, removed): # GH#52536, GH#54939 - msg = f"'{freq_depr[1:]}' is deprecated and will be removed in a " - f"future version. Please use '{freq_depr.upper()[1:]}' instead." + msg = f"Invalid frequency: {freq}" + if not removed: + msg = f"'{freq[1:]}' is deprecated and will be removed in a " + f"future version. Please use '{freq.upper()[1:]}' instead." - with tm.assert_produces_warning(FutureWarning, match=msg): - period_range(freq=freq_depr, start="1/1/2001", end="12/1/2009") + with tm.assert_produces_warning(FutureWarning, match=msg): + period_range(freq=freq, start="1/1/2001", end="12/1/2009") + + else: + with pytest.raises(ValueError, match=msg): + period_range(freq=freq, start="1/1/2001", end="12/1/2009") @pytest.mark.parametrize("freq", ["2A", "2a", "2A-AUG", "2A-aug"]) def test_A_raises_from_time_series(self, freq): diff --git a/pandas/tests/resample/test_period_index.py b/pandas/tests/resample/test_period_index.py index 89d09c70dbb7e..12ee15fc81ce2 100644 --- a/pandas/tests/resample/test_period_index.py +++ b/pandas/tests/resample/test_period_index.py @@ -998,30 +998,22 @@ def test_resample_t_l_deprecated(self): ser.resample("T").mean() @pytest.mark.parametrize( - "freq, freq_depr, freq_res, freq_depr_res, data", + "freq, freq_depr, freq_depr_res", [ - ("2Q", "2q", "2Y", "2y", [0.5]), - ("2M", "2m", "2Q", "2q", [1.0, 3.0]), + ("2Q", "2q", "2y"), + ("2M", "2m", "2q"), ], ) - def test_resample_lowercase_frequency_deprecated( - self, freq, freq_depr, freq_res, freq_depr_res, data - ): - depr_msg = f"'{freq_depr[1:]}' is deprecated and will be removed in a " - f"future version. Please use '{freq[1:]}' instead." - depr_msg_res = f"'{freq_depr_res[1:]}' is deprecated and will be removed in a " - f"future version. Please use '{freq_res[1:]}' instead." - - with tm.assert_produces_warning(FutureWarning, match=depr_msg): - rng_l = period_range("2020-01-01", "2020-08-01", freq=freq_depr) - ser = Series(np.arange(len(rng_l)), index=rng_l) - - rng = period_range("2020-01-01", "2020-08-01", freq=freq_res) - expected = Series(data=data, index=rng) + def test_resample_lowercase_frequency_raises(self, freq, freq_depr, freq_depr_res): + msg = f"Invalid frequency: {freq_depr}" + with pytest.raises(ValueError, match=msg): + period_range("2020-01-01", "2020-08-01", freq=freq_depr) - with tm.assert_produces_warning(FutureWarning, match=depr_msg_res): - result = ser.resample(freq_depr_res).mean() - tm.assert_series_equal(result, expected) + msg = f"Invalid frequency: {freq_depr_res}" + rng = period_range("2020-01-01", "2020-08-01", freq=freq) + ser = Series(np.arange(len(rng)), index=rng) + with pytest.raises(ValueError, match=msg): + ser.resample(freq_depr_res).mean() @pytest.mark.parametrize( "offset", @@ -1041,25 +1033,26 @@ def test_asfreq_invalid_period_offset(self, offset, frame_or_series): @pytest.mark.parametrize( - "freq,freq_depr", + "freq", [ - ("2M", "2ME"), - ("2Q", "2QE"), - ("2Q-FEB", "2QE-FEB"), - ("2Y", "2YE"), - ("2Y-MAR", "2YE-MAR"), - ("2M", "2me"), - ("2Q", "2qe"), - ("2Y-MAR", "2ye-mar"), + ("2ME"), + ("2QE"), + ("2QE-FEB"), + ("2YE"), + ("2YE-MAR"), + ("2me"), + ("2qe"), + ("2ye-mar"), ], ) -def test_resample_frequency_ME_QE_YE_error_message(frame_or_series, freq, freq_depr): +def test_resample_frequency_ME_QE_YE_raises(frame_or_series, freq): # GH#9586 - msg = f"{freq_depr[1:]} is not supported as period frequency" + msg = f"{freq[1:]} is not supported as period frequency" obj = frame_or_series(range(5), index=period_range("2020-01-01", periods=5)) + msg = f"Invalid frequency: {freq}" with pytest.raises(ValueError, match=msg): - obj.resample(freq_depr) + obj.resample(freq) def test_corner_cases_period(simple_period_range_series): @@ -1073,19 +1066,17 @@ def test_corner_cases_period(simple_period_range_series): @pytest.mark.parametrize( - "freq_depr", + "freq,msg", [ - "2BME", - "2CBME", - "2SME", - "2BQE-FEB", - "2BYE-MAR", + ("2BME", "Invalid frequency: BME"), + ("2CBME", "CBME is not supported as period frequency"), + ("2SME", "SME is not supported as period frequency"), + ("2BQE-FEB", "Invalid frequency: BQE-FEB"), + ("2BYE-MAR", "Invalid frequency: BYE-MAR"), ], ) -def test_resample_frequency_invalid_freq(frame_or_series, freq_depr): +def test_resample_frequency_invalid_freq(frame_or_series, freq, msg): # GH#9586 - msg = f"Invalid frequency: {freq_depr[1:]}" - obj = frame_or_series(range(5), index=period_range("2020-01-01", periods=5)) with pytest.raises(ValueError, match=msg): - obj.resample(freq_depr) + obj.resample(freq) From 8b9edf4fc28d2e1b305129db891a6ba61e34b17f Mon Sep 17 00:00:00 2001 From: Natalia Mokeeva Date: Fri, 12 Apr 2024 10:47:18 +0200 Subject: [PATCH 08/17] add dict PERIOD_TO_OFFSET_FREQSTR, corect meth to_offset, fix tests --- pandas/_libs/tslibs/dtypes.pxd | 1 + pandas/_libs/tslibs/dtypes.pyx | 80 ++++++++++++------- pandas/_libs/tslibs/offsets.pyx | 43 +++++----- pandas/tests/dtypes/test_dtypes.py | 2 +- pandas/tests/frame/methods/test_asfreq.py | 2 +- .../datetimes/methods/test_to_period.py | 13 +-- .../indexes/period/methods/test_asfreq.py | 2 +- .../tests/indexes/period/test_constructors.py | 22 ++--- .../tests/indexes/period/test_period_range.py | 36 ++++----- pandas/tests/resample/test_period_index.py | 15 +--- pandas/tests/scalar/period/test_asfreq.py | 10 +-- pandas/tests/scalar/period/test_period.py | 12 +-- 12 files changed, 114 insertions(+), 124 deletions(-) diff --git a/pandas/_libs/tslibs/dtypes.pxd b/pandas/_libs/tslibs/dtypes.pxd index 622e08c274f0f..c6c742c87b755 100644 --- a/pandas/_libs/tslibs/dtypes.pxd +++ b/pandas/_libs/tslibs/dtypes.pxd @@ -12,6 +12,7 @@ cdef NPY_DATETIMEUNIT get_supported_reso(NPY_DATETIMEUNIT reso) 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_REMOVED_FREQSTR cdef dict c_DEPR_ABBREVS cdef dict attrname_to_abbrevs diff --git a/pandas/_libs/tslibs/dtypes.pyx b/pandas/_libs/tslibs/dtypes.pyx index 5b54bf41b0c91..6e1e5eb7e5c5b 100644 --- a/pandas/_libs/tslibs/dtypes.pyx +++ b/pandas/_libs/tslibs/dtypes.pyx @@ -176,6 +176,10 @@ OFFSET_TO_PERIOD_FREQSTR: dict = { "EOM": "M", "BME": "M", "SME": "M", + "BMS": "M", + "CBME": "M", + "CBMS": "M", + "SMS": "M", "BQS": "Q", "QS": "Q", "BQE": "Q", @@ -273,37 +277,55 @@ cdef dict c_OFFSET_REMOVED_FREQSTR = { "Y-SEP": "YE-SEP", "Y-OCT": "YE-OCT", "Y-NOV": "YE-NOV", - "BY": "BYE", - "BY-DEC": "BYE-DEC", - "BY-JAN": "BYE-JAN", - "BY-FEB": "BYE-FEB", - "BY-MAR": "BYE-MAR", - "BY-APR": "BYE-APR", - "BY-MAY": "BYE-MAY", - "BY-JUN": "BYE-JUN", - "BY-JUL": "BYE-JUL", - "BY-AUG": "BYE-AUG", - "BY-SEP": "BYE-SEP", - "BY-OCT": "BYE-OCT", - "BY-NOV": "BYE-NOV", - "BM": "BME", - "CBM": "CBME", - "SM": "SME", - "BQ": "BQE", - "BQ-DEC": "BQE-DEC", - "BQ-JAN": "BQE-JAN", - "BQ-FEB": "BQE-FEB", - "BQ-MAR": "BQE-MAR", - "BQ-APR": "BQE-APR", - "BQ-MAY": "BQE-MAY", - "BQ-JUN": "BQE-JUN", - "BQ-JUL": "BQE-JUL", - "BQ-AUG": "BQE-AUG", - "BQ-SEP": "BQE-SEP", - "BQ-OCT": "BQE-OCT", - "BQ-NOV": "BQE-NOV", +} +PERIOD_TO_OFFSET_FREQSTR = { + "D": "D", + "B": "B", + "W": "W", + "W-SUN": "W-SUN", + "W-MON": "W-MON", + "W-TUE": "W-TUE", + "W-WED": "W-WED", + "W-THU": "W-THU", + "W-FRI": "W-FRI", + "W-SAT": "W-SAT", + "M": "ME", + "Q": "QE", + "Q-DEC": "QE-DEC", + "Q-JAN": "QE-JAN", + "Q-FEB": "QE-FEB", + "Q-MAR": "QE-MAR", + "Q-APR": "QE-APR", + "Q-MAY": "QE-MAY", + "Q-JUN": "QE-JUN", + "Q-JUL": "QE-JUL", + "Q-AUG": "QE-AUG", + "Q-SEP": "QE-SEP", + "Q-OCT": "QE-OCT", + "Q-NOV": "QE-NOV", + "Y": "YE", + "Y-DEC": "YE-DEC", + "Y-JAN": "YE-JAN", + "Y-FEB": "YE-FEB", + "Y-MAR": "YE-MAR", + "Y-APR": "YE-APR", + "Y-MAY": "YE-MAY", + "Y-JUN": "YE-JUN", + "Y-JUL": "YE-JUL", + "Y-AUG": "YE-AUG", + "Y-SEP": "YE-SEP", + "Y-OCT": "YE-OCT", + "Y-NOV": "YE-NOV", + "Min": "min", + "min": "min", + "s": "s", + "ms": "ms", + "us": "us", + "ns": "ns", + "h": "h", } 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 = { diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index 1971830080bdc..8ed884139e430 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -59,6 +59,7 @@ from pandas._libs.tslibs.dtypes cimport ( c_DEPR_ABBREVS, c_OFFSET_REMOVED_FREQSTR, c_OFFSET_TO_PERIOD_FREQSTR, + c_PERIOD_TO_OFFSET_FREQSTR, periods_per_day, ) from pandas._libs.tslibs.nattype cimport ( @@ -4847,37 +4848,29 @@ cpdef to_offset(freq, bint is_period=False): tups = zip(split[0::4], split[1::4], split[2::4]) for n, (sep, stride, name) in enumerate(tups): - if not is_period and name.upper() in c_OFFSET_REMOVED_FREQSTR: - raise ValueError( - f"\'{name}\' is no longer supported for offsets. Please use " - f"\'{c_OFFSET_REMOVED_FREQSTR.get(name.upper())}\' instead." - ) - if (not is_period and - name != name.upper() and - name.lower() not in {"s", "ms", "us", "ns"} and - name.upper().split("-")[0].endswith(("S", "E"))): - if name.upper() in c_OFFSET_TO_PERIOD_FREQSTR: + if not is_period: + if name.upper() in c_OFFSET_REMOVED_FREQSTR: raise ValueError( f"\'{name}\' is no longer supported for offsets. Please " - f"use \'{name.upper()}\' instead." + f"use \'{c_OFFSET_REMOVED_FREQSTR.get(name.upper())}\' " + f"instead." ) - else: + # below we raise for lowrecase monthly and bigger frequencies + if (name.upper() != name and + name.lower() not in {"h", "min", "s", "ms", "us", "ns"} and + name.upper() not in c_PERIOD_TO_OFFSET_FREQSTR and + name.upper() in c_OFFSET_TO_PERIOD_FREQSTR): raise ValueError(INVALID_FREQ_ERR_MSG.format(name)) - if (is_period and - name.upper().split("-")[0].endswith(("E")) and - name.upper() in c_OFFSET_TO_PERIOD_FREQSTR): - if name.upper().startswith("B"): - raise ValueError(INVALID_FREQ_ERR_MSG.format(name)) - else: + if is_period: + if name in c_PERIOD_TO_OFFSET_FREQSTR: + name = c_PERIOD_TO_OFFSET_FREQSTR[name] + elif name in {"d", "b"}: + name = name.upper() + elif (name.upper() not in {"B", "D"} and + not name.upper().startswith("W")): raise ValueError( - f"{name} is not supported as period frequency" + f"\'{name}\' is not supported as period frequency." ) - - elif is_period and name.upper() in c_OFFSET_REMOVED_FREQSTR: - if name.upper() != name: - raise ValueError(INVALID_FREQ_ERR_MSG.format(name)) - name = c_OFFSET_REMOVED_FREQSTR.get(name.upper()) - if sep != "" and not sep.isspace(): raise ValueError("separator must be spaces") prefix = _lite_rule_alias.get(name) or name diff --git a/pandas/tests/dtypes/test_dtypes.py b/pandas/tests/dtypes/test_dtypes.py index c6da01636247d..cea7a1850ada8 100644 --- a/pandas/tests/dtypes/test_dtypes.py +++ b/pandas/tests/dtypes/test_dtypes.py @@ -445,7 +445,7 @@ def test_construction(self): def test_cannot_use_custom_businessday(self): # GH#52534 - msg = "C is not supported as period frequency" + msg = "Invalid frequency: C" msg1 = " is not supported as period frequency" msg2 = r"PeriodDtype\[B\] is deprecated" with pytest.raises(ValueError, match=msg): diff --git a/pandas/tests/frame/methods/test_asfreq.py b/pandas/tests/frame/methods/test_asfreq.py index 88cbef4f55b2a..1c3c41e2e0299 100644 --- a/pandas/tests/frame/methods/test_asfreq.py +++ b/pandas/tests/frame/methods/test_asfreq.py @@ -259,7 +259,7 @@ def test_asfreq_frequency_M_Q_Y_raises(self, freq, freq_depr): [ ( "2MS", - "MS is not supported as period frequency", + "Invalid frequency: 2MS", ), ( offsets.MonthBegin(), diff --git a/pandas/tests/indexes/datetimes/methods/test_to_period.py b/pandas/tests/indexes/datetimes/methods/test_to_period.py index 9b5cab1000ce0..bf97ad9106452 100644 --- a/pandas/tests/indexes/datetimes/methods/test_to_period.py +++ b/pandas/tests/indexes/datetimes/methods/test_to_period.py @@ -204,16 +204,11 @@ def test_to_period_nofreq(self): assert idx.freqstr is None tm.assert_index_equal(idx.to_period(), expected) - @pytest.mark.parametrize( - "freq, msg", - [ - ("2BME", "Invalid frequency: 2BME"), - ("1SME-15", "SME-15 is not supported as period frequency"), - ("2BMS", "BMS is not supported as period frequency"), - ], - ) - def test_to_period_offsets_not_supported(self, freq, msg): + @pytest.mark.parametrize("freq", ["2BME", "SME-15", "2BMS"]) + def test_to_period_offsets_not_supported(self, freq): # GH#56243 + msg = f"Invalid frequency: {freq}" + ts = date_range("1/1/2012", periods=4, freq=freq) with pytest.raises(ValueError, match=msg): ts.to_period() diff --git a/pandas/tests/indexes/period/methods/test_asfreq.py b/pandas/tests/indexes/period/methods/test_asfreq.py index ea305a9766103..4204e15714c97 100644 --- a/pandas/tests/indexes/period/methods/test_asfreq.py +++ b/pandas/tests/indexes/period/methods/test_asfreq.py @@ -154,7 +154,7 @@ def test_asfreq_with_different_n(self): def test_pi_asfreq_not_supported_frequency(self, freq, is_str): # GH#55785, GH#56945 if is_str: - msg = f"{freq[1:]} is not supported as period frequency" + msg = f"Invalid frequency: {freq}" else: msg = re.escape(f"{freq} is not supported as period frequency") diff --git a/pandas/tests/indexes/period/test_constructors.py b/pandas/tests/indexes/period/test_constructors.py index 3f7bf42c2ce11..438d8ecb54219 100644 --- a/pandas/tests/indexes/period/test_constructors.py +++ b/pandas/tests/indexes/period/test_constructors.py @@ -33,7 +33,7 @@ class TestPeriodIndexDisallowedFreqs: ) def test_period_index_offsets_frequency_error_message(self, freq, freq_depr): # GH#52064 - msg = f"{freq_depr[1:]} is not supported as period frequency" + msg = f"Invalid frequency: {freq_depr}" with pytest.raises(ValueError, match=msg): PeriodIndex(["2020-01-01", "2020-01-02"], freq=freq_depr) @@ -42,21 +42,13 @@ def test_period_index_offsets_frequency_error_message(self, freq, freq_depr): period_range(start="2020-01-01", end="2020-01-02", freq=freq_depr) @pytest.mark.parametrize( - "freq, invalid", - [ - ("2SME", True), - ("2sme", True), - ("2BYE", True), - ("2Bye", True), - ("2CBME", False), - ], + "freq", + ["2SME", "2sme", "2BYE", "2Bye", "2CBME"], ) - def test_period_index_frequency_invalid_freq(self, freq, invalid): + def test_period_index_frequency_invalid_freq(self, freq): # GH#9586 msg = f"Invalid frequency: {freq}" - if not invalid: - msg = f"{freq[1:]} is not supported as period frequency" with pytest.raises(ValueError, match=msg): period_range("2020-01", "2020-05", freq=freq) with pytest.raises(ValueError, match=msg): @@ -65,7 +57,7 @@ def test_period_index_frequency_invalid_freq(self, freq, invalid): @pytest.mark.parametrize("freq", ["2BQE-SEP", "2BYE-MAR", "2BME"]) def test_period_index_from_datetime_index_invalid_freq(self, freq): # GH#56899 - msg = f"Invalid frequency: {freq[1:]}" + msg = f"Invalid frequency: {freq}" rng = date_range("01-Jan-2012", periods=8, freq=freq) with pytest.raises(ValueError, match=msg): @@ -555,9 +547,7 @@ def test_mixed_freq_raises(self): with tm.assert_produces_warning(FutureWarning, match=msg): end_intv = Period("2005-05-01", "B") - msg = "'w' is deprecated and will be removed in a future version." - with tm.assert_produces_warning(FutureWarning, match=msg): - vals = [end_intv, Period("2006-12-31", "w")] + vals = [end_intv, Period("2006-12-31", "W")] msg = r"Input has different freq=W-SUN from PeriodIndex\(freq=B\)" depr_msg = r"PeriodDtype\[B\] is deprecated" with pytest.raises(IncompatibleFrequency, match=msg): diff --git a/pandas/tests/indexes/period/test_period_range.py b/pandas/tests/indexes/period/test_period_range.py index 81a88b441fe3d..8b6bb035aa636 100644 --- a/pandas/tests/indexes/period/test_period_range.py +++ b/pandas/tests/indexes/period/test_period_range.py @@ -181,10 +181,8 @@ def test_construction_from_period(self): def test_mismatched_start_end_freq_raises(self): depr_msg = "Period with BDay freq is deprecated" - msg = "'w' is deprecated and will be removed in a future version." - with tm.assert_produces_warning(FutureWarning, match=msg): - end_w = Period("2006-12-31", "1w") + end_w = Period("2006-12-31", "1W") with tm.assert_produces_warning(FutureWarning, match=depr_msg): start_b = Period("02-Apr-2005", "B") end_b = Period("2005-05-01", "B") @@ -208,29 +206,18 @@ def test_constructor_U(self): @pytest.mark.parametrize("freq_depr", ["2H", "2MIN", "2S", "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 " - f"future version. Please use '{freq_depr.lower()[1:]}' instead." + msg = f"Invalid frequency: {freq_depr}" - with tm.assert_produces_warning(FutureWarning, match=msg): + with pytest.raises(ValueError, match=msg): period_range("2020-01-01 00:00:00 00:00", periods=2, freq=freq_depr) - @pytest.mark.parametrize( - "freq, removed", - [("2m", True), ("2q-sep", True), ("2y", True), ("2w", False)], - ) - def test_lowercase_freq_from_time_series_raises(self, freq, removed): + @pytest.mark.parametrize("freq", ["2m", "2q-sep", "2y"]) + def test_lowercase_freq_from_time_series_raises(self, freq): # GH#52536, GH#54939 msg = f"Invalid frequency: {freq}" - if not removed: - msg = f"'{freq[1:]}' is deprecated and will be removed in a " - f"future version. Please use '{freq.upper()[1:]}' instead." - - with tm.assert_produces_warning(FutureWarning, match=msg): - period_range(freq=freq, start="1/1/2001", end="12/1/2009") - else: - with pytest.raises(ValueError, match=msg): - period_range(freq=freq, start="1/1/2001", end="12/1/2009") + with pytest.raises(ValueError, match=msg): + period_range(freq=freq, start="1/1/2001", end="12/1/2009") @pytest.mark.parametrize("freq", ["2A", "2a", "2A-AUG", "2A-aug"]) def test_A_raises_from_time_series(self, freq): @@ -238,3 +225,12 @@ def test_A_raises_from_time_series(self, freq): with pytest.raises(ValueError, match=msg): period_range(freq=freq, start="1/1/2001", end="12/1/2009") + + @pytest.mark.parametrize("freq", ["2w"]) + def test_lowercase_freq_from_time_series_deprecated(self, freq): + # GH#52536, GH#54939 + msg = f"'{freq[1:]}' is deprecated and will be removed in a " + f"future version. Please use '{freq.upper()[1:]}' instead." + + with tm.assert_produces_warning(FutureWarning, match=msg): + period_range(freq=freq, start="1/1/2001", end="12/1/2009") diff --git a/pandas/tests/resample/test_period_index.py b/pandas/tests/resample/test_period_index.py index 12ee15fc81ce2..2bde8f0d7e271 100644 --- a/pandas/tests/resample/test_period_index.py +++ b/pandas/tests/resample/test_period_index.py @@ -1065,18 +1065,11 @@ def test_corner_cases_period(simple_period_range_series): assert len(result) == 0 -@pytest.mark.parametrize( - "freq,msg", - [ - ("2BME", "Invalid frequency: BME"), - ("2CBME", "CBME is not supported as period frequency"), - ("2SME", "SME is not supported as period frequency"), - ("2BQE-FEB", "Invalid frequency: BQE-FEB"), - ("2BYE-MAR", "Invalid frequency: BYE-MAR"), - ], -) -def test_resample_frequency_invalid_freq(frame_or_series, freq, msg): +@pytest.mark.parametrize("freq", ["2BME", "2CBME", "2SME", "2BQE-FEB", "2BYE-MAR"]) +def test_resample_frequency_invalid_freq(frame_or_series, freq): # GH#9586 + msg = f"Invalid frequency: {freq}" + obj = frame_or_series(range(5), index=period_range("2020-01-01", periods=5)) with pytest.raises(ValueError, match=msg): obj.resample(freq) diff --git a/pandas/tests/scalar/period/test_asfreq.py b/pandas/tests/scalar/period/test_asfreq.py index 1a21d234f1d50..a607536c5ca5a 100644 --- a/pandas/tests/scalar/period/test_asfreq.py +++ b/pandas/tests/scalar/period/test_asfreq.py @@ -59,6 +59,7 @@ def test_asfreq_corner(self): def test_conv_annual(self): # frequency conversion tests: from Annual Frequency + msg = INVALID_FREQ_ERR_MSG ival_A = Period(freq="Y", year=2007) @@ -110,18 +111,15 @@ 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 = "'H' is deprecated and will be removed in a future version." - with tm.assert_produces_warning(FutureWarning, match=msg): + 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 - msg = "Invalid frequency: T" 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 = "'S' is deprecated and will be removed in a future version." - with tm.assert_produces_warning(FutureWarning, match=msg): + with pytest.raises(ValueError, match=msg): assert ival_A.asfreq("S", "S") == ival_A_to_S_start assert ival_A.asfreq("S", "E") == ival_A_to_S_end @@ -820,7 +818,7 @@ def test_asfreq_MS(self): assert initial.asfreq(freq="M", how="S") == Period("2013-01", "M") - msg = "MS is not supported as period frequency" + msg = INVALID_FREQ_ERR_MSG with pytest.raises(ValueError, match=msg): initial.asfreq(freq="MS", how="S") diff --git a/pandas/tests/scalar/period/test_period.py b/pandas/tests/scalar/period/test_period.py index c77e04dbc2b16..46c3af2b9a0f4 100644 --- a/pandas/tests/scalar/period/test_period.py +++ b/pandas/tests/scalar/period/test_period.py @@ -47,7 +47,7 @@ def test_offsets_not_supported(self, freq, freq_msg): def test_custom_business_day_freq_raises(self): # GH#52534 - msg = "C is not supported as period frequency" + msg = "Invalid frequency: C" with pytest.raises(ValueError, match=msg): Period("2023-04-10", freq="C") msg = f"{offsets.CustomBusinessDay().base} is not supported as period frequency" @@ -60,7 +60,7 @@ def test_invalid_frequency_error_message(self): Period("2012-01-02", freq="WOM-1MON") def test_invalid_frequency_period_error_message(self): - msg = "ME is not supported as period frequency" + msg = "Invalid frequency: ME" with pytest.raises(ValueError, match=msg): Period("2012-01-02", freq="ME") @@ -108,9 +108,11 @@ def test_construction(self): assert i1 == i3 i1 = Period("1982", freq="min") - msg = "'MIN' is deprecated and will be removed in a future version." - with tm.assert_produces_warning(FutureWarning, match=msg): - i2 = Period("1982", freq="MIN") + msg = "Invalid frequency: MIN" + + with pytest.raises(ValueError, match=msg): + Period("1982", freq="MIN") + i2 = Period("1982", freq="min") assert i1 == i2 i1 = Period(year=2005, month=3, day=1, freq="D") From 30b77654ad1cc9b1766e1167a96101deb39f9334 Mon Sep 17 00:00:00 2001 From: Natalia Mokeeva Date: Fri, 12 Apr 2024 13:17:46 +0200 Subject: [PATCH 09/17] add a comment --- pandas/_libs/tslibs/offsets.pyx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index cbdd2d9836309..4c1287497ba21 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -4839,7 +4839,7 @@ cpdef to_offset(freq, bint is_period=False): f"use \'{c_OFFSET_REMOVED_FREQSTR.get(name.upper())}\' " f"instead." ) - # below we raise for lowrecase monthly and bigger frequencies + # below we raise for lowercase monthly and bigger frequencies if (name.upper() != name and name.lower() not in {"h", "min", "s", "ms", "us", "ns"} and name.upper() not in c_PERIOD_TO_OFFSET_FREQSTR and @@ -4848,7 +4848,9 @@ cpdef to_offset(freq, bint is_period=False): if is_period: if name in c_PERIOD_TO_OFFSET_FREQSTR: name = c_PERIOD_TO_OFFSET_FREQSTR[name] - elif name in {"d", "b"}: + # we will remove the check below after deprecating lowercase + # frequencies for "d", "b", "w", "weekday", "w-sun”, and so on. + elif name in {"b", "d"}: name = name.upper() elif (name.upper() not in {"B", "D"} and not name.upper().startswith("W")): From 81fe8f051427991beb9160b65361713181587dba Mon Sep 17 00:00:00 2001 From: Natalia Mokeeva Date: Thu, 2 May 2024 10:55:39 +0200 Subject: [PATCH 10/17] create dictionary PERIOD_AND_OFFSET_ALIASES --- pandas/_libs/tslibs/dtypes.pxd | 1 + pandas/_libs/tslibs/dtypes.pyx | 47 ++++++++++++++------------------- pandas/_libs/tslibs/offsets.pyx | 27 +++++++++++++++---- 3 files changed, 43 insertions(+), 32 deletions(-) diff --git a/pandas/_libs/tslibs/dtypes.pxd b/pandas/_libs/tslibs/dtypes.pxd index c6c742c87b755..7025c6c4813b7 100644 --- a/pandas/_libs/tslibs/dtypes.pxd +++ b/pandas/_libs/tslibs/dtypes.pxd @@ -15,6 +15,7 @@ cdef dict c_OFFSET_TO_PERIOD_FREQSTR cdef dict c_PERIOD_TO_OFFSET_FREQSTR cdef dict c_OFFSET_REMOVED_FREQSTR cdef dict c_DEPR_ABBREVS +cdef set c_PERIOD_AND_OFFSET_ALIASES cdef dict attrname_to_abbrevs cdef dict npy_unit_to_attrname cdef dict attrname_to_npy_unit diff --git a/pandas/_libs/tslibs/dtypes.pyx b/pandas/_libs/tslibs/dtypes.pyx index 6e1e5eb7e5c5b..826249e89a4ae 100644 --- a/pandas/_libs/tslibs/dtypes.pyx +++ b/pandas/_libs/tslibs/dtypes.pyx @@ -196,14 +196,6 @@ OFFSET_TO_PERIOD_FREQSTR: dict = { "BQE-OCT": "Q-OCT", "BQE-NOV": "Q-NOV", "MS": "M", - "D": "D", - "B": "B", - "min": "min", - "s": "s", - "ms": "ms", - "us": "us", - "ns": "ns", - "h": "h", "QE": "Q", "QE-DEC": "Q-DEC", "QE-JAN": "Q-JAN", @@ -230,9 +222,7 @@ OFFSET_TO_PERIOD_FREQSTR: dict = { "YE-SEP": "Y-SEP", "YE-OCT": "Y-OCT", "YE-NOV": "Y-NOV", - "W": "W", "ME": "M", - "Y": "Y", "BYE": "Y", "BYE-DEC": "Y-DEC", "BYE-JAN": "Y-JAN", @@ -279,16 +269,6 @@ cdef dict c_OFFSET_REMOVED_FREQSTR = { "Y-NOV": "YE-NOV", } PERIOD_TO_OFFSET_FREQSTR = { - "D": "D", - "B": "B", - "W": "W", - "W-SUN": "W-SUN", - "W-MON": "W-MON", - "W-TUE": "W-TUE", - "W-WED": "W-WED", - "W-THU": "W-THU", - "W-FRI": "W-FRI", - "W-SAT": "W-SAT", "M": "ME", "Q": "QE", "Q-DEC": "QE-DEC", @@ -316,13 +296,6 @@ PERIOD_TO_OFFSET_FREQSTR = { "Y-SEP": "YE-SEP", "Y-OCT": "YE-OCT", "Y-NOV": "YE-NOV", - "Min": "min", - "min": "min", - "s": "s", - "ms": "ms", - "us": "us", - "ns": "ns", - "h": "h", } cdef dict c_OFFSET_TO_PERIOD_FREQSTR = OFFSET_TO_PERIOD_FREQSTR cdef dict c_PERIOD_TO_OFFSET_FREQSTR = PERIOD_TO_OFFSET_FREQSTR @@ -335,6 +308,26 @@ cdef dict c_DEPR_ABBREVS = { "S": "s", } +PERIOD_AND_OFFSET_ALIASES = { + "W", + "W-SUN", + "W-MON", + "W-TUE", + "W-WED", + "W-THU", + "W-FRI", + "W-SAT", + "D", + "B", + "h", + "min", + "s", + "ms", + "us", + "ns", +} +cdef set c_PERIOD_AND_OFFSET_ALIASES = PERIOD_AND_OFFSET_ALIASES + class FreqGroup(Enum): # Mirrors c_FreqGroup in the .pxd file diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index 14228a22f6ede..e7a3cff82ecca 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -59,6 +59,7 @@ from pandas._libs.tslibs.dtypes cimport ( c_DEPR_ABBREVS, c_OFFSET_REMOVED_FREQSTR, c_OFFSET_TO_PERIOD_FREQSTR, + c_PERIOD_AND_OFFSET_ALIASES, c_PERIOD_TO_OFFSET_FREQSTR, periods_per_day, ) @@ -4839,22 +4840,38 @@ cpdef to_offset(freq, bint is_period=False): ) # below we raise for lowercase monthly and bigger frequencies if (name.upper() != name and - name.lower() not in {"h", "min", "s", "ms", "us", "ns"} and - name.upper() not in c_PERIOD_TO_OFFSET_FREQSTR and - name.upper() in c_OFFSET_TO_PERIOD_FREQSTR): + name.lower() not in c_PERIOD_AND_OFFSET_ALIASES and + name.upper() in c_PERIOD_AND_OFFSET_ALIASES): raise ValueError(INVALID_FREQ_ERR_MSG.format(name)) if is_period: if name in c_PERIOD_TO_OFFSET_FREQSTR: name = c_PERIOD_TO_OFFSET_FREQSTR[name] # we will remove the check below after deprecating lowercase # frequencies for "d", "b", "w", "weekday", "w-sun”, and so on. - elif name in {"b", "d"}: - name = name.upper() elif (name.upper() not in {"B", "D"} and not name.upper().startswith("W")): raise ValueError( f"\'{name}\' is not supported as period frequency." ) + if name in c_PERIOD_AND_OFFSET_ALIASES: + if name.startswith(("W", "w", "D", "d", "B", "b")) and name != name.upper(): + if name.startswith(("W", "w")): + warnings.warn( + f"\'{name}\' is deprecated and will be removed " + f"in a future version, please use \'{name.upper()}\' instead.", + FutureWarning, + stacklevel=find_stack_level(), + ) + name = name.upper() + if not name.startswith(("W", "w", "D", "d", "B", "b")) and name != name.lower(): + warnings.warn( + f"\'{name}\' is deprecated and will be removed " + f"in a future version, please use \'{name.lower()}\' instead.", + FutureWarning, + stacklevel=find_stack_level(), + ) + name = name.lower() + if sep != "" and not sep.isspace(): raise ValueError("separator must be spaces") prefix = _lite_rule_alias.get(name) or name From 727c6aee4cd8131bf7889b7b26180036d7d7a8e7 Mon Sep 17 00:00:00 2001 From: Natalia Mokeeva Date: Thu, 2 May 2024 15:26:33 +0200 Subject: [PATCH 11/17] correct def to_offset --- pandas/_libs/tslibs/offsets.pyx | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index e7a3cff82ecca..a760a7246b333 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -4843,16 +4843,21 @@ cpdef to_offset(freq, bint is_period=False): name.lower() not in c_PERIOD_AND_OFFSET_ALIASES and name.upper() in c_PERIOD_AND_OFFSET_ALIASES): raise ValueError(INVALID_FREQ_ERR_MSG.format(name)) - if is_period: - if name in c_PERIOD_TO_OFFSET_FREQSTR: - name = c_PERIOD_TO_OFFSET_FREQSTR[name] + elif is_period and name.upper() in c_OFFSET_REMOVED_FREQSTR: + if name.upper() != name: + warnings.warn( + f"\'{name}\' is deprecated and will be removed in " + f"a future version, please use \'{name.upper()}\' " + f"instead.", # we will remove the check below after deprecating lowercase # frequencies for "d", "b", "w", "weekday", "w-sun”, and so on. - elif (name.upper() not in {"B", "D"} and - not name.upper().startswith("W")): - raise ValueError( - f"\'{name}\' is not supported as period frequency." - ) + elif (name.upper() not in c_PERIOD_AND_OFFSET_ALIASES and + not name.lower() not in c_PERIOD_AND_OFFSET_ALIASES): + raise ValueError( + f"\'{name}\' is not supported as period frequency." + ) + + name = c_OFFSET_REMOVED_FREQSTR[name.upper()] if name in c_PERIOD_AND_OFFSET_ALIASES: if name.startswith(("W", "w", "D", "d", "B", "b")) and name != name.upper(): if name.startswith(("W", "w")): From c730be06ff70b323c39556f9826c6d2a1c515e8c Mon Sep 17 00:00:00 2001 From: Natalia Mokeeva Date: Fri, 17 May 2024 18:44:28 +0200 Subject: [PATCH 12/17] fixup --- pandas/_libs/tslibs/dtypes.pyx | 14 ----- pandas/_libs/tslibs/offsets.pyx | 62 +++++++++---------- pandas/tests/dtypes/test_dtypes.py | 2 +- .../datetimes/methods/test_to_period.py | 7 ++- .../tests/indexes/period/test_period_range.py | 5 +- pandas/tests/scalar/period/test_asfreq.py | 6 +- pandas/tests/scalar/period/test_period.py | 10 ++- 7 files changed, 48 insertions(+), 58 deletions(-) diff --git a/pandas/_libs/tslibs/dtypes.pyx b/pandas/_libs/tslibs/dtypes.pyx index 826249e89a4ae..a0e6b4f9374b4 100644 --- a/pandas/_libs/tslibs/dtypes.pyx +++ b/pandas/_libs/tslibs/dtypes.pyx @@ -310,21 +310,7 @@ cdef dict c_DEPR_ABBREVS = { PERIOD_AND_OFFSET_ALIASES = { "W", - "W-SUN", - "W-MON", - "W-TUE", - "W-WED", - "W-THU", - "W-FRI", - "W-SAT", - "D", - "B", - "h", "min", - "s", - "ms", - "us", - "ns", } cdef set c_PERIOD_AND_OFFSET_ALIASES = PERIOD_AND_OFFSET_ALIASES diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index ca10d93e48b1d..8a4fd014613d4 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -60,7 +60,6 @@ from pandas._libs.tslibs.dtypes cimport ( c_OFFSET_REMOVED_FREQSTR, c_OFFSET_TO_PERIOD_FREQSTR, c_PERIOD_AND_OFFSET_ALIASES, - c_PERIOD_TO_OFFSET_FREQSTR, periods_per_day, ) from pandas._libs.tslibs.nattype cimport ( @@ -4859,45 +4858,44 @@ cpdef to_offset(freq, bint is_period=False): f"use \'{c_OFFSET_REMOVED_FREQSTR.get(name.upper())}\' " f"instead." ) - # below we raise for lowercase monthly and bigger frequencies if (name.upper() != name and - name.lower() not in c_PERIOD_AND_OFFSET_ALIASES and - name.upper() in c_PERIOD_AND_OFFSET_ALIASES): + name.lower() not in {"W-TUE", "s", "ms", "us", "ns"} and + name.upper().split("-")[0].endswith(("S", "E"))): raise ValueError(INVALID_FREQ_ERR_MSG.format(name)) - elif is_period and name.upper() in c_OFFSET_REMOVED_FREQSTR: - if name.upper() != name: - warnings.warn( - f"\'{name}\' is deprecated and will be removed in " - f"a future version, please use \'{name.upper()}\' " - f"instead.", - # we will remove the check below after deprecating lowercase - # frequencies for "d", "b", "w", "weekday", "w-sun”, and so on. - elif (name.upper() not in c_PERIOD_AND_OFFSET_ALIASES and - not name.lower() not in c_PERIOD_AND_OFFSET_ALIASES): + if (is_period and + name.upper() in c_OFFSET_TO_PERIOD_FREQSTR and + name != "ms" and + name.upper().split("-")[0].endswith(("S", "E"))): + if (name.upper().startswith("B") or + name.upper().startswith("S") or + name.upper().startswith("C")): + raise ValueError(INVALID_FREQ_ERR_MSG.format(name)) + else: + name_msg = "".join(name.upper().split("E", 1)) + raise ValueError( + f"for Period, please use \'{name_msg}\' " + f"instead of \'{name}\'" + ) + if is_period: + if name.upper() in c_OFFSET_REMOVED_FREQSTR: + if name.upper() != name: raise ValueError( - f"\'{name}\' is not supported as period frequency." + f"\'{name}\' is no longer supported, " + f"please use \'{name.upper()}\' instead.", ) - - name = c_OFFSET_REMOVED_FREQSTR[name.upper()] - if name in c_PERIOD_AND_OFFSET_ALIASES: - if name.startswith(("W", "w", "D", "d", "B", "b")) and name != name.upper(): - if name.startswith(("W", "w")): - warnings.warn( + name = c_OFFSET_REMOVED_FREQSTR.get(name.upper()) + + if ((name.upper() in c_PERIOD_AND_OFFSET_ALIASES and + name.upper() != name) or + name == "MIN"): + name_new = "min" if name == "MIN" else name.upper() + warnings.warn( f"\'{name}\' is deprecated and will be removed " - f"in a future version, please use \'{name.upper()}\' instead.", + f"in a future version, please use \'{name_new}\' instead.", FutureWarning, stacklevel=find_stack_level(), ) - name = name.upper() - if not name.startswith(("W", "w", "D", "d", "B", "b")) and name != name.lower(): - warnings.warn( - f"\'{name}\' is deprecated and will be removed " - f"in a future version, please use \'{name.lower()}\' instead.", - FutureWarning, - stacklevel=find_stack_level(), - ) - name = name.lower() - + name = name_new if sep != "" and not sep.isspace(): raise ValueError("separator must be spaces") prefix = _lite_rule_alias.get(name) or name diff --git a/pandas/tests/dtypes/test_dtypes.py b/pandas/tests/dtypes/test_dtypes.py index cea7a1850ada8..c6da01636247d 100644 --- a/pandas/tests/dtypes/test_dtypes.py +++ b/pandas/tests/dtypes/test_dtypes.py @@ -445,7 +445,7 @@ def test_construction(self): def test_cannot_use_custom_businessday(self): # GH#52534 - msg = "Invalid frequency: C" + msg = "C is not supported as period frequency" msg1 = " is not supported as period frequency" msg2 = r"PeriodDtype\[B\] is deprecated" with pytest.raises(ValueError, match=msg): diff --git a/pandas/tests/indexes/datetimes/methods/test_to_period.py b/pandas/tests/indexes/datetimes/methods/test_to_period.py index c3bdfa92aa1c0..8e279162b7012 100644 --- a/pandas/tests/indexes/datetimes/methods/test_to_period.py +++ b/pandas/tests/indexes/datetimes/methods/test_to_period.py @@ -201,7 +201,12 @@ def test_to_period_nofreq(self): @pytest.mark.parametrize("freq", ["2BME", "SME-15", "2BMS"]) def test_to_period_offsets_not_supported(self, freq): # GH#56243 - msg = f"Invalid frequency: {freq}" + msg = "|".join( + [ + f"Invalid frequency: {freq}", + f"{freq} is not supported as period frequency", + ] + ) ts = date_range("1/1/2012", periods=4, freq=freq) with pytest.raises(ValueError, match=msg): diff --git a/pandas/tests/indexes/period/test_period_range.py b/pandas/tests/indexes/period/test_period_range.py index f88a9f417b7f9..4e58dc1f324b2 100644 --- a/pandas/tests/indexes/period/test_period_range.py +++ b/pandas/tests/indexes/period/test_period_range.py @@ -206,9 +206,10 @@ def test_constructor_U(self): @pytest.mark.parametrize("freq_depr", ["2H", "2MIN", "2S", "2US", "2NS"]) def test_uppercase_freq_deprecated_from_time_series(self, freq_depr): # GH#52536, GH#54939 - msg = f"Invalid frequency: {freq_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 pytest.raises(ValueError, match=msg): + 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"]) diff --git a/pandas/tests/scalar/period/test_asfreq.py b/pandas/tests/scalar/period/test_asfreq.py index a607536c5ca5a..90d4a7d0cc23b 100644 --- a/pandas/tests/scalar/period/test_asfreq.py +++ b/pandas/tests/scalar/period/test_asfreq.py @@ -111,7 +111,8 @@ 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 - with pytest.raises(ValueError, match=msg): + msg_depr = "'H' is deprecated and will be removed in a future version." + with tm.assert_produces_warning(FutureWarning, match=msg_depr): 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 @@ -119,7 +120,8 @@ def test_conv_annual(self): 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 - with pytest.raises(ValueError, match=msg): + 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 diff --git a/pandas/tests/scalar/period/test_period.py b/pandas/tests/scalar/period/test_period.py index 46c3af2b9a0f4..49bd48b40e67a 100644 --- a/pandas/tests/scalar/period/test_period.py +++ b/pandas/tests/scalar/period/test_period.py @@ -47,7 +47,7 @@ def test_offsets_not_supported(self, freq, freq_msg): def test_custom_business_day_freq_raises(self): # GH#52534 - msg = "Invalid frequency: C" + msg = "C is not supported as period frequency" with pytest.raises(ValueError, match=msg): Period("2023-04-10", freq="C") msg = f"{offsets.CustomBusinessDay().base} is not supported as period frequency" @@ -108,11 +108,9 @@ def test_construction(self): assert i1 == i3 i1 = Period("1982", freq="min") - msg = "Invalid frequency: MIN" - - with pytest.raises(ValueError, match=msg): - Period("1982", freq="MIN") - i2 = Period("1982", freq="min") + msg = "'MIN' is deprecated and will be removed in a future version." + with tm.assert_produces_warning(FutureWarning, match=msg): + i2 = Period("1982", freq="MIN") assert i1 == i2 i1 = Period(year=2005, month=3, day=1, freq="D") From 81ad38623795283c8f47f772586792d47575865f Mon Sep 17 00:00:00 2001 From: Natalia Mokeeva Date: Tue, 21 May 2024 12:42:06 +0200 Subject: [PATCH 13/17] fixup --- pandas/_libs/tslibs/dtypes.pxd | 4 +- pandas/_libs/tslibs/dtypes.pyx | 47 +++++++++++++++++-- pandas/_libs/tslibs/offsets.pyx | 25 +++++----- .../indexes/period/methods/test_asfreq.py | 25 +++++----- 4 files changed, 70 insertions(+), 31 deletions(-) diff --git a/pandas/_libs/tslibs/dtypes.pxd b/pandas/_libs/tslibs/dtypes.pxd index 7025c6c4813b7..455bca35d160a 100644 --- a/pandas/_libs/tslibs/dtypes.pxd +++ b/pandas/_libs/tslibs/dtypes.pxd @@ -13,9 +13,9 @@ 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_REMOVED_FREQSTR +cdef dict c_OFFSET_RENAMED_FREQSTR cdef dict c_DEPR_ABBREVS -cdef set c_PERIOD_AND_OFFSET_ALIASES +cdef dict c_PERIOD_AND_OFFSET_DEPR_FREQSTR cdef dict attrname_to_abbrevs cdef dict npy_unit_to_attrname cdef dict attrname_to_npy_unit diff --git a/pandas/_libs/tslibs/dtypes.pyx b/pandas/_libs/tslibs/dtypes.pyx index a0e6b4f9374b4..479a5a328b1d8 100644 --- a/pandas/_libs/tslibs/dtypes.pyx +++ b/pandas/_libs/tslibs/dtypes.pyx @@ -196,6 +196,14 @@ OFFSET_TO_PERIOD_FREQSTR: dict = { "BQE-OCT": "Q-OCT", "BQE-NOV": "Q-NOV", "MS": "M", + "D": "D", + "B": "B", + "min": "min", + "s": "s", + "ms": "ms", + "us": "us", + "ns": "ns", + "h": "h", "QE": "Q", "QE-DEC": "Q-DEC", "QE-JAN": "Q-JAN", @@ -222,6 +230,7 @@ OFFSET_TO_PERIOD_FREQSTR: dict = { "YE-SEP": "Y-SEP", "YE-OCT": "Y-OCT", "YE-NOV": "Y-NOV", + "W": "W", "ME": "M", "BYE": "Y", "BYE-DEC": "Y-DEC", @@ -239,7 +248,7 @@ OFFSET_TO_PERIOD_FREQSTR: dict = { "YS": "Y", "BYS": "Y", } -cdef dict c_OFFSET_REMOVED_FREQSTR = { +cdef dict c_OFFSET_RENAMED_FREQSTR = { "M": "ME", "Q": "QE", "Q-DEC": "QE-DEC", @@ -267,6 +276,35 @@ cdef dict c_OFFSET_REMOVED_FREQSTR = { "Y-SEP": "YE-SEP", "Y-OCT": "YE-OCT", "Y-NOV": "YE-NOV", + "BY": "BYE", + "BY-DEC": "BYE-DEC", + "BY-JAN": "BYE-JAN", + "BY-FEB": "BYE-FEB", + "BY-MAR": "BYE-MAR", + "BY-APR": "BYE-APR", + "BY-MAY": "BYE-MAY", + "BY-JUN": "BYE-JUN", + "BY-JUL": "BYE-JUL", + "BY-AUG": "BYE-AUG", + "BY-SEP": "BYE-SEP", + "BY-OCT": "BYE-OCT", + "BY-NOV": "BYE-NOV", + "BM": "BME", + "CBM": "CBME", + "SM": "SME", + "BQ": "BQE", + "BQ-DEC": "BQE-DEC", + "BQ-JAN": "BQE-JAN", + "BQ-FEB": "BQE-FEB", + "BQ-MAR": "BQE-MAR", + "BQ-APR": "BQE-APR", + "BQ-MAY": "BQE-MAY", + "BQ-JUN": "BQE-JUN", + "BQ-JUL": "BQE-JUL", + "BQ-AUG": "BQE-AUG", + "BQ-SEP": "BQE-SEP", + "BQ-OCT": "BQE-OCT", + "BQ-NOV": "BQE-NOV", } PERIOD_TO_OFFSET_FREQSTR = { "M": "ME", @@ -308,11 +346,10 @@ cdef dict c_DEPR_ABBREVS = { "S": "s", } -PERIOD_AND_OFFSET_ALIASES = { - "W", - "min", +cdef dict c_PERIOD_AND_OFFSET_DEPR_FREQSTR = { + "w": "W", + "MIN": "min", } -cdef set c_PERIOD_AND_OFFSET_ALIASES = PERIOD_AND_OFFSET_ALIASES class FreqGroup(Enum): diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index 8a4fd014613d4..77658e1de5be7 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -57,9 +57,9 @@ 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_REMOVED_FREQSTR, + c_OFFSET_RENAMED_FREQSTR, c_OFFSET_TO_PERIOD_FREQSTR, - c_PERIOD_AND_OFFSET_ALIASES, + c_PERIOD_AND_OFFSET_DEPR_FREQSTR, periods_per_day, ) from pandas._libs.tslibs.nattype cimport ( @@ -4852,14 +4852,14 @@ cpdef to_offset(freq, bint is_period=False): tups = zip(split[0::4], split[1::4], split[2::4]) for n, (sep, stride, name) in enumerate(tups): if not is_period: - if name.upper() in c_OFFSET_REMOVED_FREQSTR: + if name.upper() in c_OFFSET_RENAMED_FREQSTR: raise ValueError( f"\'{name}\' is no longer supported for offsets. Please " - f"use \'{c_OFFSET_REMOVED_FREQSTR.get(name.upper())}\' " + f"use \'{c_OFFSET_RENAMED_FREQSTR.get(name.upper())}\' " f"instead." ) if (name.upper() != name and - name.lower() not in {"W-TUE", "s", "ms", "us", "ns"} and + name.lower() not in {"s", "ms", "us", "ns"} and name.upper().split("-")[0].endswith(("S", "E"))): raise ValueError(INVALID_FREQ_ERR_MSG.format(name)) if (is_period and @@ -4877,25 +4877,24 @@ cpdef to_offset(freq, bint is_period=False): f"instead of \'{name}\'" ) if is_period: - if name.upper() in c_OFFSET_REMOVED_FREQSTR: + if name.upper() in c_OFFSET_RENAMED_FREQSTR: if name.upper() != name: raise ValueError( f"\'{name}\' is no longer supported, " f"please use \'{name.upper()}\' instead.", ) - name = c_OFFSET_REMOVED_FREQSTR.get(name.upper()) + name = c_OFFSET_RENAMED_FREQSTR.get(name.upper()) - if ((name.upper() in c_PERIOD_AND_OFFSET_ALIASES and - name.upper() != name) or - name == "MIN"): - name_new = "min" if name == "MIN" else name.upper() + if name in c_PERIOD_AND_OFFSET_DEPR_FREQSTR: warnings.warn( f"\'{name}\' is deprecated and will be removed " - f"in a future version, please use \'{name_new}\' instead.", + f"in a future version, please use " + f"\'{c_PERIOD_AND_OFFSET_DEPR_FREQSTR.get(name)}\' " + f" instead.", FutureWarning, stacklevel=find_stack_level(), ) - name = name_new + name = c_PERIOD_AND_OFFSET_DEPR_FREQSTR.get(name) if sep != "" and not sep.isspace(): raise ValueError("separator must be spaces") prefix = _lite_rule_alias.get(name) or name diff --git a/pandas/tests/indexes/period/methods/test_asfreq.py b/pandas/tests/indexes/period/methods/test_asfreq.py index 4204e15714c97..8fca53c28a036 100644 --- a/pandas/tests/indexes/period/methods/test_asfreq.py +++ b/pandas/tests/indexes/period/methods/test_asfreq.py @@ -142,21 +142,24 @@ def test_asfreq_with_different_n(self): tm.assert_series_equal(result, excepted) @pytest.mark.parametrize( - "freq, is_str", + "freq", [ - ("2BMS", True), - ("2YS-MAR", True), - ("2bh", True), - (offsets.MonthBegin(2), False), - (offsets.BusinessMonthEnd(2), False), + "2BMS", + "2YS-MAR", + "2bh", + offsets.MonthBegin(2), + offsets.BusinessMonthEnd(2), ], ) - def test_pi_asfreq_not_supported_frequency(self, freq, is_str): + def test_pi_asfreq_not_supported_frequency(self, freq): # GH#55785, GH#56945 - if is_str: - msg = f"Invalid frequency: {freq}" - else: - msg = re.escape(f"{freq} is not supported as period frequency") + msg = "|".join( + [ + f"Invalid frequency: {freq}", + re.escape(f"{freq} is not supported as period frequency"), + "bh is not supported as period frequency", + ] + ) pi = PeriodIndex(["2020-01-01", "2021-01-01"], freq="M") with pytest.raises(ValueError, match=msg): From dcc1c923fda4c4523b69e70c9e18e1460f7adc62 Mon Sep 17 00:00:00 2001 From: Natalia Mokeeva <91160475+natmokval@users.noreply.github.com> Date: Fri, 31 May 2024 19:07:05 +0200 Subject: [PATCH 14/17] replace c_OFFSET_RENAMED_FREQSTR with c_PERIOD_TO_OFFSET_FREQSTR --- pandas/_libs/tslibs/offsets.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index 77658e1de5be7..58082314f063d 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -4883,7 +4883,7 @@ cpdef to_offset(freq, bint is_period=False): f"\'{name}\' is no longer supported, " f"please use \'{name.upper()}\' instead.", ) - name = c_OFFSET_RENAMED_FREQSTR.get(name.upper()) + name = c_PERIOD_TO_OFFSET_FREQSTR.get(name.upper()) if name in c_PERIOD_AND_OFFSET_DEPR_FREQSTR: warnings.warn( From 5616c3dc93d29863f4120498a51e5437ed11c90d Mon Sep 17 00:00:00 2001 From: Natalia Mokeeva <91160475+natmokval@users.noreply.github.com> Date: Fri, 31 May 2024 19:43:33 +0200 Subject: [PATCH 15/17] add cimport c_PERIOD_TO_OFFSET_FREQSTR --- pandas/_libs/tslibs/offsets.pyx | 1 + 1 file changed, 1 insertion(+) diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index 58082314f063d..80b5181863ccb 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -60,6 +60,7 @@ from pandas._libs.tslibs.dtypes cimport ( c_OFFSET_RENAMED_FREQSTR, c_OFFSET_TO_PERIOD_FREQSTR, c_PERIOD_AND_OFFSET_DEPR_FREQSTR, + c_PERIOD_TO_OFFSET_FREQSTR, periods_per_day, ) from pandas._libs.tslibs.nattype cimport ( From d7a5037dbe8ca32286fe7bcf94e702da114d4a7a Mon Sep 17 00:00:00 2001 From: Natalia Mokeeva Date: Sat, 1 Jun 2024 18:21:57 +0200 Subject: [PATCH 16/17] add a helper function for error reporting --- pandas/_libs/tslibs/offsets.pyx | 56 ++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index 5e719308ceaab..29940f66c0e14 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -4713,6 +4713,34 @@ INVALID_FREQ_ERR_MSG = "Invalid frequency: {0}" _offset_map = {} +def _validate_to_offset_alias(alias: str, is_period: bool) -> None: + if not is_period: + if alias.upper() in c_OFFSET_RENAMED_FREQSTR: + raise ValueError( + f"\'{alias}\' is no longer supported for offsets. Please " + f"use \'{c_OFFSET_RENAMED_FREQSTR.get(alias.upper())}\' " + f"instead." + ) + if (alias.upper() != alias and + alias.lower() not in {"s", "ms", "us", "ns"} and + alias.upper().split("-")[0].endswith(("S", "E"))): + raise ValueError(INVALID_FREQ_ERR_MSG.format(alias)) + if (is_period and + alias.upper() in c_OFFSET_TO_PERIOD_FREQSTR and + alias != "ms" and + alias.upper().split("-")[0].endswith(("S", "E"))): + if (alias.upper().startswith("B") or + alias.upper().startswith("S") or + alias.upper().startswith("C")): + raise ValueError(INVALID_FREQ_ERR_MSG.format(alias)) + else: + alias_msg = "".join(alias.upper().split("E", 1)) + raise ValueError( + f"for Period, please use \'{alias_msg}\' " + f"instead of \'{alias}\'" + ) + + # TODO: better name? def _get_offset(name: str) -> BaseOffset: """ @@ -4852,33 +4880,9 @@ cpdef to_offset(freq, bint is_period=False): tups = zip(split[0::4], split[1::4], split[2::4]) for n, (sep, stride, name) in enumerate(tups): - if not is_period: - if name.upper() in c_OFFSET_RENAMED_FREQSTR: - raise ValueError( - f"\'{name}\' is no longer supported for offsets. Please " - f"use \'{c_OFFSET_RENAMED_FREQSTR.get(name.upper())}\' " - f"instead." - ) - if (name.upper() != name and - name.lower() not in {"s", "ms", "us", "ns"} and - name.upper().split("-")[0].endswith(("S", "E"))): - raise ValueError(INVALID_FREQ_ERR_MSG.format(name)) - if (is_period and - name.upper() in c_OFFSET_TO_PERIOD_FREQSTR and - name != "ms" and - name.upper().split("-")[0].endswith(("S", "E"))): - if (name.upper().startswith("B") or - name.upper().startswith("S") or - name.upper().startswith("C")): - raise ValueError(INVALID_FREQ_ERR_MSG.format(name)) - else: - name_msg = "".join(name.upper().split("E", 1)) - raise ValueError( - f"for Period, please use \'{name_msg}\' " - f"instead of \'{name}\'" - ) + _validate_to_offset_alias(name, is_period) if is_period: - if name.upper() in c_OFFSET_RENAMED_FREQSTR: + if name.upper() in c_PERIOD_TO_OFFSET_FREQSTR: if name.upper() != name: raise ValueError( f"\'{name}\' is no longer supported, " From d9b3a5e6ac003cd0d52f97f6f7f69c6a8da280f9 Mon Sep 17 00:00:00 2001 From: Marco Edward Gorelli Date: Sat, 1 Jun 2024 18:04:34 +0100 Subject: [PATCH 17/17] minor whatsnew fix --- doc/source/whatsnew/v3.0.0.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index 60f4f386a3390..1be08ee2d3bb4 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -282,10 +282,10 @@ Removal of prior version deprecations/changes Enforced deprecation of aliases ``M``, ``Q``, ``Y``, etc. in favour of ``ME``, ``QE``, ``YE``, etc. for offsets ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Removed the following frequency aliases (:issue:`57986`): +Renamed the following offset aliases (:issue:`57986`): +-------------------------------+------------------+------------------+ -|offsets |removed aliases |new aliases | +| offset | removed alias | new alias | +===============================+==================+==================+ |:class:`MonthEnd` | ``M`` | ``ME`` | +-------------------------------+------------------+------------------+ @@ -304,8 +304,8 @@ Removed the following frequency aliases (:issue:`57986`): |:class:`BYearEnd` | ``BY`` | ``BYE`` | +-------------------------------+------------------+------------------+ -Other Removal -^^^^^^^^^^^^^ +Other Removals +^^^^^^^^^^^^^^ - :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`) - :func:`concat` no longer ignores empty objects when determining output dtypes (:issue:`39122`) - :func:`concat` with all-NA entries no longer ignores the dtype of those entries when determining the result dtype (:issue:`40893`)