Scales, ticks and date axes

Log and symmetric-log scales, tick locators and formatters, and getting date and category axes to read the way you intend.

Scales and limits

ax.set_yscale("log")                       # orders of magnitude
ax.set_xscale("symlog", linthresh=1.0)    # log scale that tolerates zero
ax.set_xscale("logit")                    # proportions between 0 and 1

ax.set_xlim(0, 100)
ax.set_ylim(bottom=0)                     # keep the top automatic
ax.invert_yaxis()                         # depth, rank and lat/lon charts

ax.margins(x=0.02, y=0.05)                # breathing room around the data
ax.autoscale(enable=True, axis="y", tight=False)
  • A log scale cannot represent zero or negatives; those points are masked out, which looks like missing data.
  • Use a log scale when ratios matter and a linear scale when differences matter.
  • symlog keeps a linear region near zero so you can show zeros alongside large values.
  • Always state the scale in the axis label — readers assume linear.

Tick locators and formatters

from matplotlib.ticker import (MultipleLocator, MaxNLocator,
                               PercentFormatter, FuncFormatter)

ax.xaxis.set_major_locator(MultipleLocator(10))
ax.yaxis.set_major_locator(MaxNLocator(nbins=6))
ax.yaxis.set_major_formatter(PercentFormatter(decimals=0))

ax.yaxis.set_major_formatter(FuncFormatter(lambda v, pos: f"{v/1000:.0f}k"))
ax.tick_params(axis="x", rotation=45, labelsize=9, length=3)
ax.minorticks_on()
ax.grid(True, which="major", alpha=0.25)
LocatorPlaces ticks atTypical use
MultipleLocator(10)Fixed intervalsPredictable grids
MaxNLocator(nbins=6)Up to n nice valuesAutomatic but controlled
FixedLocator([...])Exact positionsHand-picked categories
LogLocatorPowers of the baseLog axes
AutoMinorLocator(2)SubdivisionsDenser grid without labels

Date and category axes

import matplotlib.dates as mdates

ax.plot(dates, values)
ax.xaxis.set_major_locator(mdates.MonthLocator(interval=1))
ax.xaxis.set_major_formatter(mdates.DateFormatter("%b %Y"))
fig.autofmt_xdate()                       # rotate and align the labels

# zooming into a range: pick the units that fit the span
ax.xaxis.set_major_locator(mdates.DayLocator(interval=7))
ax.xaxis.set_major_formatter(mdates.DateFormatter("%d %b"))

# category axis: keep the labels you supplied
ax.bar(labels, values)
ax.set_xticks(range(len(labels)), labels, rotation=30, ha="right")
⚠️
Pass real datetimes, not strings. A string axis is treated as categories, which spaces irregular dates evenly and silently misleads about the timing of events.

FAQ

Why have my zero values disappeared on a log axis?
They cannot be plotted. Use symlog with a suitable linthresh, or plot the values with an offset and label the axis accordingly.
How do I get thousands separators on the axis?
Use a formatter: ax.yaxis.set_major_formatter("{x:,.0f}".format) or a FuncFormatter for anything more elaborate.

Debugging plots: empty axes, missing data and overlap Chart types: bar, scatter, histogram, pie and box

Last refreshed 2026-09-18.