Skip to content

Commit 0021d24

Browse files
authored
DEPR: rename ‘BM’/‘CBM’ to ‘BME’/‘CBME’ for offsets (#55496)
* rename BM, CBM to BME, CBME for offsets * fix tests * correct docs * add tests, add notes to 2.2.0 whatsnew
1 parent b5b8be0 commit 0021d24

File tree

17 files changed

+68
-37
lines changed

17 files changed

+68
-37
lines changed

doc/source/user_guide/timeseries.rst

+8-8
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,7 @@ of those specified will not be generated:
461461

462462
.. ipython:: python
463463
464-
pd.date_range(start, end, freq="BM")
464+
pd.date_range(start, end, freq="BME")
465465
466466
pd.date_range(start, end, freq="W")
467467
@@ -557,7 +557,7 @@ intelligent functionality like selection, slicing, etc.
557557

558558
.. ipython:: python
559559
560-
rng = pd.date_range(start, end, freq="BM")
560+
rng = pd.date_range(start, end, freq="BME")
561561
ts = pd.Series(np.random.randn(len(rng)), index=rng)
562562
ts.index
563563
ts[:5].index
@@ -884,9 +884,9 @@ into ``freq`` keyword arguments. The available date offsets and associated frequ
884884
:class:`~pandas.tseries.offsets.LastWeekOfMonth`, ``'LWOM'``, "the x-th day of the last week of each month"
885885
:class:`~pandas.tseries.offsets.MonthEnd`, ``'ME'``, "calendar month end"
886886
:class:`~pandas.tseries.offsets.MonthBegin`, ``'MS'``, "calendar month begin"
887-
:class:`~pandas.tseries.offsets.BMonthEnd` or :class:`~pandas.tseries.offsets.BusinessMonthEnd`, ``'BM'``, "business month end"
887+
:class:`~pandas.tseries.offsets.BMonthEnd` or :class:`~pandas.tseries.offsets.BusinessMonthEnd`, ``'BME'``, "business month end"
888888
:class:`~pandas.tseries.offsets.BMonthBegin` or :class:`~pandas.tseries.offsets.BusinessMonthBegin`, ``'BMS'``, "business month begin"
889-
:class:`~pandas.tseries.offsets.CBMonthEnd` or :class:`~pandas.tseries.offsets.CustomBusinessMonthEnd`, ``'CBM'``, "custom business month end"
889+
:class:`~pandas.tseries.offsets.CBMonthEnd` or :class:`~pandas.tseries.offsets.CustomBusinessMonthEnd`, ``'CBME'``, "custom business month end"
890890
:class:`~pandas.tseries.offsets.CBMonthBegin` or :class:`~pandas.tseries.offsets.CustomBusinessMonthBegin`, ``'CBMS'``, "custom business month begin"
891891
:class:`~pandas.tseries.offsets.SemiMonthEnd`, ``'SM'``, "15th (or other day_of_month) and calendar month end"
892892
: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*.
12481248
"W", "weekly frequency"
12491249
"ME", "month end frequency"
12501250
"SM", "semi-month end frequency (15th and end of month)"
1251-
"BM", "business month end frequency"
1252-
"CBM", "custom business month end frequency"
1251+
"BME", "business month end frequency"
1252+
"CBME", "custom business month end frequency"
12531253
"MS", "month start frequency"
12541254
"SMS", "semi-month start frequency (1st and 15th)"
12551255
"BMS", "business month start frequency"
@@ -1586,7 +1586,7 @@ rather than changing the alignment of the data and the index:
15861586
15871587
ts.shift(5, freq="D")
15881588
ts.shift(5, freq=pd.offsets.BDay())
1589-
ts.shift(5, freq="BM")
1589+
ts.shift(5, freq="BME")
15901590
15911591
Note that with when ``freq`` is specified, the leading entry is no longer NaN
15921592
because the data is not being realigned.
@@ -1692,7 +1692,7 @@ the end of the interval.
16921692
.. warning::
16931693

16941694
The default values for ``label`` and ``closed`` is '**left**' for all
1695-
frequency offsets except for 'ME', 'Y', 'Q', 'BM', 'BY', 'BQ', and 'W'
1695+
frequency offsets except for 'ME', 'Y', 'Q', 'BME', 'BY', 'BQ', and 'W'
16961696
which all have a default of 'right'.
16971697

16981698
This might unintendedly lead to looking ahead, where the value for a later

doc/source/whatsnew/v2.2.0.rst

+5-1
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,11 @@ Other Deprecations
253253
- 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`)
254254
- 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`)
255255
- 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`)
256-
- 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`)
256+
- 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`)
257+
- 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`)
258+
- 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`)
259+
- 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`)
260+
- Deprecated strings ``BM``, and ``CBM`` denoting frequencies in :class:`BusinessMonthEnd`, :class:`CustomBusinessMonthEnd` (:issue:`52064`)
257261
- Deprecated strings ``H``, ``BH``, and ``CBH`` denoting frequencies in :class:`Hour`, :class:`BusinessHour`, :class:`CustomBusinessHour` (:issue:`52536`)
258262
- Deprecated strings ``H``, ``S``, ``U``, and ``N`` denoting units in :func:`to_timedelta` (:issue:`52536`)
259263
- Deprecated strings ``H``, ``T``, ``S``, ``L``, ``U``, and ``N`` denoting units in :class:`Timedelta` (:issue:`52536`)

