pandas-dev/pandas · error · ValueError
Intervals must all be closed on the same side.
Error message
Intervals must all be closed on the same side.
What it means
Raised by IntervalArray._concat_same_type when concatenating two or more IntervalArrays whose 'closed' attribute differs (e.g., mixing 'left' with 'right'). Pandas requires all concatenated intervals to share one closure side because a single IntervalArray can only carry one 'closed' value. The check uses a set comprehension over each array's .closed and fails when more than one distinct value appears.
Source
Thrown at pandas/core/arrays/interval.py:998
and self.right.equals(other.right)
)
@classmethod
def _concat_same_type(cls, to_concat: Sequence[IntervalArray]) -> Self:
"""
Concatenate multiple IntervalArray
Parameters
----------
to_concat : sequence of IntervalArray
Returns
-------
IntervalArray
"""
closed_set = {interval.closed for interval in to_concat}
if len(closed_set) != 1:
raise ValueError("Intervals must all be closed on the same side.")
closed = closed_set.pop()
left: IntervalSide = np.concatenate([interval.left for interval in to_concat])
right: IntervalSide = np.concatenate([interval.right for interval in to_concat])
left, right, dtype = cls._ensure_simple_new_inputs(left, right, closed=closed)
return cls._simple_new(left, right, dtype=dtype)
def copy(self) -> Self:
"""
Return a copy of the array.
Returns
-------
IntervalArray
"""
left = self._left.copy()View on GitHub (pinned to 71959b8cb9)
Solutions
- Normalize each IntervalArray's closure before concat by calling arr.set_closed('right') on every input so they share one closed value.
- Rebuild the offending arrays from their breaks with a single closed= argument (e.g. pd.interval_range(..., closed='right')).
- Filter or drop the arrays whose .closed differs rather than concatenating them.
Example fix
# before
a = pd.arrays.IntervalArray.from_breaks([0,1,2], closed='left')
b = pd.arrays.IntervalArray.from_breaks([2,3,4], closed='right')
pd.concat([pd.Series(a), pd.Series(b)])
# after
a = a.set_closed('right')
b = b.set_closed('right')
pd.concat([pd.Series(a), pd.Series(b)]) Defensive patterns
Strategy: validation
Validate before calling
def safe_concat_interval(arrays):
closed_set = {a.closed for a in arrays}
if len(closed_set) != 1:
target = closed_set.pop()
arrays = [a.set_closed(target) for a in arrays]
return pd.concat([pd.Series(a) for a in arrays]) Type guard
def same_closed(arrays) -> bool:
return len({a.closed for a in arrays}) == 1 Prevention
- Standardize 'closed' across all interval sources at ingest time.
- Assert arr.closed == expected_closed before concat.
- Document the closed convention for each interval column in your schema.
When it happens
Trigger: Calling pd.concat on a list of Series/Index backed by IntervalArrays with different .closed, or IntervalArray._concat_same_type([...]) with mismatched closures, e.g. one array built with closed='left' and another closed='right'.
Common situations: Combining interval data sourced from different producers (cut/qcut defaults 'right' vs. user-built 'left'), merging interval columns created with explicit closed= arguments that disagree, or upgrading from versions where mixed concatenation was silently coerced.
Related errors
- to_concat must have the same dtype
- invalid option for 'closed': {closed}
- No such keys(s): {pat!r}
- {k} is not a valid identifier
- {k} is a python keyword
AI-assisted analysis of pandas-dev/pandas@71959b8cb9 (2026-08-07).
Data as JSON: /api/errors/ac1199ad5e831649.
Report an issue: GitHub.