diff --git a/doc/source/whatsnew/v1.5.0.rst b/doc/source/whatsnew/v1.5.0.rst index 0b450fab53137..6b90126f556e0 100644 --- a/doc/source/whatsnew/v1.5.0.rst +++ b/doc/source/whatsnew/v1.5.0.rst @@ -959,6 +959,7 @@ Period - Bug in adding ``np.timedelta64("NaT", "ns")`` to a :class:`Period` with a timedelta-like freq incorrectly raising ``IncompatibleFrequency`` instead of returning ``NaT`` (:issue:`47196`) - Bug in adding an array of integers to an array with :class:`PeriodDtype` giving incorrect results when ``dtype.freq.n > 1`` (:issue:`47209`) - Bug in subtracting a :class:`Period` from an array with :class:`PeriodDtype` returning incorrect results instead of raising ``OverflowError`` when the operation overflows (:issue:`47538`) +- Bug in :func:`period_range` ignoring frequency multiple when deriving ``freq`` from ``start`` or ``end`` field (:issue:`47465`) - Plotting diff --git a/pandas/core/arrays/period.py b/pandas/core/arrays/period.py index 4d97345912250..53ef88263b6bb 100644 --- a/pandas/core/arrays/period.py +++ b/pandas/core/arrays/period.py @@ -1047,52 +1047,40 @@ def dt64arr_to_periodarr(data, freq, tz=None): return c_dt64arr_to_periodarr(data.view("i8"), base, tz, reso=reso), freq -def _get_ordinal_range(start, end, periods, freq, mult=1): +def _get_ordinal_range(start, end, periods, freq): if com.count_not_none(start, end, periods) != 2: raise ValueError( "Of the three parameters: start, end, and periods, " "exactly two must be specified" ) - if freq is not None: - freq = to_offset(freq) - mult = freq.n + # If `freq` is passed, it will overwrite the freq of start and end, but + # doesn't change it if it is None. + # Note: `to_offset(None) == None`. + freq = to_offset(freq) - if start is not None: - start = Period(start, freq) - if end is not None: - end = Period(end, freq) + # become NaT, in case of None + start = Period(start, freq) + end = Period(end, freq) - is_start_per = isinstance(start, Period) - is_end_per = isinstance(end, Period) + if periods is not None: + # we decrease periods here, since we later do end + 1 + periods -= 1 - if is_start_per and is_end_per and start.freq != end.freq: - raise ValueError("start and end must have same freq") - if start is NaT or end is NaT: + if start is NaT and end is NaT: raise ValueError("start and end must not be NaT") - if freq is None: - if is_start_per: - freq = start.freq - elif is_end_per: - freq = end.freq - else: # pragma: no cover - raise ValueError("Could not infer freq from start/end") - - if periods is not None: - periods = periods * mult - if start is None: - data = np.arange( - end.ordinal - periods + mult, end.ordinal + 1, mult, dtype=np.int64 - ) - else: - data = np.arange( - start.ordinal, start.ordinal + periods, mult, dtype=np.int64 - ) + if start is not NaT and end is not NaT: + if start.freq != end.freq: + raise ValueError("start and end must have same freq") + elif start is NaT: + start = end - periods else: - data = np.arange(start.ordinal, end.ordinal + 1, mult, dtype=np.int64) + end = start + periods + + data = np.arange(start.ordinal, (end + 1).ordinal, start.freq.n, dtype=np.int64) - return data, freq + return data, start.freq def _range_from_fields( diff --git a/pandas/tests/series/test_constructors.py b/pandas/tests/series/test_constructors.py index 3dce22a06c1b2..8a220961183ad 100644 --- a/pandas/tests/series/test_constructors.py +++ b/pandas/tests/series/test_constructors.py @@ -1253,6 +1253,14 @@ def test_constructor_periodindex(self): expected = Series(pi.astype(object)) tm.assert_series_equal(s, expected) + def test_constructor_periodindex_multiple(self): + # GH47465 + + p = Period("2020-01-01") + pi = period_range(p, periods=5) + + assert pi[0] + 1 == pi[1] + def test_constructor_dict(self): d = {"a": 0.0, "b": 1.0, "c": 2.0}