pandas/_libs/tslibs/dtypes.pyx

+3-1
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ cdef dict _abbrev_to_attrnames = {v: k for k, v in attrname_to_abbrevs.items()}
188188
OFFSET_TO_PERIOD_FREQSTR: dict = {
189189
"WEEKDAY": "D",
190190
"EOM": "M",
191-
"BM": "M",
191+
"BME": "M",
192192
"BQS": "Q",
193193
"QS": "Q",
194194
"BQ": "Q",
@@ -280,6 +280,8 @@ DEPR_ABBREVS: dict[str, str]= {
280280
"BAS-SEP": "BYS-SEP",
281281
"BAS-OCT": "BYS-OCT",
282282
"BAS-NOV": "BYS-NOV",
283+
"BM": "BME",
284+
"CBM": "CBME",
283285
"H": "h",
284286
"BH": "bh",
285287
"CBH": "cbh",

pandas/_libs/tslibs/offsets.pyx

+5-5
Original file line numberDiff line numberDiff line change
@@ -2935,7 +2935,7 @@ cdef class BusinessMonthEnd(MonthOffset):
29352935
>>> pd.offsets.BMonthEnd().rollforward(ts)
29362936
Timestamp('2022-11-30 00:00:00')
29372937
"""
2938-
_prefix = "BM"
2938+
_prefix = "BME"
29392939
_day_opt = "business_end"
29402940

29412941

@@ -4465,10 +4465,10 @@ cdef class CustomBusinessMonthEnd(_CustomBusinessMonth):
44654465
>>> freq = pd.offsets.CustomBusinessMonthEnd(calendar=bdc)
44664466
>>> pd.date_range(dt.datetime(2022, 7, 10), dt.datetime(2022, 11, 10), freq=freq)
44674467
DatetimeIndex(['2022-07-29', '2022-08-31', '2022-09-29', '2022-10-28'],
4468-
dtype='datetime64[ns]', freq='CBM')
4468+
dtype='datetime64[ns]', freq='CBME')
44694469
"""
44704470

4471-
_prefix = "CBM"
4471+
_prefix = "CBME"
44724472

44734473

44744474
cdef class CustomBusinessMonthBegin(_CustomBusinessMonth):
@@ -4551,12 +4551,12 @@ prefix_mapping = {
45514551
BYearEnd, # 'BY'
45524552
BusinessDay, # 'B'
45534553
BusinessMonthBegin, # 'BMS'
4554-
BusinessMonthEnd, # 'BM'
4554+
BusinessMonthEnd, # 'BME'
45554555
BQuarterEnd, # 'BQ'
45564556
BQuarterBegin, # 'BQS'
45574557
BusinessHour, # 'bh'
45584558
CustomBusinessDay, # 'C'
4559-
CustomBusinessMonthEnd, # 'CBM'
4559+
CustomBusinessMonthEnd, # 'CBME'
45604560
CustomBusinessMonthBegin, # 'CBMS'
45614561
CustomBusinessHour, # 'cbh'
45624562
MonthEnd, # 'ME'

pandas/core/generic.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -9187,11 +9187,11 @@ def resample(
91879187
Use frame.T.resample(...) instead.
91889188
closed : {{'right', 'left'}}, default None
91899189
Which side of bin interval is closed. The default is 'left'
9190-
for all frequency offsets except for 'ME', 'Y', 'Q', 'BM',
9190+
for all frequency offsets except for 'ME', 'Y', 'Q', 'BME',
91919191
'BA', 'BQ', and 'W' which all have a default of 'right'.
91929192
label : {{'right', 'left'}}, default None
91939193
Which bin edge label to label bucket with. The default is 'left'
9194-
for all frequency offsets except for 'ME', 'Y', 'Q', 'BM',
9194+
for all frequency offsets except for 'ME', 'Y', 'Q', 'BME',
91959195
'BA', 'BQ', and 'W' which all have a default of 'right'.
91969196
convention : {{'start', 'end', 's', 'e'}}, default 'start'
91979197
For `PeriodIndex` only, controls whether to use the start or

pandas/core/resample.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -2101,7 +2101,7 @@ def __init__(
21012101
else:
21022102
freq = to_offset(freq)
21032103

2104-
end_types = {"ME", "Y", "Q", "BM", "BY", "BQ", "W"}
2104+
end_types = {"ME", "Y", "Q", "BME", "BY", "BQ", "W"}
21052105
rule = freq.rule_code
21062106
if rule in end_types or ("-" in rule and rule[: rule.find("-")] in end_types):
21072107
if closed is None:
@@ -2297,7 +2297,7 @@ def _adjust_bin_edges(
22972297
) -> tuple[DatetimeIndex, npt.NDArray[np.int64]]:
22982298
# Some hacks for > daily data, see #1471, #1458, #1483
22992299

2300-
if self.freq.name in ("BM", "ME", "W") or self.freq.name.split("-")[0] in (
2300+
if self.freq.name in ("BME", "ME", "W") or self.freq.name.split("-")[0] in (
23012301
"BQ",
23022302
"BY",
23032303
"Q",

pandas/tests/frame/methods/test_asfreq.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,16 @@ def test_asfreq2(self, frame_or_series):
3232
datetime(2009, 11, 30),
3333
datetime(2009, 12, 31),
3434
],
35-
freq="BM",
35+
freq="BME",
3636
),
3737
)
3838

3939
daily_ts = ts.asfreq("B")
40-
monthly_ts = daily_ts.asfreq("BM")
40+
monthly_ts = daily_ts.asfreq("BME")
4141
tm.assert_equal(monthly_ts, ts)
4242

4343
daily_ts = ts.asfreq("B", method="pad")
44-
monthly_ts = daily_ts.asfreq("BM")
44+
monthly_ts = daily_ts.asfreq("BME")
4545
tm.assert_equal(monthly_ts, ts)
4646

4747
daily_ts = ts.asfreq(offsets.BDay())
@@ -140,12 +140,12 @@ def test_asfreq_resample_set_correct_freq(self, frame_or_series):
140140
def test_asfreq_empty(self, datetime_frame):
141141
# test does not blow up on length-0 DataFrame
142142
zero_length = datetime_frame.reindex([])
143-
result = zero_length.asfreq("BM")
143+
result = zero_length.asfreq("BME")
144144
assert result is not zero_length
145145

146146
def test_asfreq(self, datetime_frame):
147147
offset_monthly = datetime_frame.asfreq(offsets.BMonthEnd())
148-
rule_monthly = datetime_frame.asfreq("BM")
148+
rule_monthly = datetime_frame.asfreq("BME")
149149

150150
tm.assert_frame_equal(offset_monthly, rule_monthly)
151151

pandas/tests/indexes/datetimes/methods/test_shift.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,6 @@ def test_shift_bmonth(self):
157157

158158
def test_shift_empty(self):
159159
# GH#14811
160-
dti = date_range(start="2016-10-21", end="2016-10-21", freq="BM")
160+
dti = date_range(start="2016-10-21", end="2016-10-21", freq="BME")
161161
result = dti.shift(1)
162162
tm.assert_index_equal(result, dti)

pandas/tests/indexes/datetimes/methods/test_to_period.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def test_to_period_annualish(self, off):
6363
assert prng.freq == "Y-DEC"
6464

6565
def test_to_period_monthish(self):
66-
offsets = ["MS", "BM"]
66+
offsets = ["MS", "BME"]
6767
for off in offsets:
6868
rng = date_range("01-Jan-2012", periods=8, freq=off)
6969
prng = rng.to_period()

pandas/tests/indexes/datetimes/test_datetime.py

+14
Original file line numberDiff line numberDiff line change
@@ -264,3 +264,17 @@ def test_AS_BA_BAS_deprecated(self, freq_depr, expected_values, expected_freq):
264264
)
265265

266266
tm.assert_index_equal(result, expected)
267+
268+
def test_BM_deprecated(self):
269+
# GH#52064
270+
msg = "'BM' is deprecated and will be removed in a future version."
271+
272+
with tm.assert_produces_warning(FutureWarning, match=msg):
273+
expected = date_range(start="2016-02-21", end="2016-08-21", freq="2BM")
274+
result = DatetimeIndex(
275+
["2016-02-29", "2016-04-29", "2016-06-30"],
276+
dtype="datetime64[ns]",
277+
freq="2BME",
278+
)
279+
280+
tm.assert_index_equal(result, expected)

pandas/tests/indexes/datetimes/test_misc.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ def test_datetimeindex_accessors4(self):
141141

142142
def test_datetimeindex_accessors5(self):
143143
freq_m = to_offset("ME")
144-
bm = to_offset("BM")
144+
bm = to_offset("BME")
145145
qfeb = to_offset("Q-FEB")
146146
qsfeb = to_offset("QS-FEB")
147147
bq = to_offset("BQ")

pandas/tests/indexes/multi/test_get_level_values.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ def test_get_level_values_when_periods():
115115

116116
def test_values_loses_freq_of_underlying_index():
117117
# GH#49054
118-
idx = pd.DatetimeIndex(date_range("20200101", periods=3, freq="BM"))
118+
idx = pd.DatetimeIndex(date_range("20200101", periods=3, freq="BME"))
119119
expected = idx.copy(deep=True)
120120
idx2 = Index([1, 2, 3])
121121
midx = MultiIndex(levels=[idx, idx2], codes=[[0, 1, 2], [0, 1, 2]])

pandas/tests/plotting/test_datetimelike.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@ def test_business_freq(self):
360360
assert PeriodIndex(data=idx).freqstr == "B"
361361

362362
def test_business_freq_convert(self):
363-
bts = tm.makeTimeSeries(300).asfreq("BM")
363+
bts = tm.makeTimeSeries(300).asfreq("BME")
364364
ts = bts.to_period("M")
365365
_, ax = mpl.pyplot.subplots()
366366
bts.plot(ax=ax)

pandas/tests/resample/test_datetime_index.py

+13-2
Original file line numberDiff line numberDiff line change
@@ -498,12 +498,12 @@ def test_resample_how_method(unit):
498498

499499
def test_resample_extra_index_point(unit):
500500
# GH#9756
501-
index = date_range(start="20150101", end="20150331", freq="BM").as_unit(unit)
501+
index = date_range(start="20150101", end="20150331", freq="BME").as_unit(unit)
502502
expected = DataFrame({"A": Series([21, 41, 63], index=index)})
503503

504504
index = date_range(start="20150101", end="20150331", freq="B").as_unit(unit)
505505
df = DataFrame({"A": Series(range(len(index)), index=index)}, dtype="int64")
506-
result = df.resample("BM").last()
506+
result = df.resample("BME").last()
507507
tm.assert_frame_equal(result, expected)
508508

509509

@@ -2020,6 +2020,17 @@ def test_resample_M_deprecated():
20202020
tm.assert_series_equal(result, expected)
20212021

20222022

2023+
def test_resample_BM_deprecated():
2024+
# GH#52064
2025+
depr_msg = "'BM' is deprecated and will be removed in a future version."
2026+
2027+
s = Series(range(10), index=date_range("20130101", freq="d", periods=10))
2028+
expected = s.resample("2BME").mean()
2029+
with tm.assert_produces_warning(FutureWarning, match=depr_msg):
2030+
result = s.resample("2BM").mean()
2031+
tm.assert_series_equal(result, expected)
2032+
2033+
20232034
def test_resample_ms_closed_right():
20242035
# https://github.com/pandas-dev/pandas/issues/55271
20252036
dti = date_range(start="2020-01-31", freq="1min", periods=6000)

pandas/tests/tseries/frequencies/test_inference.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def base_delta_code_pair(request):
5353
freqs = (
5454
[f"Q-{month}" for month in MONTHS]
5555
+ [f"{annual}-{month}" for annual in ["Y", "BY"] for month in MONTHS]
56-
+ ["ME", "BM", "BMS"]
56+
+ ["ME", "BME", "BMS"]
5757
+ [f"WOM-{count}{day}" for count in range(1, 5) for day in DAYS]
5858
+ [f"W-{day}" for day in DAYS]
5959
)

pandas/tests/tseries/offsets/test_offsets.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -757,7 +757,7 @@ class TestOffsetNames:
757757
def test_get_offset_name(self):
758758
assert BDay().freqstr == "B"
759759
assert BDay(2).freqstr == "2B"
760-
assert BMonthEnd().freqstr == "BM"
760+
assert BMonthEnd().freqstr == "BME"
761761
assert Week(weekday=0).freqstr == "W-MON"
762762
assert Week(weekday=1).freqstr == "W-TUE"
763763
assert Week(weekday=2).freqstr == "W-WED"
@@ -776,8 +776,8 @@ def test_get_offset():
776776
pairs = [
777777
("B", BDay()),
778778
("b", BDay()),
779-
("bm", BMonthEnd()),
780-
("Bm", BMonthEnd()),
779+
("bme", BMonthEnd()),
780+
("Bme", BMonthEnd()),
781781
("W-MON", Week(weekday=0)),
782782
("W-TUE", Week(weekday=1)),
783783
("W-WED", Week(weekday=2)),
@@ -811,7 +811,7 @@ def test_alias_equality(self):
811811
assert k == v.copy()
812812

813813
def test_rule_code(self):
814-
lst = ["ME", "MS", "BM", "BMS", "D", "B", "h", "min", "s", "ms", "us"]
814+
lst = ["ME", "MS", "BME", "BMS", "D", "B", "h", "min", "s", "ms", "us"]
815815
for k in lst:
816816
assert k == _get_offset(k).rule_code
817817
# should be cached - this is kind of an internals test...

pandas/tseries/frequencies.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,7 @@ def _get_monthly_rule(self) -> str | None:
369369
if pos_check is None:
370370
return None
371371
else:
372-
return {"cs": "MS", "bs": "BMS", "ce": "ME", "be": "BM"}.get(pos_check)
372+
return {"cs": "MS", "bs": "BMS", "ce": "ME", "be": "BME"}.get(pos_check)
373373

374374
def _is_business_daily(self) -> bool:
375375
# quick check: cannot be business daily

0 commit comments

Comments
 (0)