matplotlib/matplotlib · error · ValueError

must be same number of segments as levels

Error message

must be same number of segments as levels

What it means

On the manual-construction path (ContourSet(levels, allsegs, allkinds)), unfilled line contours require exactly one segment list per level: len(allsegs) == len(levels). Unlike the filled case, line contours get a segment list for every level, including levels with no contour lines (represented by an empty list). ValueError is raised on any other count.

Source

Thrown at lib/matplotlib/contour.py:916

        Must set self.levels, self.zmin and self.zmax, and update Axes limits.
        """
        self.levels = args[0]
        allsegs = args[1]
        allkinds = args[2] if len(args) > 2 else None
        self.zmax = np.max(self.levels)
        self.zmin = np.min(self.levels)

        if allkinds is None:
            allkinds = [[None] * len(segs) for segs in allsegs]

        # Check lengths of levels and allsegs.
        if self.filled:
            if len(allsegs) != len(self.levels) - 1:
                raise ValueError('must be one less number of segments as '
                                 'levels')
        else:
            if len(allsegs) != len(self.levels):
                raise ValueError('must be same number of segments as levels')

        # Check length of allkinds.
        if len(allkinds) != len(allsegs):
            raise ValueError('allkinds has different length to allsegs')

        # Determine x, y bounds and update axes data limits.
        flatseglist = [s for seg in allsegs for s in seg]
        points = np.concatenate(flatseglist, axis=0)
        self._mins = points.min(axis=0)
        self._maxs = points.max(axis=0)

        # Each entry in (allsegs, allkinds) is a list of (segs, kinds): segs is a list
        # of (N, 2) arrays of xy coordinates, kinds is a list of arrays of corresponding
        # pathcodes.  However, kinds can also be None; in which case all paths in that
        # list are codeless (this case is normalized above).  These lists are used to
        # construct paths, which then get concatenated.
        self._paths = [Path.make_compound_path(*map(Path, segs, kinds))
                       for segs, kinds in zip(allsegs, allkinds)]

View on GitHub (pinned to b379c1b69e)

Solutions

  1. Supply exactly len(levels) segment lists; keep empty lists [] for levels with no geometry.
  2. If the geometry was computed for filled bands, regenerate it as lines (contour_generator(...).lines(levels)) or construct with filled=True.
  3. Recompute rather than hand-trimming: len(allsegs) must equal len(levels) at construction time.
  4. Add a pre-check: assert len(allsegs) == len(levels) before constructing.

Example fix

# before
segs = [s for s in line_segs if s]   # accidentally dropped empties
ContourSet(ax, levels, segs)         # len mismatch -> ValueError

# after
ContourSet(ax, levels, line_segs)   # keep one (possibly empty) list per level
Defensive patterns

Strategy: validation

Validate before calling

assert len(allsegs) == len(levels), \
    f'{len(allsegs)} segment lists vs {len(levels)} levels'

Try / catch

try:
    ContourSet(ax, levels, allsegs)
except ValueError:
    allsegs = [allsegs[i] if i < len(allsegs) else [] for i in range(len(levels))]
    ContourSet(ax, levels, allsegs)

Prevention

When it happens

Trigger: Building ContourSet with line-contour geometry that dropped empty levels (e.g. filtered out levels where no line was found); passing band geometry (len(levels)-1 lists, correct only for filled=True) while leaving filled=False; off-by-one errors when slicing cached geometry.

Common situations: Replaying serialized contour geometry; mixing up filled and line geometry conventions when switching between contourf output and contour; trimming 'empty' segment lists during caching to save space.

Related errors


AI-assisted analysis of matplotlib/matplotlib@b379c1b69e (2026-08-21). Data as JSON: /api/errors/1ca1e104b89dd1f5. Report an issue: GitHub.