diff --git a/doc/source/user_guide/timeseries.rst b/doc/source/user_guide/timeseries.rst index 9f3077e266e98..7ffbd9b2d740a 100644 --- a/doc/source/user_guide/timeseries.rst +++ b/doc/source/user_guide/timeseries.rst @@ -461,7 +461,7 @@ of those specified will not be generated: .. ipython:: python - pd.date_range(start, end, freq="BM") + pd.date_range(start, end, freq="BME") pd.date_range(start, end, freq="W") @@ -557,7 +557,7 @@ intelligent functionality like selection, slicing, etc. .. ipython:: python - rng = pd.date_range(start, end, freq="BM") + rng = pd.date_range(start, end, freq="BME") ts = pd.Series(np.random.randn(len(rng)), index=rng) ts.index ts[:5].index @@ -884,9 +884,9 @@ into ``freq`` keyword arguments. The available date offsets and associated frequ :class:`~pandas.tseries.offsets.LastWeekOfMonth`, ``'LWOM'``, "the x-th day of the last week of each month" :class:`~pandas.tseries.offsets.MonthEnd`, ``'ME'``, "calendar month end" :class:`~pandas.tseries.offsets.MonthBegin`, ``'MS'``, "calendar month begin" - :class:`~pandas.tseries.offsets.BMonthEnd` or :class:`~pandas.tseries.offsets.BusinessMonthEnd`, ``'BM'``, "business month end" + :class:`~pandas.tseries.offsets.BMonthEnd` or :class:`~pandas.tseries.offsets.BusinessMonthEnd`, ``'BME'``, "business month end" :class:`~pandas.tseries.offsets.BMonthBegin` or :class:`~pandas.tseries.offsets.BusinessMonthBegin`, ``'BMS'``, "business month begin" - :class:`~pandas.tseries.offsets.CBMonthEnd` or :class:`~pandas.tseries.offsets.CustomBusinessMonthEnd`, ``'CBM'``, "custom business month end" + :class:`~pandas.tseries.offsets.CBMonthEnd` or :class:`~pandas.tseries.offsets.CustomBusinessMonthEnd`, ``'CBME'``, "custom business month end" :class:`~pandas.tseries.offsets.CBMonthBegin` or :class:`~pandas.tseries.offsets.CustomBusinessMonthBegin`, ``'CBMS'``, "custom business month begin" :class:`~pandas.tseries.offsets.SemiMonthEnd`, ``'SM'``, "15th (or other day_of_month) and calendar month end" :class:`~pandas.tseries.offsets.SemiMonthBegin`, ``'SMS'``, "15th (or other day_of_month) and calendar month begin" @@ -1248,8 +1248,8 @@ frequencies. We will refer to these aliases as *offset aliases*. "W", "weekly frequency" "ME", "month end frequency" "SM", "semi-month end frequency (15th and end of month)" - "BM", "business month end frequency" - "CBM", "custom business month end frequency" + "BME", "business month end frequency" + "CBME", "custom business month end frequency" "MS", "month start frequency" "SMS", "semi-month start frequency (1st and 15th)" "BMS", "business month start frequency" @@ -1586,7 +1586,7 @@ rather than changing the alignment of the data and the index: ts.shift(5, freq="D") ts.shift(5, freq=pd.offsets.BDay()) - ts.shift(5, freq="BM") + ts.shift(5, freq="BME") Note that with when ``freq`` is specified, the leading entry is no longer NaN because the data is not being realigned. @@ -1692,7 +1692,7 @@ the end of the interval. .. warning:: The default values for ``label`` and ``closed`` is '**left**' for all - frequency offsets except for 'ME', 'Y', 'Q', 'BM', 'BY', 'BQ', and 'W' + frequency offsets except for 'ME', 'Y', 'Q', 'BME', 'BY', 'BQ', and 'W' which all have a default of 'right'. This might unintendedly lead to looking ahead, where the value for a later diff --git a/doc/source/whatsnew/v2.2.0.rst b/doc/source/whatsnew/v2.2.0.rst index 93ca2541d7ecd..ef59c86a21598 100644 --- a/doc/source/whatsnew/v2.2.0.rst +++ b/doc/source/whatsnew/v2.2.0.rst @@ -253,7 +253,11 @@ Other Deprecations - Deprecated downcasting behavior in :meth:`Series.where`, :meth:`DataFrame.where`, :meth:`Series.mask`, :meth:`DataFrame.mask`, :meth:`Series.clip`, :meth:`DataFrame.clip`; in a future version these will not infer object-dtype columns to non-object dtype, or all-round floats to integer dtype. Call ``result.infer_objects(copy=False)`` on the result for object inference, or explicitly cast floats to ints. To opt in to the future version, use ``pd.set_option("future.no_silent_downcasting", True)`` (:issue:`53656`) - Deprecated including the groups in computations when using :meth:`DataFrameGroupBy.apply` and :meth:`DataFrameGroupBy.resample`; pass ``include_groups=False`` to exclude the groups (:issue:`7155`) - Deprecated not passing a tuple to :class:`DataFrameGroupBy.get_group` or :class:`SeriesGroupBy.get_group` when grouping by a length-1 list-like (:issue:`25971`) -- Deprecated string ``A`` denoting frequency in :class:`YearEnd` and strings ``A-DEC``, ``A-JAN``, etc. denoting annual frequencies with various fiscal year ends (:issue:`52536`) +- Deprecated string ``AS`` denoting frequency in :class:`YearBegin` and strings ``AS-DEC``, ``AS-JAN``, etc. denoting annual frequencies with various fiscal year starts (:issue:`54275`) +- Deprecated string ``A`` denoting frequency in :class:`YearEnd` and strings ``A-DEC``, ``A-JAN``, etc. denoting annual frequencies with various fiscal year ends (:issue:`54275`) +- Deprecated string ``BAS`` denoting frequency in :class:`BYearBegin` and strings ``BAS-DEC``, ``BAS-JAN``, etc. denoting annual frequencies with various fiscal year starts (:issue:`54275`) +- Deprecated string ``BA`` denoting frequency in :class:`BYearEnd` and strings ``BA-DEC``, ``BA-JAN``, etc. denoting annual frequencies with various fiscal year ends (:issue:`54275`) +- Deprecated strings ``BM``, and ``CBM`` denoting frequencies in :class:`BusinessMonthEnd`, :class:`CustomBusinessMonthEnd` (:issue:`52064`) - Deprecated strings ``H``, ``BH``, and ``CBH`` denoting frequencies in :class:`Hour`, :class:`BusinessHour`, :class:`CustomBusinessHour` (:issue:`52536`) - Deprecated strings ``H``, ``S``, ``U``, and ``N`` denoting units in :func:`to_timedelta` (:issue:`52536`) - Deprecated strings ``H``, ``T``, ``S``, ``L``, ``U``, and ``N`` denoting units in :class:`Timedelta` (:issue:`52536`) diff --git a/pandas/_libs/tslibs/dtypes.pyx b/pandas/_libs/tslibs/dtypes.pyx index 26181d8f15518..370917df4dca6 100644 --- a/pandas/_libs/tslibs/dtypes.pyx +++ b/pandas/_libs/tslibs/dtypes.pyx @@ -188,7 +188,7 @@ cdef dict _abbrev_to_attrnames = {v: k for k, v in attrname_to_abbrevs.items()} OFFSET_TO_PERIOD_FREQSTR: dict = { "WEEKDAY": "D", "EOM": "M", - "BM": "M", + "BME": "M", "BQS": "Q", "QS": "Q", "BQ": "Q", @@ -280,6 +280,8 @@ DEPR_ABBREVS: dict[str, str]= { "BAS-SEP": "BYS-SEP", "BAS-OCT": "BYS-OCT", "BAS-NOV": "BYS-NOV", + "BM": "BME", + "CBM": "CBME", "H": "h", "BH": "bh", "CBH": "cbh", diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index 6c5cdde20da5f..f20073766bc3a 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -2935,7 +2935,7 @@ cdef class BusinessMonthEnd(MonthOffset): >>> pd.offsets.BMonthEnd().rollforward(ts) Timestamp('2022-11-30 00:00:00') """ - _prefix = "BM" + _prefix = "BME" _day_opt = "business_end" @@ -4465,10 +4465,10 @@ cdef class CustomBusinessMonthEnd(_CustomBusinessMonth): >>> freq = pd.offsets.CustomBusinessMonthEnd(calendar=bdc) >>> pd.date_range(dt.datetime(2022, 7, 10), dt.datetime(2022, 11, 10), freq=freq) DatetimeIndex(['2022-07-29', '2022-08-31', '2022-09-29', '2022-10-28'], - dtype='datetime64[ns]', freq='CBM') + dtype='datetime64[ns]', freq='CBME') """ - _prefix = "CBM" + _prefix = "CBME" cdef class CustomBusinessMonthBegin(_CustomBusinessMonth): @@ -4551,12 +4551,12 @@ prefix_mapping = { BYearEnd, # 'BY' BusinessDay, # 'B' BusinessMonthBegin, # 'BMS' - BusinessMonthEnd, # 'BM' + BusinessMonthEnd, # 'BME' BQuarterEnd, # 'BQ' BQuarterBegin, # 'BQS' BusinessHour, # 'bh' CustomBusinessDay, # 'C' - CustomBusinessMonthEnd, # 'CBM' + CustomBusinessMonthEnd, # 'CBME' CustomBusinessMonthBegin, # 'CBMS' CustomBusinessHour, # 'cbh' MonthEnd, # 'ME' diff --git a/pandas/core/generic.py b/pandas/core/generic.py index a7183a9d9498a..f1ecc57335a51 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -9187,11 +9187,11 @@ def resample( Use frame.T.resample(...) instead. closed : {{'right', 'left'}}, default None Which side of bin interval is closed. The default is 'left' - for all frequency offsets except for 'ME', 'Y', 'Q', 'BM', + for all frequency offsets except for 'ME', 'Y', 'Q', 'BME', 'BA', 'BQ', and 'W' which all have a default of 'right'. label : {{'right', 'left'}}, default None Which bin edge label to label bucket with. The default is 'left' - for all frequency offsets except for 'ME', 'Y', 'Q', 'BM', + for all frequency offsets except for 'ME', 'Y', 'Q', 'BME', 'BA', 'BQ', and 'W' which all have a default of 'right'. convention : {{'start', 'end', 's', 'e'}}, default 'start' For `PeriodIndex` only, controls whether to use the start or diff --git a/pandas/core/resample.py b/pandas/core/resample.py index 8b3071a6f8582..969748209772a 100644 --- a/pandas/core/resample.py +++ b/pandas/core/resample.py @@ -2101,7 +2101,7 @@ def __init__( else: freq = to_offset(freq) - end_types = {"ME", "Y", "Q", "BM", "BY", "BQ", "W"} + end_types = {"ME", "Y", "Q", "BME", "BY", "BQ", "W"} rule = freq.rule_code if rule in end_types or ("-" in rule and rule[: rule.find("-")] in end_types): if closed is None: @@ -2297,7 +2297,7 @@ def _adjust_bin_edges( ) -> tuple[DatetimeIndex, npt.NDArray[np.int64]]: # Some hacks for > daily data, see #1471, #1458, #1483 - if self.freq.name in ("BM", "ME", "W") or self.freq.name.split("-")[0] in ( + if self.freq.name in ("BME", "ME", "W") or self.freq.name.split("-")[0] in ( "BQ", "BY", "Q", diff --git a/pandas/tests/frame/methods/test_asfreq.py b/pandas/tests/frame/methods/test_asfreq.py index b3ab11d07bd7e..bc6e74d5b1f00 100644 --- a/pandas/tests/frame/methods/test_asfreq.py +++ b/pandas/tests/frame/methods/test_asfreq.py @@ -32,16 +32,16 @@ def test_asfreq2(self, frame_or_series): datetime(2009, 11, 30), datetime(2009, 12, 31), ], - freq="BM", + freq="BME", ), ) daily_ts = ts.asfreq("B") - monthly_ts = daily_ts.asfreq("BM") + monthly_ts = daily_ts.asfreq("BME") tm.assert_equal(monthly_ts, ts) daily_ts = ts.asfreq("B", method="pad") - monthly_ts = daily_ts.asfreq("BM") + monthly_ts = daily_ts.asfreq("BME") tm.assert_equal(monthly_ts, ts) daily_ts = ts.asfreq(offsets.BDay()) @@ -140,12 +140,12 @@ def test_asfreq_resample_set_correct_freq(self, frame_or_series): def test_asfreq_empty(self, datetime_frame): # test does not blow up on length-0 DataFrame zero_length = datetime_frame.reindex([]) - result = zero_length.asfreq("BM") + result = zero_length.asfreq("BME") assert result is not zero_length def test_asfreq(self, datetime_frame): offset_monthly = datetime_frame.asfreq(offsets.BMonthEnd()) - rule_monthly = datetime_frame.asfreq("BM") + rule_monthly = datetime_frame.asfreq("BME") tm.assert_frame_equal(offset_monthly, rule_monthly) diff --git a/pandas/tests/indexes/datetimes/methods/test_shift.py b/pandas/tests/indexes/datetimes/methods/test_shift.py index 064f664a4de10..c50f75894d810 100644 --- a/pandas/tests/indexes/datetimes/methods/test_shift.py +++ b/pandas/tests/indexes/datetimes/methods/test_shift.py @@ -157,6 +157,6 @@ def test_shift_bmonth(self): def test_shift_empty(self): # GH#14811 - dti = date_range(start="2016-10-21", end="2016-10-21", freq="BM") + dti = date_range(start="2016-10-21", end="2016-10-21", freq="BME") result = dti.shift(1) tm.assert_index_equal(result, dti) diff --git a/pandas/tests/indexes/datetimes/methods/test_to_period.py b/pandas/tests/indexes/datetimes/methods/test_to_period.py index 6839fafcdc114..d95cd6f3a2cc5 100644 --- a/pandas/tests/indexes/datetimes/methods/test_to_period.py +++ b/pandas/tests/indexes/datetimes/methods/test_to_period.py @@ -63,7 +63,7 @@ def test_to_period_annualish(self, off): assert prng.freq == "Y-DEC" def test_to_period_monthish(self): - offsets = ["MS", "BM"] + offsets = ["MS", "BME"] for off in offsets: rng = date_range("01-Jan-2012", periods=8, freq=off) prng = rng.to_period() diff --git a/pandas/tests/indexes/datetimes/test_datetime.py b/pandas/tests/indexes/datetimes/test_datetime.py index a18501a193b60..3b0b856d07673 100644 --- a/pandas/tests/indexes/datetimes/test_datetime.py +++ b/pandas/tests/indexes/datetimes/test_datetime.py @@ -264,3 +264,17 @@ def test_AS_BA_BAS_deprecated(self, freq_depr, expected_values, expected_freq): ) tm.assert_index_equal(result, expected) + + def test_BM_deprecated(self): + # GH#52064 + msg = "'BM' is deprecated and will be removed in a future version." + + with tm.assert_produces_warning(FutureWarning, match=msg): + expected = date_range(start="2016-02-21", end="2016-08-21", freq="2BM") + result = DatetimeIndex( + ["2016-02-29", "2016-04-29", "2016-06-30"], + dtype="datetime64[ns]", + freq="2BME", + ) + + tm.assert_index_equal(result, expected) diff --git a/pandas/tests/indexes/datetimes/test_misc.py b/pandas/tests/indexes/datetimes/test_misc.py index 0a5287d154adc..d86d78ba47c5b 100644 --- a/pandas/tests/indexes/datetimes/test_misc.py +++ b/pandas/tests/indexes/datetimes/test_misc.py @@ -141,7 +141,7 @@ def test_datetimeindex_accessors4(self): def test_datetimeindex_accessors5(self): freq_m = to_offset("ME") - bm = to_offset("BM") + bm = to_offset("BME") qfeb = to_offset("Q-FEB") qsfeb = to_offset("QS-FEB") bq = to_offset("BQ") diff --git a/pandas/tests/indexes/multi/test_get_level_values.py b/pandas/tests/indexes/multi/test_get_level_values.py index 84907f5279876..28c77e78924cb 100644 --- a/pandas/tests/indexes/multi/test_get_level_values.py +++ b/pandas/tests/indexes/multi/test_get_level_values.py @@ -115,7 +115,7 @@ def test_get_level_values_when_periods(): def test_values_loses_freq_of_underlying_index(): # GH#49054 - idx = pd.DatetimeIndex(date_range("20200101", periods=3, freq="BM")) + idx = pd.DatetimeIndex(date_range("20200101", periods=3, freq="BME")) expected = idx.copy(deep=True) idx2 = Index([1, 2, 3]) midx = MultiIndex(levels=[idx, idx2], codes=[[0, 1, 2], [0, 1, 2]]) diff --git a/pandas/tests/plotting/test_datetimelike.py b/pandas/tests/plotting/test_datetimelike.py index db7c0cec09e6c..a384fd9cdc8f2 100644 --- a/pandas/tests/plotting/test_datetimelike.py +++ b/pandas/tests/plotting/test_datetimelike.py @@ -360,7 +360,7 @@ def test_business_freq(self): assert PeriodIndex(data=idx).freqstr == "B" def test_business_freq_convert(self): - bts = tm.makeTimeSeries(300).asfreq("BM") + bts = tm.makeTimeSeries(300).asfreq("BME") ts = bts.to_period("M") _, ax = mpl.pyplot.subplots() bts.plot(ax=ax) diff --git a/pandas/tests/resample/test_datetime_index.py b/pandas/tests/resample/test_datetime_index.py index f66f5bf50974e..e0ba7902a8a6c 100644 --- a/pandas/tests/resample/test_datetime_index.py +++ b/pandas/tests/resample/test_datetime_index.py @@ -498,12 +498,12 @@ def test_resample_how_method(unit): def test_resample_extra_index_point(unit): # GH#9756 - index = date_range(start="20150101", end="20150331", freq="BM").as_unit(unit) + index = date_range(start="20150101", end="20150331", freq="BME").as_unit(unit) expected = DataFrame({"A": Series([21, 41, 63], index=index)}) index = date_range(start="20150101", end="20150331", freq="B").as_unit(unit) df = DataFrame({"A": Series(range(len(index)), index=index)}, dtype="int64") - result = df.resample("BM").last() + result = df.resample("BME").last() tm.assert_frame_equal(result, expected) @@ -2020,6 +2020,17 @@ def test_resample_M_deprecated(): tm.assert_series_equal(result, expected) +def test_resample_BM_deprecated(): + # GH#52064 + depr_msg = "'BM' is deprecated and will be removed in a future version." + + s = Series(range(10), index=date_range("20130101", freq="d", periods=10)) + expected = s.resample("2BME").mean() + with tm.assert_produces_warning(FutureWarning, match=depr_msg): + result = s.resample("2BM").mean() + tm.assert_series_equal(result, expected) + + def test_resample_ms_closed_right(): # https://github.com/pandas-dev/pandas/issues/55271 dti = date_range(start="2020-01-31", freq="1min", periods=6000) diff --git a/pandas/tests/tseries/frequencies/test_inference.py b/pandas/tests/tseries/frequencies/test_inference.py index 22ff7f8405a40..aec9915d69e3c 100644 --- a/pandas/tests/tseries/frequencies/test_inference.py +++ b/pandas/tests/tseries/frequencies/test_inference.py @@ -53,7 +53,7 @@ def base_delta_code_pair(request): freqs = ( [f"Q-{month}" for month in MONTHS] + [f"{annual}-{month}" for annual in ["Y", "BY"] for month in MONTHS] - + ["ME", "BM", "BMS"] + + ["ME", "BME", "BMS"] + [f"WOM-{count}{day}" for count in range(1, 5) for day in DAYS] + [f"W-{day}" for day in DAYS] ) diff --git a/pandas/tests/tseries/offsets/test_offsets.py b/pandas/tests/tseries/offsets/test_offsets.py index 9389f78c9e672..5678dd1fb511e 100644 --- a/pandas/tests/tseries/offsets/test_offsets.py +++ b/pandas/tests/tseries/offsets/test_offsets.py @@ -757,7 +757,7 @@ class TestOffsetNames: def test_get_offset_name(self): assert BDay().freqstr == "B" assert BDay(2).freqstr == "2B" - assert BMonthEnd().freqstr == "BM" + assert BMonthEnd().freqstr == "BME" assert Week(weekday=0).freqstr == "W-MON" assert Week(weekday=1).freqstr == "W-TUE" assert Week(weekday=2).freqstr == "W-WED" @@ -776,8 +776,8 @@ def test_get_offset(): pairs = [ ("B", BDay()), ("b", BDay()), - ("bm", BMonthEnd()), - ("Bm", BMonthEnd()), + ("bme", BMonthEnd()), + ("Bme", BMonthEnd()), ("W-MON", Week(weekday=0)), ("W-TUE", Week(weekday=1)), ("W-WED", Week(weekday=2)), @@ -811,7 +811,7 @@ def test_alias_equality(self): assert k == v.copy() def test_rule_code(self): - lst = ["ME", "MS", "BM", "BMS", "D", "B", "h", "min", "s", "ms", "us"] + lst = ["ME", "MS", "BME", "BMS", "D", "B", "h", "min", "s", "ms", "us"] for k in lst: assert k == _get_offset(k).rule_code # should be cached - this is kind of an internals test... diff --git a/pandas/tseries/frequencies.py b/pandas/tseries/frequencies.py index db4fdf0d24465..4bd558a30c92f 100644 --- a/pandas/tseries/frequencies.py +++ b/pandas/tseries/frequencies.py @@ -369,7 +369,7 @@ def _get_monthly_rule(self) -> str | None: if pos_check is None: return None else: - return {"cs": "MS", "bs": "BMS", "ce": "ME", "be": "BM"}.get(pos_check) + return {"cs": "MS", "bs": "BMS", "ce": "ME", "be": "BME"}.get(pos_check) def _is_business_daily(self) -> bool: # quick check: cannot be business daily