When I build dashboards for financial analysis, I often need to compare two different metrics that don’t share the same scale.
For instance, you might want to visualize the S&P 500 index price alongside the daily trading volume.
If you plot them on the same Y-axis, the volume (in millions) will completely flatten the index price (in thousands).
In this tutorial, I will show you exactly how I handle this using a secondary Y-axis within Matplotlib subplots.
The Need for a Dual Y-Axis
I’ve found that using twinx() is the most efficient way to overlay two different scales on a single plot.
This allows both datasets to share the same X-axis (like time) while maintaining their own independent Y-ranges.
Below, I’ll walk you through the primary methods I use to achieve this, from basic dual-axis plots to complex subplot grids.
Method 1: Use the twinx() Function on a Single Subplot
This is my go-to method for a quick comparison. We create a standard axis and then “twin” it to create a second one.
In this example, let’s look at the average temperature in New York City versus the monthly precipitation.
import matplotlib.pyplot as plt
import numpy as np
# Data: NYC Monthly Weather (Approximate)
months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
temp_f = [32, 35, 43, 53, 63, 73, 79, 78, 70, 59, 48, 37]
precip_in = [3.6, 3.2, 4.4, 4.5, 4.5, 4.8, 4.6, 4.4, 4.3, 4.4, 4.0, 4.4]
# Create the figure and the first axis
fig, ax1 = plt.subplots(figsize=(10, 6))
# Plotting the Temperature on the left Y-axis
color_temp = 'tab:red'
ax1.set_xlabel('Month')
ax1.set_ylabel('Average Temp (°F)', color=color_temp, fontsize=12)
ax1.plot(months, temp_f, color=color_temp, marker='o', linewidth=2, label='Temp')
ax1.tick_params(axis='y', labelcolor=color_temp)
ax1.grid(True, alpha=0.3)
# Instantiate a second axes that shares the same x-axis
ax2 = ax1.twinx()
# Plotting Precipitation on the right Y-axis
color_precip = 'tab:blue'
ax2.set_ylabel('Precipitation (inches)', color=color_precip, fontsize=12)
ax2.bar(months, precip_in, color=color_precip, alpha=0.3, label='Precip')
ax2.tick_params(axis='y', labelcolor=color_precip)
# Adding a title and layout adjustments
plt.title('New York City: Temperature vs. Precipitation Analysis', fontsize=14)
fig.tight_layout()
plt.show()I executed the above example code and added the screenshot below.

I use ax1.twinx() to create ax2. This ensures that even though the temperature is in the 70s and the rain is around 4 inches, both are clearly visible.
I always recommend coloring the Y-axis labels to match the plot lines; it makes the chart much more intuitive for the reader.
Method 2: Secondary Y-Axis within a Multi-Subplot Grid
Sometimes one plot isn’t enough. I often have to create a grid of subplots where only one specific pane requires a secondary axis.
Let’s imagine we are analyzing California’s Green Energy production. We want one plot for Solar/Wind and another comparing Total Capacity to Cost.
import matplotlib.pyplot as plt
# Data: California Energy Trends
years = [2018, 2019, 2020, 2021, 2022]
solar_mwh = [27000, 28500, 31000, 35000, 39000]
wind_mwh = [14000, 13800, 13500, 15000, 16500]
avg_cost_index = [100, 95, 88, 82, 75] # Dropping cost of tech
# Create a figure with two subplots (1 row, 2 columns)
fig, (plt1, plt2) = plt.subplots(1, 2, figsize=(15, 6))
# Subplot 1: Stacked Solar and Wind
plt1.stackplot(years, solar_mwh, wind_mwh, labels=['Solar', 'Wind'], colors=['orange', 'skyblue'])
plt1.set_title('Renewable Energy Generation (CA)')
plt1.set_ylabel('MWh')
plt1.legend(loc='upper left')
# Subplot 2: Comparing Solar to Cost Index (Using twinx here)
plt2.plot(years, solar_mwh, color='orange', marker='s', label='Solar Production')
plt2.set_ylabel('Solar MWh', color='orange')
plt2.tick_params(axis='y', labelcolor='orange')
# Create the twin axis for the second subplot only
plt2_twin = plt2.twinx()
plt2_twin.plot(years, avg_cost_index, color='green', linestyle='--', marker='d', label='Cost Index')
plt2_twin.set_ylabel('Cost Index (Normalized)', color='green')
plt2_twin.tick_params(axis='y', labelcolor='green')
plt2.set_title('Solar Output vs. Infrastructure Cost')
fig.tight_layout(pad=3.0)
plt.show()I executed the above example code and added the screenshot below.

One thing I struggled with early on was the legend. Because plt2 and plt2_twin are technically different axes, calling .legend() on one won’t show the lines from the other.
To fix this, I manually combine the “handles” and “labels” from both axes into a single legend box.
Method 3: Use the secondary_yaxis Function (Modern Matplotlib)
In newer versions of Matplotlib, there is a secondary_yaxis function. I use this specifically when the second axis is a direct mathematical conversion of the first.
For example, if your primary Y-axis is in Meters, but your US-based audience needs to see Feet.
import matplotlib.pyplot as plt
import numpy as np
# Sample Data: Elevation of a hiking trail in the Rocky Mountains
miles = np.linspace(0, 10, 100)
elevation_meters = 2000 + 500 * np.sin(miles / 2) + 10 * miles
fig, ax = plt.subplots(figsize=(10, 5))
ax.plot(miles, elevation_meters, color='brown')
ax.set_xlabel('Distance (Miles)')
ax.set_ylabel('Elevation (Meters)')
ax.set_title('Trail Elevation: Rocky Mountain National Park')
# Define the conversion functions
def m_to_ft(x):
return x * 3.28084
def ft_to_m(x):
return x / 3.28084
# Add the secondary Y-axis on the right
secax = ax.secondary_yaxis('right', functions=(m_to_ft, ft_to_m))
secax.set_ylabel('Elevation (Feet)')
plt.show()I executed the above example code and added the screenshot below.

I use this method whenever the two scales are dependent on each other. It’s much cleaner than manually calculating tick marks for a twinx() axis.
If the data is unrelated (like temperature vs. price), stick to twinx().
Common Issues to Avoid
I’ve spent many hours debugging overlapping labels. Always use plt.tight_layout() or fig.subplots_adjust().
When you add a secondary axis, the right-side labels can often get cut off the edge of the saved image.
Another tip: disable the grid on the secondary axis. If both axes have grids, the chart becomes a messy web of lines that are impossible to read.
I usually keep the grid on for the primary axis (ax1) and set ax2.grid(False).
In this tutorial, we looked at several ways to implement a secondary Y-axis in your Matplotlib subplots.
Whether you are overlaying different units or comparing completely different datasets like energy and cost, these methods provide the flexibility you need.
I hope you found this guide helpful. If you’re working on complex data visualizations, try incorporating these dual-axis techniques to make your charts more insightful.
You may read:
- Set Axis Limits for All Subplots in Matplotlib
- Set Axis Range in Matplotlib imshow
- Set the Secondary Axis Range in Matplotlib
- How to Set Axis Lower Limit in Matplotlib

Bijay Kumar is an experienced Python and AI professional who enjoys helping developers learn modern technologies through practical tutorials and examples. His expertise includes Python development, Machine Learning, Artificial Intelligence, automation, and data analysis using libraries like Pandas, NumPy, TensorFlow, Matplotlib, SciPy, and Scikit-Learn. At PythonGuides.com, he shares in-depth guides designed for both beginners and experienced developers. More about us.