Matplotlib·Lesson 10·12 min·0/3 exercises

Scales and limits

Log axes, zero baselines, and why twin axes can prove anything

ax.set_xlimax.set_yscalesymlogax.twinxax.marginssharex

Watch it happen

Play it through, or step back and forth yourself.

36
ax.margins(x=0)line touches both edges
ax.margins(0.1)10% padding both axes
ax.autoscale(enable=False)freeze it
ax.relim(); ax.autoscale_view()recompute after adding data

By default matplotlib fits the data and adds 5% breathing room on each side. That padding is ax.margins(), and setting ax.margins(x=0) makes a time series touch both edges of the frame, which usually looks better.

The idea

Limits and scales are where a chart stops being a rendering of your data and starts being an argument about it. Most of this lesson is about not making that argument by accident.

Autoscale and margins

By default matplotlib fits the data and adds 5% padding on each side. That padding is ax.margins():

ax.margins(x=0)      # line touches both edges — usually right for time series
ax.margins(0.1)       # 10% on both axes

Setting limits

ax.set_ylim(0, 100)
ax.set_ylim(bottom=0)     # pin one end, let the other float
ax.set_ylim(100, 0)       # reversed — for depth, or rank where 1 is best
ax.get_ylim()             # read it back

Setting a limit turns autoscaling off for that axis. That's usually what you want in a report — it stops the chart resizing itself when next month's data arrives — and occasionally a trap, when you set limits early and then add a series that falls outside them.

The zero question

There is a real answer here, and it depends on what the chart encodes. Bars encode value as length, so cutting the baseline changes the length without changing the number: the chart is then simply wrong. Lines encode position, and the reader is looking at the shape of the movement — forcing a stock price down to zero can flatten the very thing you're showing.

So: bars start at zero, always. Lines may be zoomed, as long as the axis is labelled and you aren't dressing up noise as a trend.

Log scales

ax.set_yscale("log")

Use it when values span orders of magnitude — populations, file sizes, case counts. On a log axis equal distances mean equal ratios, so exponential growth straightens into a line and the slope is the growth rate. On a linear axis the same data looks like nothing happened for months and then everything happened at once.

Two catches, one of them silent:

  • A log axis cannot show zero or negative values. They don't error and they don't leave a gap; they're simply not drawn. If your counts include zeros, you have lost rows and nothing told you.
  • Readers miss log axes. Put it in the label — "cases (log scale)" — because somebody who reads it as linear will misjudge every gap on the chart.

ax.set_yscale("symlog") handles data crossing zero: linear near the origin, log beyond it. There's also "logit" for probabilities.

Twin axes

fig, ax = plt.subplots()
ax.plot(when, cups, color="C0")
ax2 = ax.twinx()
ax2.plot(when, temp, color="C1")

len(fig.axes)      # 2 — twinx adds a real Axes

This is the standard way to show two quantities in different units against the same x. It's also the easiest way in matplotlib to produce a chart that is readable and false.

Both y ranges are your choice, and so the crossing points are too. Slide one scale and the same two series can be made to look like they move together, or opposite, or lead each other by a week. Nothing in the chart tells the reader which arrangement you picked.

Two honest alternatives:

# stacked, sharing the x axis
fig, (a, b) = plt.subplots(2, 1, sharex=True)

# or put both on one scale as % change from the start
pct = s / s.iloc[0] * 100

Use twinx when the two units are genuinely fixed and familiar — °C and mm of rain — and say in the caption which axis is which. Otherwise, stack them.

Sharing

fig, axes = plt.subplots(2, 1, sharex=True)   # zoom one, both move
fig, axes = plt.subplots(1, 3, sharey=True)   # one y scale, comparable panels

sharey across a row of panels is the quiet hero of small multiples: without it each panel scales itself and the comparison you built the layout for becomes meaningless.

See it run

The lesson's code, ready to run and to fiddle with.

Putting the kettle on…

Starting up…

Worked example

not graded

Already written and ready to go — press Run to see what it does, then change a number, a column name, anything, and run it again.

tryadding a zero to `growth` and watching that point vanish from the log panel.

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 pin the y axis to exactly 0 to 100. Return list(ax.get_ylim()).

your answer

Plot delhi on a log y axis, then return ax.get_yscale() to confirm it took.

your answer

Plot delhi, then add a second y scale with twinx() and plot mumbai on it. Return len(fig.axes) — the point is that a twin is another Axes.

your answer