Twin Axes & Secondary Y

Reviewed & published by Brayan K

Matplotlib is a Python library for creating charts and visualizations — and twin axes let you overlay two series with completely different scales, like temperature and rainfall, on a single shared x-axis.

Part of the free Matplotlib course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

In this lesson you'll create a second y-axis with ax.twinx(), color-code each axis to its line, and combine both series into one legend.

What You'll Learn in This Lesson

1 Why You Need a Second Y-Axis

Imagine plotting temperature (around 20°C) and rainfall (around 200 mm) on the same y-axis. The rainfall values are ten times bigger, so the temperature line would be squashed flat at the bottom. A twin axis gives each series its own scale.

import matplotlib.pyplot as plt

months = ['Jan', 'Feb', 'Mar', 'Apr', 'May']
temp = [5, 7, 11, 16, 20]        # degrees Celsius
rain = [80, 70, 90, 60, 50]      # millimetres

fig, ax = plt.subplots()

# Left axis: temperature
ax.plot(months, temp, color='tab:red', marker='o')

# Create a twin sharing the same x-axis
ax2 = ax.twinx()

# Right axis: rainfall
ax2.plot(months, rain, color='tab:blue', marker='s')

plt.title("Temperature and rainfall")
plt.show()
The chart this code draws: Plot two scales on one chart with ax.twinx()
What this code draws, rendered with Matplotlib.

What you'll see: two lines over the same months — a red temperature line rising left-to-right, and a blue rainfall line, each read against its own y-axis (temperature on the left, rainfall on the right).

2 Color-Coding the Axes

With two y-axes, the reader needs to know which scale belongs to which line. The convention is to color each set_ylabel to match its line — and color the tick labels too with ax.tick_params(axis='y', labelcolor=...).

import matplotlib.pyplot as plt

months = ['Jan', 'Feb', 'Mar', 'Apr', 'May']
temp = [5, 7, 11, 16, 20]
rain = [80, 70, 90, 60, 50]

fig, ax = plt.subplots()

# Temperature on the left, in red
ax.plot(months, temp, color='tab:red', marker='o')
ax.set_ylabel("Temperature (°C)", color='tab:red')
ax.tick_params(axis='y', labelcolor='tab:red')

# Rainfall on the right twin, in blue
ax2 = ax.twinx()
ax2.plot(months, rain, color='tab:blue', marker='s')
ax2.set_ylabel("Rainfall (mm)", color='tab:blue')
ax2.tick_params(axis='y', labelcolor='tab:blue')

ax.set_xlabel("Month")
plt.title("Color-coded twin axes")
plt.show()
The chart this code draws: Color each y-label and its ticks to match its line
What this code draws, rendered with Matplotlib.

What you'll see: the same two-line chart, but now the left axis label and ticks are red (matching the temperature line) and the right axis label and ticks are blue (matching rainfall) — so it's obvious which scale each line uses.

3 Combining Both Lines in One Legend

Because each line lives on a different axes, a normal ax.legend() would only show one of them. The fix: keep the line objects that plot() returns, then build a single legend from both.

import matplotlib.pyplot as plt

months = ['Jan', 'Feb', 'Mar', 'Apr', 'May']
temp = [5, 7, 11, 16, 20]
rain = [80, 70, 90, 60, 50]

fig, ax = plt.subplots()
ax2 = ax.twinx()

# Keep the line objects (note the trailing comma to unpack)
line1, = ax.plot(months, temp, color='tab:red', marker='o', label='Temp (°C)')
line2, = ax2.plot(months, rain, color='tab:blue', marker='s', label='Rain (mm)')

ax.set_ylabel("Temperature (°C)")
ax2.set_ylabel("Rainfall (mm)")

# One combined legend built from both lines
ax.legend([line1, line2], ['Temp (°C)', 'Rain (mm)'], loc='upper left')

plt.title("Twin axes with a shared legend")
plt.show()
The chart this code draws: Gather both lines into a single legend box
What this code draws, rendered with Matplotlib.

