pandas-dev/pandas · error · ValueError

Quarter must be 1 <= q <= 4

Error message

Quarter must be 1 <= q <= 4

What it means

Raised in _range_from_fields when a vectorized `quarter` array contains values outside [1,4]. quarters are mapped to month ordinals arithmetically, so an out-of-range quarter would produce nonsense calendar dates; pandas rejects it up front. It is reached via period_range(year=..., quarter=..., freq='Q').

Source

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

        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 base != FreqGroup.FR_QTR.value:
                raise AssertionError("base must equal FR_QTR")

        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(
            years, months, ones, zeros, zeros, zeros, base
        )
    else:
        freq = to_offset(freq, is_period=True)
        base = libperiod.freq_to_dtype_code(freq)
        arrays = _make_field_arrays(year, month, day, hour, minute, second)
        ordinals = libperiod.period_ordinals_from_fields(
            _field_to_int64(arrays[0]),

View on GitHub (pinned to 71959b8cb9)

Solutions

  1. Compute quarter as ((month - 1) // 3) + 1 to guarantee the 1-4 range.
  2. Clip/validate the quarter column before calling: assert quarter between 1 and 4.
  3. If passing a scalar quarter, ensure it is a literal in 1..4.

Example fix

// before
pd.period_range(year=2020, quarter=(month // 3), freq='Q')
// after
pd.period_range(year=2020, quarter=((month - 1) // 3) + 1, freq='Q')
Defensive patterns

Strategy: validation

Validate before calling

import pandas as pd

def period_range_quarter(year, quarter, freq='Q'):
    q = pd.Series(quarter)
    if ((q < 1) | (q > 4)).any():
        raise ValueError(f'quarter out of range: {q.tolist()}')
    return pd.period_range(year=year, quarter=quarter, freq=freq)

Type guard

def valid_quarter(q) -> bool:
    import numpy as np
    arr = np.asarray(q)
    return bool(((arr >= 1) & (arr <= 4)).all())

Try / catch

try:
    rng = pd.period_range(year=year, quarter=quarter, freq='Q')
except ValueError as e:
    if 'Quarter must be' in str(e):
        quarter = [min(4, max(1, q)) for q in quarter]
        rng = pd.period_range(year=year, quarter=quarter, freq='Q')
    else:
        raise

Prevention

When it happens

Trigger: pd.period_range(year=2020, quarter=5, freq='Q'); period_range(year=[2020,2021], quarter=[1,7]); quarter computed mod-1 or off-by-one from a 0-based month formula (quarter = month // 3 yielding 0 or 4).

Common situations: Deriving quarter from month with integer division that yields 0 (Jan) or 4 (Dec) instead of 1-4; UI dropdowns returning 0-indexed quarters; data entry or CSV with quarter=0 or quarter=5.

Related errors


AI-assisted analysis of pandas-dev/pandas@71959b8cb9 (2026-08-07). Data as JSON: /api/errors/85b4fb6e62c8211a. Report an issue: GitHub.