Skip to content

BUG: Breaking change to offsets.pyx - MonthOffset #39887

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 71 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
a944a27
Update offsets.pyx
Pawel-Kranzberg Feb 17, 2021
fb439fc
Merge remote-tracking branch 'upstream/master'
Pawel-Kranzberg Feb 17, 2021
87e1b87
Update offsets.pyx
Pawel-Kranzberg Feb 17, 2021
5d46697
Update offsets.pyx
Pawel-Kranzberg Feb 17, 2021
a583905
Update offsets.pyx
Pawel-Kranzberg Feb 17, 2021
cd216ae
Update offsets.pyx
Pawel-Kranzberg Feb 17, 2021
53b09c1
Update offsets.pyx
Pawel-Kranzberg Feb 18, 2021
6fdb0af
Update offsets.pyx
Pawel-Kranzberg Feb 18, 2021
b7d671b
Update offsets.pyx
Pawel-Kranzberg Feb 18, 2021
de72073
Add ability to provide months as short names
Pawel-Kranzberg Feb 18, 2021
bfaf6ce
Update offsets.pyx
Pawel-Kranzberg Feb 18, 2021
1b396dd
Update offsets.pyx
Pawel-Kranzberg Feb 18, 2021
d7f7020
Update offsets.pyx
Pawel-Kranzberg Feb 18, 2021
ca67f8a
Update offsets.pyx
Pawel-Kranzberg Feb 18, 2021
e04e27d
Update offsets.pyx
Pawel-Kranzberg Feb 18, 2021
d4d87c4
Update offsets.pyx
Pawel-Kranzberg Feb 18, 2021
2c22a31
Update offsets.pyx
Pawel-Kranzberg Feb 18, 2021
11cb0b5
Update offsets.pyx
Pawel-Kranzberg Feb 18, 2021
71e9cab
Update offsets.pyx
Pawel-Kranzberg Feb 18, 2021
29edef3
Update offsets.pyx
Pawel-Kranzberg Feb 18, 2021
485e60b
[BUG] Breaking change to offsets.pyx - MonthOffset
Pawel-Kranzberg Feb 18, 2021
0fa3644
Update offsets.pyx
Pawel-Kranzberg Feb 18, 2021
3456f5a
Update offsets.pyx
Pawel-Kranzberg Feb 18, 2021
564cd29
Update offsets.pyx
Pawel-Kranzberg Feb 18, 2021
926745d
Update offsets.pyx
Pawel-Kranzberg Feb 18, 2021
4593d72
Update offsets.pyx
Pawel-Kranzberg Feb 20, 2021
85c896c
Update offsets.pyx
Pawel-Kranzberg Feb 20, 2021
6654c89
Update test_asfreq.py
Pawel-Kranzberg Feb 20, 2021
527f70f
Update offsets.pyx
Pawel-Kranzberg Feb 20, 2021
32d61ed
Update offsets.pyx
Pawel-Kranzberg Feb 20, 2021
63325a0
Update offsets.pyx
Pawel-Kranzberg Feb 20, 2021
199237a
Update dtypes.pyx
Pawel-Kranzberg Feb 20, 2021
cd79bff
Update test_asfreq.py
Pawel-Kranzberg Feb 20, 2021
42c4e26
Update test_asfreq.py
Pawel-Kranzberg Feb 20, 2021
0ce8c02
Update test_asfreq.py
Pawel-Kranzberg Feb 20, 2021
362d3d1
Update test_asfreq.py
Pawel-Kranzberg Feb 20, 2021
6f18722
Update test_period.py
Pawel-Kranzberg Feb 20, 2021
818cbdf
Update offsets.pyx
Pawel-Kranzberg Feb 20, 2021
1d14b0f
Update offsets.pyx
Pawel-Kranzberg Feb 20, 2021
700ba0e
Update offsets.pyx
Pawel-Kranzberg Feb 20, 2021
648c7a8
Update offsets.pyx
Pawel-Kranzberg Feb 20, 2021
fdc7e54
Update offsets.pyx
Pawel-Kranzberg Feb 20, 2021
070cd72
Update offsets.pyx
Pawel-Kranzberg Feb 20, 2021
efb314e
Update offsets.pyx
Pawel-Kranzberg Feb 20, 2021
045177e
Update offsets.pyx
Pawel-Kranzberg Feb 21, 2021
4f15b5b
Update offsets.pyx
Pawel-Kranzberg Feb 21, 2021
fbf3f68
Update offsets.pyx
Pawel-Kranzberg Feb 21, 2021
5f8711c
Update dtypes.pyx
Pawel-Kranzberg Mar 17, 2021
a804a3e
Update test_asfreq.py
Pawel-Kranzberg Mar 17, 2021
213fb66
fix formatting
Pawel-Kranzberg Mar 17, 2021
50ddb86
Update test_asfreq.py
Pawel-Kranzberg Mar 18, 2021
2acbd33
Fixed "MS" code mapping in _period_code_map
Pawel-Kranzberg Mar 25, 2021
6c53c39
Merge remote-tracking branch 'upstream/master'
Pawel-Kranzberg Mar 25, 2021
73f698a
Merge branch 'master' into patch-1
Pawel-Kranzberg Mar 25, 2021
407850c
Add PeriodDtypeCode to MonthBegin
Pawel-Kranzberg Mar 25, 2021
7bae3f5
Update offsets.pyx
Pawel-Kranzberg Mar 25, 2021
1c1fd3a
Fix MonthBegin PeriodDtypeCode
Pawel-Kranzberg Mar 25, 2021
e519610
Bug hunt
Pawel-Kranzberg Mar 25, 2021
c5a69b2
Remove double BusinessMonthBegin
Pawel-Kranzberg Mar 25, 2021
c618bfc
Merge pull request #1 from pandas-dev/master
Pawel-Kranzberg Mar 30, 2021
b114ff9
Merge pull request #4 from pandas-dev/master
Pawel-Kranzberg Mar 30, 2021
0628d22
Coverage bug hunt
Pawel-Kranzberg Mar 30, 2021
ff98362
Revert "Coverage bug hunt"
Pawel-Kranzberg Mar 30, 2021
56be0c9
Bug hunt
Pawel-Kranzberg Mar 30, 2021
5303ff9
Revert "Bug hunt"
Pawel-Kranzberg Mar 30, 2021
8aa0d8f
retrigger checks
Pawel-Kranzberg Apr 23, 2021
cfa2972
Update v1.3.0.rst
Pawel-Kranzberg Apr 23, 2021
045365f
Merge branch 'master' into patch-1
Pawel-Kranzberg Apr 23, 2021
118f422
Update v1.3.0.rst
Pawel-Kranzberg Apr 23, 2021
8da61dc
retrigger checks
Pawel-Kranzberg Apr 25, 2021
0ebf77b
Merge branch 'patch-1' of https://github.com/Pawel-Kranzberg/pandas i…
Pawel-Kranzberg Apr 25, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion doc/source/whatsnew/v1.3.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ Other enhancements
- :meth:`.GroupBy.any` and :meth:`.GroupBy.all` return a ``BooleanDtype`` for columns with nullable data types (:issue:`33449`)
- Constructing a :class:`DataFrame` or :class:`Series` with the ``data`` argument being a Python iterable that is *not* a NumPy ``ndarray`` consisting of NumPy scalars will now result in a dtype with a precision the maximum of the NumPy scalars; this was already the case when ``data`` is a NumPy ``ndarray`` (:issue:`40908`)
- Add keyword ``sort`` to :func:`pivot_table` to allow non-sorting of the result (:issue:`39143`)
- Date offset :class:`MonthBegin` can now be used as a period (:issue:`38859`)
-

