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

  1. Normalize each IntervalArray's closure before concat by calling arr.set_closed('right') on every input so they share one closed value.
  2. Rebuild the offending arrays from their breaks with a single closed= argument (e.g. pd.interval_range(..., closed='right')).
  3. 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

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


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