To add a secondary y-axis in Matplotlib, call ax2 = ax.twinx(): it creates a second axes that shares the x-axis and puts its y-axis on the right, so two series with different scales can share one plot. If the right axis should show the same data in another unit (°C and °F, km and miles), use ax.secondary_yaxis() instead. This guide covers two y-axes, a combined legend, bar-and-line charts, three y-axes, pandas and seaborn, and secondary axes in subplots.
Tested with Python 3.12.5, Matplotlib 3.11.2, pandas 3.0.6 and seaborn 0.13.2; the plots are real Matplotlib windows. Reference: Axes.twinx and Secondary axis example.
Two y-axes with twinx()
import matplotlib.pyplot as plt
months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
revenue = [120, 132, 150, 161, 175, 190, 210, 205, 188, 170, 160, 230] # thousand USD
customers = [4.2, 4.0, 3.7, 3.5, 3.6, 3.4, 3.9, 4.3, 4.6, 4.8, 5.1, 5.6] # thousand people
fig, ax1 = plt.subplots(figsize=(8, 4.5))
ax1.plot(months, revenue, color="tab:blue", marker="o", label="Revenue")
ax1.set_ylabel("Revenue (thousand USD)", color="tab:blue")
ax1.tick_params(axis="y", labelcolor="tab:blue")
ax2 = ax1.twinx() # second y-axis on the right, same x-axis
ax2.plot(months, customers, color="tab:red", marker="s", label="Customers")
ax2.set_ylabel("Customers (thousands)", color="tab:red")
ax2.tick_params(axis="y", labelcolor="tab:red")
lines = ax1.get_lines() + ax2.get_lines() # one legend for both axes
ax1.legend(lines, [l.get_label() for l in lines], loc="upper left")
plt.tight_layout()
plt.show()
twinx() returns a completely new axes object placed on top of the first one. Everything you do to the right axis goes through ax2:
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
fig, ax1 = plt.subplots()
ax1.plot([1, 2, 3], [100, 200, 300])
ax2 = ax1.twinx()
ax2.plot([1, 2, 3], [0.1, 0.5, 0.2])
print("axes in the figure:", len(fig.axes))
print("ax1 y limits:", [round(float(v), 2) for v in ax1.get_ylim()])
print("ax2 y limits:", [round(float(v), 2) for v in ax2.get_ylim()])
print("shared x limits:", ax1.get_xlim() == ax2.get_xlim())
print("ax2 y-axis side:", ax2.yaxis.get_label_position())
Output:
axes in the figure: 2
ax1 y limits: [90.0, 310.0]
ax2 y limits: [0.08, 0.52]
shared x limits: True
ax2 y-axis side: right
Because each axes has its own legend, collect the lines from both (as above) or use fig.legend() to get one combined legend.
secondary_yaxis(): the same data in another unit
twinx() is for a second data series. When the right axis is only a different unit for the same values, secondary_yaxis() with a pair of conversion functions keeps both axes in sync automatically, even when you zoom:
import matplotlib.pyplot as plt
months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun"]
temp_c = [-2, 1, 6, 12, 18, 23]
fig, ax = plt.subplots(figsize=(7, 4))
ax.plot(months, temp_c, marker="o")
ax.set_ylabel("°C")
# a secondary axis that shows the SAME data in another unit
secax = ax.secondary_yaxis("right", functions=(lambda c: c * 9 / 5 + 32, lambda f: (f - 32) * 5 / 9))
secax.set_ylabel("°F")
ax.set_title("secondary_yaxis: Celsius and Fahrenheit")
plt.tight_layout()
plt.show()
secondary_yaxis("right", functions=(to_f, to_c)).Bar chart and line chart with two y-axes
import matplotlib.pyplot as plt
months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
rainfall = [78, 60, 70, 65, 80, 95, 110, 105, 90, 85, 88, 82] # mm
temperature = [1, 2, 7, 13, 18, 23, 26, 25, 21, 14, 8, 3] # °C
fig, ax1 = plt.subplots(figsize=(8, 4.5))
ax1.bar(months, rainfall, color="lightsteelblue", label="Rainfall (mm)")
ax1.set_ylabel("Rainfall (mm)")
ax2 = ax1.twinx()
ax2.plot(months, temperature, color="tab:orange", marker="o", linewidth=2, label="Temperature (°C)")
ax2.set_ylabel("Temperature (°C)")
fig.legend(loc="upper left", bbox_to_anchor=(0.1, 0.9))
plt.tight_layout()
plt.show()
Three or more y-axes
Create another twinx() and move its spine outward so the axes do not overlap. Leave space on the right with subplots_adjust(right=...):
import matplotlib.pyplot as plt
import numpy as np
hours = np.arange(0, 24)
temp = 15 + 8 * np.sin((hours - 8) / 24 * 2 * np.pi)
humidity = 70 - 20 * np.sin((hours - 8) / 24 * 2 * np.pi)
wind = 5 + 3 * np.cos(hours / 24 * 2 * np.pi)
fig, ax1 = plt.subplots(figsize=(9, 4.5))
fig.subplots_adjust(right=0.78)
ax2 = ax1.twinx()
ax3 = ax1.twinx()
ax3.spines["right"].set_position(("axes", 1.15)) # move the third axis further right
p1, = ax1.plot(hours, temp, color="tab:red", label="Temperature (°C)")
p2, = ax2.plot(hours, humidity, color="tab:blue", label="Humidity (%)")
p3, = ax3.plot(hours, wind, color="tab:green", label="Wind (m/s)")
for ax, p in [(ax1, p1), (ax2, p2), (ax3, p3)]:
ax.set_ylabel(p.get_label(), color=p.get_color())
ax.tick_params(axis="y", colors=p.get_color())
ax1.set_xlabel("Hour of day")
plt.show()
spines["right"].set_position(("axes", 1.15)).Secondary y-axis with pandas
DataFrame.plot(secondary_y=...) puts the listed columns on a right-hand axis, available afterwards as ax.right_ax:
import matplotlib.pyplot as plt
import pandas as pd
df = pd.DataFrame({"visits": [1200, 1350, 1600, 1580, 1900, 2250],
"conversion_rate": [2.1, 2.4, 2.2, 2.9, 3.1, 3.4]},
index=["Jan", "Feb", "Mar", "Apr", "May", "Jun"])
ax = df.plot(secondary_y="conversion_rate", marker="o", figsize=(7, 4)) # right axis for this column
ax.set_ylabel("Visits")
ax.right_ax.set_ylabel("Conversion rate (%)")
plt.tight_layout()
plt.show()
df.plot(secondary_y="conversion_rate").Secondary y-axis in subplots
Call twinx() on each subplot that needs a second axis:
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(1, 13)
stores = {"Store A": (x * 10 + 50, 34 + 4 * np.sin(x / 2)), "Store B": (x * 7 + 80, 45 - x)}
fig, axes = plt.subplots(1, 2, figsize=(10, 4))
for ax, (name, (sales, staff)) in zip(axes, stores.items()):
ax.plot(x, sales, color="tab:blue")
ax.set_ylabel("Sales", color="tab:blue")
right = ax.twinx() # each subplot gets its own secondary axis
right.plot(x, staff, color="tab:green", linestyle="--")
right.set_ylabel("Staff", color="tab:green")
ax.set_title(name)
ax.set_xlabel("Month")
plt.tight_layout()
plt.show()
Secondary y-axis with seaborn
Seaborn functions accept ax=, so draw one plot on ax1 and the other on ax1.twinx(). With a bar plot on the x-axis, seaborn uses positions 0, 1, 2 …, so the line uses range(len(df)) as x:
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
df = pd.DataFrame({"day": range(1, 11),
"orders": [52, 58, 61, 55, 70, 74, 69, 80, 85, 83],
"avg_basket": [31.5, 30.2, 32.8, 33.1, 29.9, 31.0, 34.2, 35.0, 33.7, 36.1]})
fig, ax1 = plt.subplots(figsize=(7, 4))
sns.barplot(data=df, x="day", y="orders", color="lightgray", ax=ax1)
ax2 = ax1.twinx() # seaborn draws on any Matplotlib axes
sns.lineplot(x=range(len(df)), y=df["avg_basket"], color="tab:purple", marker="o", ax=ax2)
ax2.set_ylabel("Average basket (USD)")
plt.tight_layout()
plt.show()
barplot and lineplot on two y-axes.Tips for charts with two y-axes
- Colour each axis label and tick labels like its data so readers do not mix them up.
- Two axes can suggest a relationship that is not there, because the scales are arbitrary; consider two stacked subplots with
sharex=Trueas an alternative. - Set sensible limits for both axes (
ax1.set_ylim(),ax2.set_ylim()), for example starting both at zero. - For a second x-axis on top, use
twiny()orsecondary_xaxis("top").
More Matplotlib axis tutorials:
- Set the y-axis range in Matplotlib
- Plot multiple lines in Python
- Invert the y-axis in Matplotlib
- Style ticks with tick_params()
Frequently asked questions
How do I add a secondary y-axis in Matplotlib?
Call ax2 = ax.twinx() and plot the second series on ax2. Its y-axis appears on the right and it shares the x-axis with ax.
What is the difference between twinx and secondary_yaxis?
twinx() creates a new axes for a different data series. secondary_yaxis() shows the same data in another unit using conversion functions.
How do I make one legend for both y-axes?
Combine the lines: lines = ax1.get_lines() + ax2.get_lines() and call ax1.legend(lines, [l.get_label() for l in lines]), or use fig.legend().
How do I add a third y-axis?
Create another ax3 = ax1.twinx() and move its spine: ax3.spines["right"].set_position(("axes", 1.15)).
How do I plot a secondary y-axis with pandas?
df.plot(secondary_y="column") puts that column on a right-hand axis; label it through ax.right_ax.
How do I set limits on the secondary y-axis?
Call ax2.set_ylim(bottom, top) on the twin axes; each y-axis has its own limits.
Bijay Kumar is a 13-time Microsoft MVP with more than 18 years in software development, and the founder of Python Guides and TSinfo Technologies. He started out building .NET and SharePoint solutions at HP, TCS and KPIT before moving into Python, machine learning and AI, and he also builds web apps with TypeScript and React. He writes the tutorials here himself, and every example is run before publishing so you see the real output. More about Bijay · Microsoft MVP profile · LinkedIn