.. ---------------------------------------------------------------------------
Expand Down Expand Up @@ -801,7 +802,7 @@ I/O
Period
^^^^^^
- Comparisons of :class:`Period` objects or :class:`Index`, :class:`Series`, or :class:`DataFrame` with mismatched ``PeriodDtype`` now behave like other mismatched-type comparisons, returning ``False`` for equals, ``True`` for not-equal, and raising ``TypeError`` for inequality checks (:issue:`39274`)
-
- Timestamp.to_period() fails for freq="MS" (:issue:`38914`)
-

Plotting
Expand Down
3 changes: 2 additions & 1 deletion pandas/_libs/tslibs/dtypes.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ _period_code_map = {
"Q-OCT": 2010, # Quarterly - October year end
"Q-NOV": 2011, # Quarterly - November year end

"M": 3000, # Monthly
"M": 3000, # Monthly - month end

"W-SUN": 4000, # Weekly - Sunday end of week
"W-MON": 4001, # Weekly - Monday end of week
Expand Down Expand Up @@ -110,6 +110,7 @@ _period_code_map.update({
"A": 1000, # Annual
"W": 4000, # Weekly
"C": 5000, # Custom Business Day
"MS": 3000, # Monthly - beginning of month
})

cdef set _month_names = {
Expand Down
63 changes: 35 additions & 28 deletions pandas/_libs/tslibs/offsets.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -2156,6 +2156,8 @@ cdef class QuarterBegin(QuarterOffset):
# Month-Based Offset Classes

cdef class MonthOffset(SingleConstructorOffset):
# _period_dtype_code = PeriodDtypeCode.M

def is_on_offset(self, dt: datetime) -> bool:
if self.normalize and not _is_normalized(dt):
return False
Expand Down Expand Up @@ -2185,21 +2187,44 @@ cdef class MonthOffset(SingleConstructorOffset):
BaseOffset.__setstate__(self, state)


cdef class MonthBegin(MonthOffset):
"""
DateOffset of one month at beginning.
"""

_period_dtype_code = PeriodDtypeCode.M
_prefix = "MS"
_day_opt = "start"


cdef class MonthEnd(MonthOffset):
"""
DateOffset of one month end.
"""

_period_dtype_code = PeriodDtypeCode.M
_prefix = "M"
_day_opt = "end"


cdef class MonthBegin(MonthOffset):
cdef class BusinessMonthBegin(MonthOffset):
"""
DateOffset of one month at beginning.
DateOffset of one month at the first business day.

Examples
--------
>>> from pandas.tseries.offsets import BusinessMonthBegin
>>> ts=pd.Timestamp('2020-05-24 05:01:15')
>>> ts + BusinessMonthBegin()
Timestamp('2020-06-01 05:01:15')
>>> ts + BusinessMonthBegin(2)
Timestamp('2020-07-01 05:01:15')
>>> ts + BusinessMonthBegin(-3)
Timestamp('2020-03-02 05:01:15')
"""
_prefix = "MS"
_day_opt = "start"

_prefix = "BMS"
_day_opt = "business_start"


cdef class BusinessMonthEnd(MonthOffset):
Expand All @@ -2208,38 +2233,20 @@ cdef class BusinessMonthEnd(MonthOffset):

Examples
--------
>>> from pandas.tseries.offset import BMonthEnd
>>> from pandas.tseries.offsets import BusinessMonthEnd
>>> ts = pd.Timestamp('2020-05-24 05:01:15')
>>> ts + BMonthEnd()
>>> ts + BusinessMonthEnd()
Timestamp('2020-05-29 05:01:15')
>>> ts + BMonthEnd(2)
>>> ts + BusinessMonthEnd(2)
Timestamp('2020-06-30 05:01:15')
>>> ts + BMonthEnd(-2)
>>> ts + BusinessMonthEnd(-2)
Timestamp('2020-03-31 05:01:15')
"""

_prefix = "BM"
_day_opt = "business_end"


cdef class BusinessMonthBegin(MonthOffset):
"""
DateOffset of one month at the first business day.

Examples
--------
>>> from pandas.tseries.offset import BMonthBegin
>>> ts=pd.Timestamp('2020-05-24 05:01:15')
>>> ts + BMonthBegin()
Timestamp('2020-06-01 05:01:15')
>>> ts + BMonthBegin(2)
Timestamp('2020-07-01 05:01:15')
>>> ts + BMonthBegin(-3)
Timestamp('2020-03-02 05:01:15')
"""
_prefix = "BMS"
_day_opt = "business_start"


# ---------------------------------------------------------------------
# Semi-Month Based Offsets

Expand All @@ -2261,7 +2268,7 @@ cdef class SemiMonthOffset(SingleConstructorOffset):
if not self._min_day_of_month <= self.day_of_month <= 27:
raise ValueError(
"day_of_month must be "
f"{self._min_day_of_month}<=day_of_month<=27, "
f"{self._min_day_of_month} <= day_of_month <= 27, "
f"got {self.day_of_month}"
)

Expand Down
42 changes: 27 additions & 15 deletions pandas/tests/scalar/period/test_asfreq.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import pytest

from pandas._libs.tslibs.dtypes import _period_code_map
from pandas._libs.tslibs.period import INVALID_FREQ_ERR_MSG
from pandas.errors import OutOfBoundsDatetime

Expand All @@ -14,7 +13,7 @@
class TestFreqConversion:
"""Test frequency conversion of date objects"""

@pytest.mark.parametrize("freq", ["A", "Q", "M", "W", "B", "D"])
@pytest.mark.parametrize("freq", ["A", "Q", "M", "MS", "W", "B", "D"])
def test_asfreq_near_zero(self, freq):
# GH#19643, GH#19650
per = Period("0001-01-01", freq=freq)
Expand Down Expand Up @@ -215,6 +214,7 @@ def test_conv_monthly(self):
ival_M_to_S_end = Period(
freq="S", year=2007, month=1, day=31, hour=23, minute=59, second=59
)
ival_MS = Period(freq="MS", year=2007, month=1)

assert ival_M.asfreq("A") == ival_M_to_A
assert ival_M_end_of_year.asfreq("A") == ival_M_to_A
Expand All @@ -235,6 +235,13 @@ def test_conv_monthly(self):
assert ival_M.asfreq("S", "E") == ival_M_to_S_end

assert ival_M.asfreq("M") == ival_M
assert ival_M.asfreq("M") != ival_MS
assert ival_M.asfreq("MS") == ival_MS
assert ival_M.asfreq("MS") != ival_M
assert ival_MS.asfreq("MS") == ival_MS
assert ival_MS.asfreq("MS") != ival_M
assert ival_MS.asfreq("M") == ival_M
assert ival_MS.asfreq("M") != ival_MS

def test_conv_weekly(self):
# frequency conversion tests: from Weekly Frequency
Expand Down Expand Up @@ -269,6 +276,7 @@ def test_conv_weekly(self):
ival_W_to_A = Period(freq="A", year=2007)
ival_W_to_Q = Period(freq="Q", year=2007, quarter=1)
ival_W_to_M = Period(freq="M", year=2007, month=1)
ival_W_to_MS = Period(freq="MS", year=2007, month=1)

if Period(freq="D", year=2007, month=12, day=31).weekday == 6:
ival_W_to_A_end_of_year = Period(freq="A", year=2007)
Expand Down Expand Up @@ -311,6 +319,7 @@ def test_conv_weekly(self):
assert ival_W_end_of_quarter.asfreq("Q") == ival_W_to_Q_end_of_quarter

assert ival_W.asfreq("M") == ival_W_to_M
assert ival_W.asfreq("MS") == ival_W_to_MS
assert ival_W_end_of_month.asfreq("M") == ival_W_to_M_end_of_month

assert ival_W.asfreq("B", "S") == ival_W_to_B_start
Expand Down Expand Up @@ -789,16 +798,19 @@ def test_asfreq_combined(self):
assert result2.ordinal == expected.ordinal
assert result2.freq == expected.freq

def test_asfreq_MS(self):
initial = Period("2013")

assert initial.asfreq(freq="M", how="S") == Period("2013-01", "M")

msg = INVALID_FREQ_ERR_MSG
with pytest.raises(ValueError, match=msg):
initial.asfreq(freq="MS", how="S")

with pytest.raises(ValueError, match=msg):
Period("2013-01", "MS")

assert _period_code_map.get("MS") is None
@pytest.mark.parametrize(
"year_month", ["2013-01", "2021-02", "2022-02", "2020-07", "2027-12"]
)
def test_asfreq_M_vs_MS(self, year_month):
year = year_month.split("-")[0]
initial = Period(year)
ts0 = Period(year_month, freq="MS").to_timestamp()
ts1 = Period(year_month, freq="M").to_timestamp()

assert initial.asfreq(freq="M", how="S") == Period(f"{year}-01", "M")
assert initial.asfreq(freq="M", how="S") != Period(f"{year}-01", "MS")
assert initial.asfreq(freq="MS", how="S") == Period(f"{year}-01", "MS")
assert initial.asfreq(freq="MS", how="S") != Period(f"{year}-01", "M")
assert initial.asfreq(freq="M", how="E") == Period(f"{year}-12", "M")
assert initial.asfreq(freq="MS", how="E") == Period(f"{year}-12", "MS")
assert ts0 == ts1
1 change: 1 addition & 0 deletions pandas/tests/scalar/period/test_period.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ def test_period_constructor_offsets(self):
)

assert Period(200701, freq=offsets.MonthEnd()) == Period(200701, freq="M")
assert Period(200701, freq=offsets.MonthBegin()) == Period(200701, freq="MS")

i1 = Period(ordinal=200701, freq=offsets.MonthEnd())
i2 = Period(ordinal=200701, freq="M")
Expand Down