pandas-dev/pandas · error · ValueError

freq must be a quarterly frequency

Error message

freq must be a quarterly frequency

What it means

Raised by _range_from_fields when quarter is supplied but freq resolves to a non-quarterly frequency (FreqGroup != FR_QTR). Quarter-based period construction is only meaningful for quarterly frequencies because the quarter field maps onto a 3-month window.

Source

Thrown at pandas/core/arrays/period.py:1593

) -> tuple[np.ndarray, BaseOffset]:
    if hour is None:
        hour = 0
    if minute is None:
        minute = 0
    if second is None:
        second = 0
    if day is None:
        day = 1

    if quarter is not None:
        if freq is None:
            freq = to_offset("Q", is_period=True)
            base = FreqGroup.FR_QTR.value
        else:
            freq = to_offset(freq, is_period=True)
            base = libperiod.freq_to_dtype_code(freq)
            if FreqGroup.from_period_dtype_code(base) != FreqGroup.FR_QTR:
                raise ValueError("freq must be a quarterly frequency")

        freqstr = freq.freqstr
        year, quarter = _make_field_arrays(year, quarter)
        year = _field_to_int64(year)
        quarter = _field_to_int64(quarter)

        if (quarter < 1).any() or (quarter > 4).any():
            raise ValueError("Quarter must be 1 <= q <= 4")

        # Vectorized quarter_to_myear
        mnum = MONTH_NUMBERS[parsing.get_rule_month(freqstr)] + 1
        months = (mnum + (quarter - 1) * 3) % 12 + 1
        years = np.where(months > mnum, year - 1, year)

        length = len(years)
        ones = np.ones(length, dtype=np.int64)
        zeros = np.zeros(length, dtype=np.int64)
        ordinals = libperiod.period_ordinals_from_fields(

View on GitHub (pinned to 3b7651241d)

Solutions

  1. Use freq='Q' or a quarterly anchored variant like 'Q-DEC', 'Q-JAN'.
  2. If you have year+month data instead, pass month= rather than quarter= with freq='M'.
  3. Drop the quarter argument and supply month directly for non-quarterly freqs.

Example fix

# before
pd.PeriodIndex(year=2020, quarter=[1,2,3], freq='M')
# after
pd.PeriodIndex(year=2020, quarter=[1,2,3], freq='Q')
Defensive patterns

Strategy: validation

Validate before calling

from pandas.tseries.frequencies import to_offset
from pandas._libs.tslibs.period import FreqGroup
from pandas._libs.tslibs import libperiod

def is_quarterly(freq) -> bool:
    base = libperiod.freq_to_dtype_code(to_offset(freq, is_period=True))
    return FreqGroup.from_period_dtype_code(base) == FreqGroup.FR_QTR

Type guard

def is_quarterly_freq(freq) -> bool:
    try:
        return to_offset(freq, is_period=True).name.startswith('Q')
    except Exception:
        return False

Try / catch

try:
    pi = pd.PeriodIndex(year=y, quarter=q, freq=freq)
except ValueError as e:
    if 'quarterly frequency' in str(e):
        pi = pd.PeriodIndex(year=y, quarter=q, freq='Q')
    else:
        raise

Prevention

When it happens

Trigger: pd.PeriodIndex(year=..., quarter=..., freq='M') or freq='D'; passing year+quarter arrays to PeriodIndex construction with an annual/monthly/daily freq.

Common situations: Treating quarter as a generic field independent of freq; passing an offset that is a multiple of a quarter (e.g. '2Q') without realizing the group check still passes — but passing 'A', 'M', 'W' fails.

Related errors


AI-assisted analysis of pandas-dev/pandas@3b7651241d (2026-08-11). Data as JSON: /api/errors/4c43fef85363a3fa. Report an issue: GitHub.