pandas-dev/pandas · error · ValueError
Quarter must be 1 <= q <= 4
Error message
Quarter must be 1 <= q <= 4
What it means
Raised by _range_from_fields after _field_to_int64 conversion when any element of the quarter array is outside [1, 4]. Quarters are a closed 1..4 enumeration; anything else indicates bad input.
Solutions
- Compute quarter as (month - 1) // 3 + 1.
- Clip or filter invalid quarters before passing: q = np.where((q>=1)&(q<=4), q, np.nan) then drop NaN.
- Validate explicitly with assert df['quarter'].between(1,4).all().
Example fix
# before quarter = (month - 1) // 3 # 0..3 pd.PeriodIndex(year=year, quarter=quarter, freq='Q') # after quarter = (month - 1) // 3 + 1 # 1..4 pd.PeriodIndex(year=year, quarter=quarter, freq='Q')
Defensive patterns
Strategy: validation
Validate before calling
import numpy as np
def quarters_in_range(quarter) -> bool:
q = np.asarray(quarter)
return bool(((q >= 1) & (q <= 4)).all()) Type guard
def is_valid_quarter(q) -> bool:
try:
return 1 <= int(q) <= 4
except (TypeError, ValueError):
return False Try / catch
try:
pi = pd.PeriodIndex(year=y, quarter=q, freq='Q')
except ValueError as e:
if 'Quarter must be' in str(e):
q = np.clip(np.asarray(q).astype(int), 1, 4)
pi = pd.PeriodIndex(year=y, quarter=q, freq='Q')
else:
raise Prevention
- Compute quarter as (month - 1) // 3 + 1, never a 0-based index.
- Add df['quarter'].between(1,4).all() assertions in your pipeline.
- Reject quarter == 0 or quarter == 5 in upstream validators.
When it happens
Trigger: pd.PeriodIndex(year=..., quarter=[0,1,2], freq='Q'); quarter=[1,2,5]; quarter derived from a 0-based index without adding 1; NaN-free float values like 4.5.
Common situations: Off-by-one when computing quarter from month ((month-1)//3 instead of (month-1)//3 + 1); 0-indexed user input; data-entry typo producing quarter=5.
Related errors
- dtype must be PeriodDtype
- freq must be a quarterly frequency
- Mismatched Period array lengths
- Not enough parameters to construct Period range
- Of the three parameters: start, end, and periods, exactly…
AI-assisted analysis of pandas-dev/pandas@3b7651241d (2026-08-11).
Data as JSON: /api/errors/85b4fb6e62c8211a.
Report an issue: GitHub.
Appendix: source
Thrown at pandas/core/arrays/period.py:1601
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(
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 3b7651241d)