pandas-dev/pandas · error · ValueError

closed keyword does not match dtype.closed

Error message

closed keyword does not match dtype.closed

What it means

Raised by IntervalArray construction when both the `closed` keyword ('left'|'right'|'both'|'neither') and an IntervalDtype carrying its own `.closed` attribute are supplied, and the two disagree. The library treats `closed` as a single source of truth, so an explicit conflict is a programming error rather than something to silently pick. It fires in pandas/core/arrays/interval.py:310 inside `_ensure_simple_new_inputs`.

Source

Thrown at pandas/core/arrays/interval.py:310

        closed = closed or "right"

        if dtype is not None:
            # GH 19262: dtype must be an IntervalDtype to override inferred
            dtype = pandas_dtype(dtype)
            if isinstance(dtype, IntervalDtype):
                if dtype.subtype is not None:
                    left = left.astype(dtype.subtype)
                    right = right.astype(dtype.subtype)
            else:
                msg = f"dtype must be an IntervalDtype, got {dtype}"
                raise TypeError(msg)

            if dtype.closed is None:
                # possibly loading an old pickle
                dtype = IntervalDtype(dtype.subtype, closed)
            elif closed != dtype.closed:
                raise ValueError("closed keyword does not match dtype.closed")

        # coerce dtypes to match if needed
        if is_float_dtype(left.dtype) and is_integer_dtype(right.dtype):
            right = right.astype(left.dtype)
        elif is_float_dtype(right.dtype) and is_integer_dtype(left.dtype):
            left = left.astype(right.dtype)

        if type(left) != type(right):
            msg = (
                f"must not have differing left [{type(left).__name__}] and "
                f"right [{type(right).__name__}] types"
            )
            raise ValueError(msg)
        if (
            isinstance(left.dtype, CategoricalDtype)
            or is_string_dtype(left.dtype)
            or is_string_dtype(right.dtype)
        ):

View on GitHub (pinned to 71959b8cb9)

Solutions

  1. Pass `closed` in only one place: either via the IntervalDtype or via the `closed` keyword, not both.
  2. If you must reuse a dtype, normalize it first: `IntervalDtype(dtype.subtype, closed='right')` and drop the `closed=` keyword.
  3. When loading old pickles, reconstruct the IntervalArray without the stale dtype and let pandas infer `closed`.

Example fix

// before
pd.IntervalArray(left, right, closed='right', dtype=pd.IntervalDtype('int64', closed='left'))
// after
pd.IntervalArray(left, right, closed='right')
// or
pd.IntervalArray(left, right, dtype=pd.IntervalDtype('int64', closed='right'))
Defensive patterns

Strategy: validation

Validate before calling

def safe_interval_array(left, right, *, closed=None, dtype=None):
    if closed is not None and dtype is not None:
        d = pd.core.dtypes.dtypes.IntervalDtype.__class__
        from pandas import IntervalDtype
        if isinstance(dtype, IntervalDtype) and dtype.closed is not None and dtype.closed != closed:
            raise ValueError(f"closed={closed!r} conflicts with dtype.closed={dtype.closed!r}")
    return pd.IntervalArray(left, right, closed=closed, dtype=dtype)

Type guard

from pandas import IntervalDtype

def closed_consistent(dtype, closed) -> bool:
    return not isinstance(dtype, IntervalDtype) or dtype.closed is None or closed is None or dtype.closed == closed

Try / catch

try:
    ia = pd.IntervalArray(left, right, closed=closed, dtype=dtype)
except ValueError as e:
    if "closed keyword does not match" in str(e):
        ia = pd.IntervalArray(left, right, closed=closed)  # drop dtype.closed
    else:
        raise

Prevention

When it happens

Trigger: Calling `pd.IntervalArray(..., closed='right', dtype=pd.IntervalDtype(..., closed='left'))`, or `pd.IntervalIndex.from_arrays(left, right, closed='both', dtype='interval[left]')`, or restoring an old pickle whose IntervalDtype already stores `closed` while the constructor call also passes an explicit `closed`.

Common situations: Migrating code that previously inferred closed-ness, passing a dtype string like 'interval[int64, left]' alongside `closed='right'`, or copying a dtype from another object whose `.closed` differs from the desired one.

Related errors


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