Matplotlib Secondary Axis: Twin Axes vs secondary_yaxis Explained
Updated on
When one plot needs two scales (think dollars vs units, Celsius vs Fahrenheit), slapping a second axis in the wrong way makes readers misjudge trends. Misaligned ticks, mismatched legends, and clipped labels are common traps. The fix is simple: pick the right dual-axis tool and apply a clear conversion so both scales stay honest.
Quick decision guide
Use this before writing code:
| Approach | Use when | Pros | Watch-outs |
|---|---|---|---|
twinx / twiny | Two unrelated data series that just share x (or y) positions | Fast, minimal code | Manual tick formatting; no automatic scale linking |
secondary_yaxis / secondary_xaxis | Two series are convertible (e.g., Celsius ↔ Fahrenheit) | Guarantees tick alignment via forward/inverse functions | Must supply accurate transforms; only for monotonic conversions |
| Two subplots with shared axes | Readers must compare without overplot | Clear separation | Needs more space; legends per subplot |
Recipe: twinx for two y-scales
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(1, 13)
sales = np.array([12, 18, 25, 30, 28, 35, 40, 38, 32, 28, 22, 18])
customers = np.array([120, 155, 180, 210, 205, 230, 245, 240, 225, 200, 175, 150])
fig, ax = plt.subplots(figsize=(8, 4))
ax.plot(x, sales, color="tab:blue", label="Sales (k$)")
ax.set_xlabel("Month")
ax.set_ylabel("Sales (k$)", color="tab:blue")
ax.tick_params(axis="y", labelcolor="tab:blue")
ax2 = ax.twinx()
ax2.plot(x, customers, color="tab:orange", label="Customers")
ax2.set_ylabel("Customers", color="tab:orange")
ax2.tick_params(axis="y", labelcolor="tab:orange")
lines = ax.get_lines() + ax2.get_lines()
labels = [line.get_label() for line in lines]
ax.legend(lines, labels, loc="upper left")
plt.tight_layout()
plt.show()Tips:
- Use distinct colors per axis to avoid legend confusion.
- Collect lines from both axes to build a single legend.
- Call
tight_layout()orconstrained_layout=Trueto avoid clipped labels.
Recipe: secondary_yaxis with safe conversions
import matplotlib.pyplot as plt
import numpy as np
temps_c = np.linspace(-20, 40, 200)
fig, ax = plt.subplots(figsize=(7, 4))
ax.plot(temps_c, np.sin(temps_c / 10), color="tab:blue", label="Signal vs °C")
ax.set_xlabel("Temperature (°C)")
ax.set_ylabel("Signal")
def c_to_f(c):
return c * 9/5 + 32
def f_to_c(f):
return (f - 32) * 5/9
ax_f = ax.secondary_xaxis("top", functions=(c_to_f, f_to_c))
ax_f.set_xlabel("Temperature (°F)")
ax.legend(loc="lower right")
plt.tight_layout()
plt.show()Tips:
- Always provide both forward and inverse functions so ticks stay aligned.
- Keep conversions monotonic; avoid nonlinear, non-invertible transforms.
- Format ticks (e.g.,
ax_f.xaxis.set_major_formatter("{x:.0f}°F")) for clarity.
Keep scales honest
- Avoid dual-axis when the series are unrelated; separate subplots may be clearer.
- Check for misleading slopes: if one series dwarfs the other, normalize first.
- Sync gridlines selectively: enable only on the primary axis to reduce clutter.
Troubleshooting checklist
- Stacked legends? Combine lines from both axes before calling
legend. - Labels clipped? Use
constrained_layout=Trueinsubplotsor addplt.tight_layout(). - Mismatched tick colors? Apply
tick_params(labelcolor=...)to both axes.