pandas-dev/pandas · error · ValueError
start and end must have same freq
Error message
start and end must have same freq
What it means
Raised inside _get_ordinal_range (the engine behind period_range and PeriodIndex construction) when both `start` and `end` are Period objects but carry different frequencies. pandas cannot lay out a contiguous ordinal range whose endpoints live on incompatible grids, so it refuses rather than silently picking one freq. The check fires only when no explicit `freq` argument overrides the Periods' own frequencies.
Source
Thrown at pandas/core/arrays/period.py:1534
raise ValueError(
"Of the three parameters: start, end, and periods, "
"exactly two must be specified"
)
if freq is not None:
freq = to_offset(freq, is_period=True)
mult = freq.n
if start is not None:
start = Period(start, freq)
if end is not None:
end = Period(end, freq)
is_start_per = isinstance(start, Period)
is_end_per = isinstance(end, Period)
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:
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")
mult = freq.n
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
)View on GitHub (pinned to 71959b8cb9)
Solutions
- Pass an explicit freq= to period_range so both endpoints are re-grounded to it: period_range(start, end, freq='Q').
- Normalize both endpoints to the same freq before calling: start = start.asfreq('Q'); end = end.asfreq('Q').
- If the endpoints are strings, pass freq and let pandas parse both against it instead of constructing Periods yourself.
Example fix
// before
pd.period_range(start=pd.Period('2020','A-DEC'), end=pd.Period('2020Q1','Q'))
// after
pd.period_range(start='2020', end='2020Q1', freq='Q') Defensive patterns
Strategy: validation
Validate before calling
import pandas as pd
from pandas import Period
def safe_period_range(start, end, freq=None):
s = Period(start, freq) if not isinstance(start, Period) else start
e = Period(end, freq) if not isinstance(end, Period) else end
if freq is None and s.freq != e.freq:
freq = s.freq # or raise a clear error
return pd.period_range(start=start, end=end, freq=freq) Type guard
def same_freq_period_pair(start, end) -> bool:
return (
isinstance(start, pd.Period)
and isinstance(end, pd.Period)
and start.freq == end.freq
) Try / catch
try:
rng = pd.period_range(start=start, end=end, freq=freq)
except ValueError as e:
if 'must have same freq' in str(e):
rng = pd.period_range(start=start, end=end, freq=start.freq)
else:
raise Prevention
- Always pass an explicit freq= when both start and end are Period objects.
- Normalize endpoints with .asfreq() before constructing the range.
- Add a unit test asserting period_range inputs share a frequency.
When it happens
Trigger: Calling period_range(start=Period('2020','A-DEC'), end=Period('2020Q1','Q')) or constructing a PeriodIndex/period_range with two Period endpoints whose .freq differ and with freq=None. Because Period(end, freq) preserves end's own freq when freq is None, the mismatch surfaces here.
Common situations: Mixing annual and quarterly endpoints copied from different columns; passing a start from one df and an end inferred from another without normalizing freq; refactors that drop the explicit freq= kwarg and rely on the Period objects.
Related errors
- You must pass a freq argument as current index has none.
- start and end must not be NaT
- Could not infer freq from start/end
- Quarter must be 1 <= q <= 4
- cannot convert float NaN to integer
AI-assisted analysis of pandas-dev/pandas@71959b8cb9 (2026-08-07).
Data as JSON: /api/errors/fd40ee485718ad78.
Report an issue: GitHub.