← Matplotlib·Lesson 9·12 min·0/3 exercises

Ticks and labels

Locators say where, formatters say what — almost every tick problem is one of the two

ax.set_xticksMultipleLocatorStrMethodFormatterDateFormatterax.tick_paramsax.margins

Watch it happen

Play it through, or step back and forth yourself.

20,00040,00060,00080,000100,000locator → whereformatter → what it says
import matplotlib.ticker as mticker

ax.yaxis.set_major_locator(
    mticker.MultipleLocator(20000))

ax.yaxis.set_major_formatter(
    mticker.StrMethodFormatter("{x:,.0f}"))

An axis has two separate jobs, done by two separate objects. A locator decides where the ticks go; a formatter decides what each one says. Almost every tick problem is one or the other, and knowing which halves the work.

The idea

An axis has two jobs, and they belong to two different objects. A locator decides where the ticks go. A formatter decides what each one says. Once you know that, tick problems stop being a search and become a question with two possible answers.

import matplotlib.ticker as mticker

ax.yaxis.set_major_locator(mticker.MultipleLocator(20))
ax.yaxis.set_major_formatter(mticker.StrMethodFormatter("{x:,.0f}"))

Note that these hang off ax.xaxis and ax.yaxis, not off ax — this is one of the few places the Axis/Axes distinction from lesson 1 actually shows up in code you write.

Setting them by hand

ax.set_xticks(range(len(items)), items)   # positions, then labels
ax.set_xticks([0, 3, 6])                  # positions only
ax.set_xticks([])                          # remove them entirely

This does both jobs at once, and it's exactly right when you know precisely what you want — seven days, five rating levels, four quarters. Its weakness is that it goes stale: the positions are hardcoded, so the day the data gains a row the labels are wrong and nothing says so.

Rotation

Long category labels collide. The standard fix:

ax.set_xticks(range(len(items)), items, rotation=45, ha="right")

ha="right" is not decoration. Rotating text about its centre leaves it drifting sideways from the tick it belongs to; right-aligning pins the end of the word to the mark. Get this wrong and the labels look subtly, unaccountably off.

The better fix is usually to sidestep it: ax.barh() gives every label a full horizontal line and nothing needs rotating at all.

Locators — a rule instead of a list

  • MultipleLocator(20) — a tick every 20.
  • MaxNLocator(5) — about five, placed at round numbers. Usually what you want.
  • LogLocator() — one per decade, for log axes.
  • AutoMinorLocator() — minor ticks between the major ones.

These keep working when the data changes, which hardcoded positions don't.

Formatters

mticker.StrMethodFormatter("{x:,.0f}")     # 30000 -> "30,000"
mticker.PercentFormatter(xmax=1.0)         # 0.256 -> "25.6%"
mticker.FuncFormatter(lambda v, pos: f"₹{v/1e6:.1f}M")

FuncFormatter takes a function of (value, position) and returns a string, which means any formatting you can express in Python is available. Currency, units, ordinals, "2h 30m" — all three lines away.

One specific irritation worth naming: the +1e7 that appears in the corner of an axis with large numbers. That's ScalarFormatter factoring out a common offset. Turn it off with ax.ticklabel_format(useOffset=False), or replace the formatter outright.

Hand-drawn notes showing crowded overlapping tick labels fixed by rotating them and by asking for a smaller number of ticks.

Dates

import matplotlib.dates as mdates

ax.xaxis.set_major_locator(mdates.AutoDateLocator())
ax.xaxis.set_major_formatter(mdates.DateFormatter("%d %b"))
fig.autofmt_xdate()      # rotate and right-align, in one call

Dates get their own locator/formatter pair because "a sensible tick" means something different for them — months, quarters, weekdays. Learn fig.autofmt_xdate() before you write the rotation out longhand.

Fewer is better

Ticks are furniture, not data. Five well-spaced ones beat twelve crowded ones. Minor ticks are usually noise. And these two are worth knowing:

ax.tick_params(axis="x", labelsize=9, length=0)   # labels, no marks
ax.margins(x=0)                                   # drop the 5% padding

Practice

Write it yourself. The answer is there when you want it.

Putting the kettle on…

Starting up…

Write it yourself

not graded

Plot Delhi's week scaled up to thousands. Put the y ticks every 20,000 and format them with thousands separators, then replace the x ticks with the day names in capitals, rotated. Trim the x margins, take the tick marks themselves off, and label the y axis. Draw the canvas — tick labels do not exist until it is drawn — and print both sets of label strings. Finish with fig.

Write something and press Run — the output appears here.

Your turn

3 exercises. Write the code yourself, then press Check — a nudge and the answer are there if you want them.

Plot delhi and replace the x tick labels with the day names in upper case. Return [t.get_text() for t in ax.get_xticklabels()].

your answer

Plot delhi, then place a y tick every 20 with a locator rather than by hand. Return [int(v) for v in ax.get_yticks()].

your answer

Plot delhi scaled up by 1000, then format the y ticks with thousands separators. Draw the figure with fig.canvas.draw() and return the rendered label texts.

your answer