Skip to content
19 changes: 18 additions & 1 deletion pandas/core/indexes/period.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,13 @@
Period,
Resolution,
Tick,
to_offset,
)
from pandas._libs.tslibs.dtypes import OFFSET_TO_PERIOD_FREQSTR
from pandas._libs.tslibs.dtypes import (
OFFSET_TO_PERIOD_FREQSTR,
freq_to_period_freqstr,
)
from pandas._libs.tslibs.period import INVALID_FREQ_ERR_MSG
from pandas.util._decorators import (
cache_readonly,
doc,
Expand Down Expand Up @@ -205,6 +210,18 @@ def _resolution_obj(self) -> Resolution:
**_shared_doc_kwargs,
)
def asfreq(self, freq=None, how: str = "E") -> Self:
if isinstance(freq, str):
offset = to_offset(freq, is_period=True)
if hasattr(offset, "_period_dtype_code"):
freq = freq_to_period_freqstr(offset.n, offset.name)
elif offset.name == freq.replace(f"{offset.n}", ""):
raise ValueError(
f"Invalid offset: '{offset.name}' for converting time series "
f"with PeriodIndex."
)
Copy link
Member

Choose a reason for hiding this comment

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

I don't really understand this, but I think you don't need it - just raise ValueError(INVALID_FREQ_ERR_MSG.format(f"{freq}")) in the else branch below?

Copy link
Contributor Author

@natmokval natmokval Feb 1, 2024

Choose a reason for hiding this comment

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

I use two different error messages here because we raise an error of invalid freq such as “ABC” and also for freq=“BMS” which is valid for offsets, but invalid for period. I agree, that it can be confusing, so better to use the standard error message f"Invalid frequency: {freq}"

else:
raise ValueError(INVALID_FREQ_ERR_MSG.format(f"{freq}"))

arr = self._data.asfreq(freq, how)
Copy link
Member

@MarcoGorelli MarcoGorelli Jan 31, 2024

Choose a reason for hiding this comment

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

can you follow this asfreq and put this validation even deeper?

EDIT: as mentioned in the review, it might be better to go all the way down and do this validation within to_offset itself?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

thanks, I did as you suggested and moved this validation to to_offset. It works very well.

return type(self)._simple_new(arr, name=self.name)

Expand Down
35 changes: 34 additions & 1 deletion pandas/tests/indexes/period/methods/test_asfreq.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def test_asfreq(self):

msg = "How must be one of S or E"
with pytest.raises(ValueError, match=msg):
pi7.asfreq("T", "foo")
pi7.asfreq("min", "foo")
result1 = pi1.asfreq("3M")
result2 = pi1.asfreq("M")
expected = period_range(freq="M", start="2001-12", end="2001-12")
Expand Down Expand Up @@ -136,3 +136,36 @@ def test_asfreq_with_different_n(self):

excepted = Series([1, 2], index=PeriodIndex(["2020-02", "2020-04"], freq="M"))
tm.assert_series_equal(result, excepted)

@pytest.mark.parametrize(
"freq",
[
"2BMS",
"2YS-MAR",
"2bh",
],
)
def test_pi_asfreq_invalid_offset(self, freq):
# GH#55785
msg = f"Invalid offset: '{freq[1:]}' for converting time series "

pi = PeriodIndex(["2020-01-01", "2021-01-01"], freq="M")
with pytest.raises(ValueError, match=msg):
pi.asfreq(freq=freq)

@pytest.mark.parametrize(
"freq",
[
"2BME",
"2YE-MAR",
"2BM",
"2QE",
],
)
def test_pi_asfreq_invalid_frequency(self, freq):
# GH#55785
msg = f"Invalid frequency: {freq}"

pi = PeriodIndex(["2020-01-01", "2021-01-01"], freq="M")
with pytest.raises(ValueError, match=msg):
pi.asfreq(freq=freq)
4 changes: 2 additions & 2 deletions pandas/tests/resample/test_period_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -1032,8 +1032,8 @@ def test_resample_lowercase_frequency_deprecated(
offsets.BusinessHour(2),
],
)
def test_asfreq_invalid_period_freq(self, offset, frame_or_series):
# GH#9586
def test_asfreq_invalid_period_offset(self, offset, frame_or_series):
# GH#55785
msg = f"Invalid offset: '{offset.base}' for converting time series "

obj = frame_or_series(range(5), index=period_range("2020-01-01", periods=5))
Expand Down