What you'll see: the dual-scale chart with a single legend box in the upper-left listing both "Temp (°C)" and "Rain (mm)" together — even though the two lines belong to different axes.

🎯 Your Turn: Fill in the Blanks

Replace each ___ to add a second y-axis and label it.

import matplotlib.pyplot as plt

months = ['Jan', 'Feb', 'Mar', 'Apr']
sales = [100, 130, 120, 160]
returns = [5, 8, 6, 9]

fig, ax = plt.subplots()
ax.plot(months, sales, color='tab:green')
ax.set_ylabel("Sales", color='tab:green')

# Create the second y-axis sharing the x-axis
ax2 = ax.___()

ax2.plot(months, returns, color='tab:orange')
ax2.set_ylabel("Returns", color='tab:orange')

plt.___()

📋 Common Errors & Quick Reference

❌ One line is squashed flat at the bottom

The two series share a single y-axis. Add ax2 = ax.twinx() and plot the second series on ax2.

❌ My legend only shows one line

Each axes has its own legend. Keep both line objects and call ax.legend([line1, line2], [...]).

CallWhat It Does
ax2 = ax.twinx()Add a second y-axis (shared x)
ax.set_ylabel("L", color=...)Color a y-axis label
ax.tick_params(axis='y', labelcolor=...)Color the y-tick labels
ax.legend([l1, l2], [...])One legend for both lines

🏆 Mini Challenge: Price vs Volume

Plot a stock's price and trading volume on twin axes with matching colors and a shared legend.

import matplotlib.pyplot as plt

days = [1, 2, 3, 4, 5]
price = [150, 153, 149, 158, 161]      # dollars
volume = [1200, 1800, 900, 2200, 1600] # shares

fig, ax = plt.subplots()
ax2 = ax.twinx()

# 1. Price on the left axis (purple)
line1, = ax.plot(days, price, color='tab:purple', marker='o', label='Price ($)')
ax.set_ylabel("Price ($)", color='tab:purple')
ax.tick_params(axis='y', labelcolor='tab:purple')

# 2. Volume on the right axis (gray)
line2, = ax2.plot(days, volume, color='tab:gray', marker='s', label='Volume')
ax2.set_ylabel("Volume", color='tab:gray')
ax2.tick_params(axis='y', labelcolor='tab:gray')

# 3. One combined legend
ax.legend([line1, line2], ['Price ($)', 'Volume'], loc='upper left')

ax.set_xlabel("Day")
plt.title("Price vs trading volume")
plt.show()
The chart this code draws: Build a dual-scale price-and-volume chart
What this code draws, rendered with Matplotlib.
import matplotlib.pyplot as plt

months = [1, 2, 3, 4]
sales = [100, 140, 120, 180]
temp = [4.0, 6.5, 9.0, 13.5]

fig, ax = plt.subplots()
ax.bar(months, sales, color="steelblue")
ax.set_ylabel("sales")

ax2 = ax.twinx()
ax2.plot(months, temp, color="tomato", marker="o")
ax2.set_ylabel("temperature")

print("Axes on the figure:", len(fig.axes))
print("They share the x axis:", ax.get_shared_x_axes().joined(ax, ax2))
print("Left label: ", ax.get_ylabel())
print("Right label:", ax2.get_ylabel())
print("Different y ranges:", ax.get_ylim() != ax2.get_ylim())
print("Bars on the left, a line on the right:", len(ax.patches), len(ax2.lines))

# ✅ Expected output:
# Axes on the figure: 2
# They share the x axis: True
# Left label:  sales
# Right label: temperature
# Different y ranges: True
# Bars on the left, a line on the right: 4 1
The chart this code draws: Two axes sharing one x axis, with separate y labels and separate ranges
What this code draws, rendered with Matplotlib.

❓ Frequently Asked Questions

Lesson complete — two scales, one chart!

