matplotlib/matplotlib · error · ValueError

'step' must be positive

Error message

'step' must be positive

What it means

_Edge_integer is the internal helper behind MultipleLocator, MaxNLocator and the matplotlib.dates locators that snaps tick positions to integer multiples of a step. Its constructor requires step > 0 because every operation on it divides by the step (divmod-based largest/smallest multiple); zero or negative steps would divide by zero or reverse the tick ladder, so they are rejected immediately.

Source

Thrown at lib/matplotlib/ticker.py:2089

    """
    Helper for `.MaxNLocator`, `.MultipleLocator`, etc.

    Take floating-point precision limitations into account when calculating
    tick locations as integer multiples of a step.
    """

    def __init__(self, step, offset):
        """
        Parameters
        ----------
        step : float > 0
            Interval between ticks.
        offset : float
            Offset subtracted from the data limits prior to calculating tick
            locations.
        """
        if step <= 0:
            raise ValueError("'step' must be positive")
        self.step = step
        self._offset = abs(offset)

    def closeto(self, ms, edge):
        # Allow more slop when the offset is large compared to the step.
        if self._offset > 0:
            digits = np.log10(self._offset / self.step)
            tol = max(1e-10, 10 ** (digits - 12))
            tol = min(0.4999, tol)
        else:
            tol = 1e-10
        return abs(ms - edge) < tol

    def le(self, x):
        """Return the largest n: n*step <= x."""
        d, m = divmod(x, self.step)
        if self.closeto(m / self.step, 1):
            return d + 1

View on GitHub (pinned to b379c1b69e)

Solutions

  1. Ensure the value passed to MultipleLocator, or the base/interval of a date locator, is a strictly positive number.
  2. Guard computed spacings: if step <= 0 or not finite, skip installing the locator or clamp to a tiny positive value.
  3. For date locators use interval=1 (or leave it out) rather than 0.

Example fix

import numpy as np
from matplotlib import ticker

// before
step = (x.max() - x.min()) / 5  # 0 when x is constant
ax.xaxis.set_major_locator(ticker.MultipleLocator(step))

// after
step = (x.max() - x.min()) / 5
if step > 0:
    ax.xaxis.set_major_locator(ticker.MultipleLocator(step))
# else: keep the default AutoLocator
Defensive patterns

Strategy: validation

Validate before calling

import numpy as np
from matplotlib import ticker

def safe_tick_step(data, n=5):
    span = np.ptp(data) if len(data) else 0.0
    return span / n if span > 0 else None

step = safe_tick_step(x)
if step:
    ax.xaxis.set_major_locator(ticker.MultipleLocator(step))

Prevention

When it happens

Trigger: ticker.MultipleLocator(0) or MultipleLocator(base=-5); a date locator built with a non-positive base/interval, e.g. matplotlib.dates.DayLocator(interval=0) (dates.py constructs _Edge_integer(base, 0) with it); rarely, a MaxNLocator whose internal staircase step degenerates to 0 with pathological data ranges.

Common situations: Spacing derived from data, e.g. step = (x.max() - x.min()) / n, which becomes 0 for constant or empty data; passing a negative value intending 'step backwards'; treating interval=0 on date locators as 'use default'.

Related errors


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