Create Subplots with a Secondary Y-Axis in Matplotlib

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.

Create Subplots with Secondary Y-Axis in Matplotlib

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.

Create Matplotlib Subplots with Secondary Y-Axis

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.

Matplotlib Create Subplots with a Secondary Y-Axis

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:

51 Python Programs

51 PYTHON PROGRAMS PDF FREE

Download a FREE PDF (112 Pages) Containing 51 Useful Python Programs.

pyython developer roadmap

Aspiring to be a Python developer?

Download a FREE PDF on how to become a Python developer.

Let’s be friends

Be the first to know about sales and special discounts.