Skip to content

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:

ApproachUse whenProsWatch-outs
twinx / twinyTwo unrelated data series that just share x (or y) positionsFast, minimal codeManual tick formatting; no automatic scale linking
secondary_yaxis / secondary_xaxisTwo series are convertible (e.g., Celsius ↔ Fahrenheit)Guarantees tick alignment via forward/inverse functionsMust supply accurate transforms; only for monotonic conversions
Two subplots with shared axesReaders must compare without overplotClear separationNeeds 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() or constrained_layout=True to 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=True in subplots or add plt.tight_layout().
  • Mismatched tick colors? Apply tick_params(labelcolor=...) to both axes.