Log scales and twin axes

Matplotlib · Practical · 10 min

Growth, latency and frequency data often span decades, where a linear axis hides everything but the largest values.

ax.set_yscale("log") (or ax.set_xscale) spaces ticks by powers of ten, so equal distances mean equal ratios. Exponential growth becomes a straight line, and small and huge values are both visible. ax.loglog, ax.semilogy and ax.semilogx are shortcuts that plot and set scales at once.

Log scales cannot show zero or negative values. By default those points are masked (not drawn). If your data crosses zero, use:

  • "symlog": linear within linthresh of zero and logarithmic beyond, symmetric for negatives.
  • "asinh": a smooth alternative with similar behavior.
  • "logit" for probabilities very close to 0 or 1.

A twin axis shares the x axis but has an independent y axis on the right: ax2 = ax.twinx(). Use it to overlay, say, temperature and rainfall over the same months. Each Axes has its own lines, so a combined legend needs the handles from both:

h1, l1 = ax.get_legend_handles_labels()
h2, l2 = ax2.get_legend_handles_labels()
ax.legend(h1 + h2, l1 + l2)

Color each y axis label like its line so readers know which scale applies.

Use twin axes sparingly: two independent scales let a chart suggest correlations that depend only on how you set the limits. Side-by-side subplots with a shared x are often more honest.

Example

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np

days = np.arange(1, 31)
users = 10 * 1.3 ** days

fig, ax = plt.subplots()
ax.plot(days, users, color="C0", label="users")
ax.set_yscale("log")                          # exponential -> straight line
ax.set_ylabel("Users (log)", color="C0")

ax2 = ax.twinx()                               # second y axis, same x
ax2.plot(days, np.gradient(users) / users * 100, "C1--", label="growth %")
ax2.set_ylabel("Daily growth (%)", color="C1")

h1, l1 = ax.get_legend_handles_labels()
h2, l2 = ax2.get_legend_handles_labels()
ax.legend(h1 + h2, l1 + l2, loc="upper left")
print(ax.get_yscale(), ax2.get_yscale())       # log linear
fig.savefig("growth.png")

Remember

  • Log scales show ratios; exponential growth becomes linear
  • Use symlog or asinh when data includes zero or negatives
  • ax.twinx() adds an independent right y axis; merge legends manually

Watch out for

  • Plotting zeros on a log axis and silently losing those points.

Check yourself

  1. Your data includes zeros and values up to one million. Which scale shows all of it with log-like spacing for large values?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: matplotlib.org/stable/users/explain/axes/axes_scales.html

Builds on

Unlocks

Nothing builds on this yet.