You created a secondary y-axis with ax.twinx(), color-matched each axis to its line, and merged two series into a single shared legend.

Practice quiz

What is a twin axis?

  • A second figure window
  • A duplicated chart title
  • A second y-axis that shares the same x-axis
  • Two separate subplots side by side

Answer: A second y-axis that shares the same x-axis. A twin axis is a second y-axis sharing the same x-axis, made with ax.twinx().

Which call creates a second y-axis sharing the x-axis?

  • ax2 = ax.twinx()
  • ax2 = ax.copy()
  • ax2 = plt.second_axis()
  • ax2 = ax.clone()

Answer: ax2 = ax.twinx(). ax2 = ax.twinx() makes a new axes with an independent y-axis on the right.

When is a twin axis most useful?

  • When both series use the same units
  • When you have only one series
  • When you want a 3D chart
  • When two series share x but have very different scales

Answer: When two series share x but have very different scales. It gives each series its own y-scale so one isn't squashed flat.

How do you color a y-axis label to match its line?

  • ax.color_label('red')
  • ax.set_ylabel('Temp', color='tab:red')
  • ax.label_color('red')
  • ax.ylabel('Temp').color('red')

Answer: ax.set_ylabel('Temp', color='tab:red'). set_ylabel('Temp', color='tab:red') colors the label to match the line.

Which call colors the y-tick labels of an axis?

  • ax.set_tickcolor('red')
  • ax.color_ticks('red')
  • ax.tick_params(axis='y', labelcolor='tab:red')
  • ax.yticks(color='red')

Answer: ax.tick_params(axis='y', labelcolor='tab:red'). tick_params(axis='y', labelcolor=...) recolors the y-tick labels.

Why does a plain ax.legend() show only one line on twin axes?

  • Each axes has its own separate lines
  • Legends are disabled on twins
  • twinx() deletes labels
  • Only the last line is kept

Answer: Each axes has its own separate lines. Each line lives on a different axes, so one axes' legend sees only its own lines.

What does the trailing comma do in 'line1, = ax.plot(...)'?

  • Adds a second line
  • Sets the line color
  • Reverses the data
  • Unpacks the single Line2D from the returned list

Answer: Unpacks the single Line2D from the returned list. plot() returns a list; the trailing comma unpacks the single Line2D object.

How do you build one legend covering both lines?

  • Call ax.legend() twice
  • ax.legend([line1, line2], ['A', 'B'])
  • plt.legend('all')
  • ax2.legend(ax)

Answer: ax.legend([line1, line2], ['A', 'B']). Gather the saved line objects and pass them both to ax.legend([...], [...]).

What problem does twinx() solve when units differ greatly?

  • It changes the x-axis labels
  • It adds gridlines
  • One series being squashed flat against a single shared y-axis
  • It rotates the chart

Answer: One series being squashed flat against a single shared y-axis. Without a twin, the smaller-scale series flattens; the twin gives it its own scale.

On which side does ax.twinx() place the new y-axis?

  • The right side of the chart
  • The left side
  • The top
  • The bottom

Answer: The right side of the chart. twinx() adds an independent y-axis on the right while sharing the x-axis.

Continue this course

Frequently asked questions

What is a twin axis?

A twin axis is a second y-axis that shares the same x-axis. You create it with ax2 = ax.twinx(), which lets you plot two series with very different scales — like temperature and rainfall — on one chart.

When should I use ax.twinx()?

Use it when two series share an x-axis but have different units or ranges, so plotting them on a single y-axis would flatten one of them. The twin gives each series its own y-scale.

How do I tell which line belongs to which axis?

Color is the standard trick: set each line and its matching y-label to the same color with ax.set_ylabel('Temp', color='tab:red') and the same color on the line. The reader follows the color.

How do I make one combined legend?

Each axis has its own lines, so gather them together: keep the Line2D objects returned by plot(), then call ax.legend([line1, line2], ['A', 'B']) to put both in a single legend box.