57

So far I have the following code:

colors = ('k','r','b')
ax = []
for i in range(3):
 ax.append(plt.axes())
 plt.plot(datamatrix[:,0],datamatrix[:,i],colors[i]+'o')
 ax[i].set(autoscale_on=True)

With the autoscale_on=True option for each axis, I thought each plot should have its own y-axis limits, but it appears they all share the same value (even if they share different axes). How do I set them to scale to show the range of each datamatrix[:,i] (just an explicit call to .set_ylim()?) And also, how can I create an offset y-axis for the third variable (datamatrix[:,2]) that might be required above? Thanks all.

Trenton McKinney
63.1k41 gold badges169 silver badges210 bronze badges
asked Oct 11, 2011 at 23:27

5 Answers 5

130

It sounds like what you're wanting is subplots... What you're doing now doesn't make much sense (Or I'm very confused by your code snippet, at any rate...).

Try something more like this:

import matplotlib.pyplot as plt
import numpy as np
fig, axes = plt.subplots(nrows=3)
colors = ('k', 'r', 'b')
for ax, color in zip(axes, colors):
 data = np.random.random(1) * np.random.random(10)
 ax.plot(data, marker='o', linestyle='none', color=color)
plt.show()

enter image description here

Edit:

If you don't want subplots, your code snippet makes a lot more sense.

You're trying to add three axes right on top of each other. Matplotlib is recognizing that there's already a subplot in that exactly size and location on the figure, and so it's returning the same axes object each time. In other words, if you look at your list ax, you'll see that they're all the same object.

If you really want to do that, you'll need to reset fig._seen to an empty dict each time you add an axes. You probably don't really want to do that, however.

Instead of putting three independent plots over each other, have a look at using twinx instead.

E.g.

import matplotlib.pyplot as plt
import numpy as np
# To make things reproducible...
np.random.seed(1977)
fig, ax = plt.subplots()
# Twin the x-axis twice to make independent y-axes.
axes = [ax, ax.twinx(), ax.twinx()]
# Make some space on the right side for the extra y-axis.
fig.subplots_adjust(right=0.75)
# Move the last y-axis spine over to the right by 20% of the width of the axes
axes[-1].spines['right'].set_position(('axes', 1.2))
# To make the border of the right-most axis visible, we need to turn the frame
# on. This hides the other plots, however, so we need to turn its fill off.
axes[-1].set_frame_on(True)
axes[-1].patch.set_visible(False)
# And finally we get to plot things...
colors = ('Green', 'Red', 'Blue')
for ax, color in zip(axes, colors):
 data = np.random.random(1) * np.random.random(10)
 ax.plot(data, marker='o', linestyle='none', color=color)
 ax.set_ylabel('%s Thing' % color, color=color)
 ax.tick_params(axis='y', colors=color)
axes[0].set_xlabel('X-axis')
plt.show()

enter image description here

answered Oct 12, 2011 at 2:26
12
  • 1
    Thanks -- actually I do want a single panel with all the points, but each having a different y scale... Commented Oct 12, 2011 at 5:22
  • 1
    Hopefully the edits help. I'm assuming you want the y-axes labeled with the actual ranges. If you don't (and basically just want the y-values to be meaningless), then you can do this much more simply. Commented Oct 12, 2011 at 17:01
  • Ah, spines is the attribute I wanted I think. But manually adjusting the spine seems like the way to go, even with a "floating" axis which is not necessarily connected with the data, so long as I ensure that the scales are manipulated correctly! Thanks. Commented Oct 13, 2011 at 18:52
  • 3
    This is actually quite beautiful. I have copied this and put it to use already. One thing though, tick_parms does not exist? I had to set axes[-1].yaxis.set_ticks_position('right'), but otherwise worked beautifully! Commented Oct 15, 2011 at 1:10
  • 1
    @JoeKington, I solved, it was enough to add fig.tight_layout() Commented Jun 12, 2014 at 3:15
10

Bootstrapping something fast to chart multiple y-axes sharing an x-axis using @joe-kington's answer: enter image description here

# d = Pandas Dataframe, 
# ys = [ [cols in the same y], [cols in the same y], [cols in the same y], .. ] 
def chart(d,ys):
 from itertools import cycle
 fig, ax = plt.subplots()
 axes = [ax]
 for y in ys[1:]:
 # Twin the x-axis twice to make independent y-axes.
 axes.append(ax.twinx())
 extra_ys = len(axes[2:])
 # Make some space on the right side for the extra y-axes.
 if extra_ys>0:
 temp = 0.85
 if extra_ys<=2:
 temp = 0.75
 elif extra_ys<=4:
 temp = 0.6
 if extra_ys>5:
 print 'you are being ridiculous'
 fig.subplots_adjust(right=temp)
 right_additive = (0.98-temp)/float(extra_ys)
 # Move the last y-axis spine over to the right by x% of the width of the axes
 i = 1.
 for ax in axes[2:]:
 ax.spines['right'].set_position(('axes', 1.+right_additive*i))
 ax.set_frame_on(True)
 ax.patch.set_visible(False)
 ax.yaxis.set_major_formatter(matplotlib.ticker.OldScalarFormatter())
 i +=1.
 # To make the border of the right-most axis visible, we need to turn the frame
 # on. This hides the other plots, however, so we need to turn its fill off.
 cols = []
 lines = []
 line_styles = cycle(['-','-','-', '--', '-.', ':', '.', ',', 'o', 'v', '^', '<', '>',
 '1', '2', '3', '4', 's', 'p', '*', 'h', 'H', '+', 'x', 'D', 'd', '|', '_'])
 colors = cycle(matplotlib.rcParams['axes.color_cycle'])
 for ax,y in zip(axes,ys):
 ls=line_styles.next()
 if len(y)==1:
 col = y[0]
 cols.append(col)
 color = colors.next()
 lines.append(ax.plot(d[col],linestyle =ls,label = col,color=color))
 ax.set_ylabel(col,color=color)
 #ax.tick_params(axis='y', colors=color)
 ax.spines['right'].set_color(color)
 else:
 for col in y:
 color = colors.next()
 lines.append(ax.plot(d[col],linestyle =ls,label = col,color=color))
 cols.append(col)
 ax.set_ylabel(', '.join(y))
 #ax.tick_params(axis='y')
 axes[0].set_xlabel(d.index.name)
 lns = lines[0]
 for l in lines[1:]:
 lns +=l
 labs = [l.get_label() for l in lns]
 axes[0].legend(lns, labs, loc=0)
 plt.show()
answered Jan 15, 2015 at 14:42
6

Thanks to Joe Kington's answer I could come up with a solution for my requirement that all additional y-axis are on the left hand side of the graph.

I still would like to know how to do it correct, because it's just a work around:

import matplotlib.pyplot as plt
import numpy as np
# To make things reproducible...
np.random.seed(1977)
fig, ax = plt.subplots()
# Twin the x-axis twice to make independent y-axes.
axes = [ax, ax.twinx(), ax.twinx()]
# Make some space on the right side for the extra y-axis.
fig.subplots_adjust(right=0.75)
# Move the last y-axis spine over to the right by 20% of the width of the axes
axes[1].spines['right'].set_position(('axes', -0.25))
axes[2].spines['right'].set_position(('axes', -0.5))
# To make the border of the right-most axis visible, we need to turn the frame
# on. This hides the other plots, however, so we need to turn its fill off.
axes[-1].set_frame_on(True)
axes[-1].patch.set_visible(False)
# And finally we get to plot things...
colors = ('Green', 'Red', 'Blue')
intAxNo = 0
for ax, color in zip(axes, colors):
 intAxNo += 1
 data = np.random.random(1) * np.random.random(10)
 ax.plot(data, marker='o', linestyle='none', color=color)
 if (intAxNo > 1):
 if (intAxNo == 2):
 ax.set_ylabel('%s Thing' % color, color=color, labelpad = -40 )
 elif (intAxNo == 3):
 ax.set_ylabel('%s Thing' % color, color=color, labelpad = -45 )
 ax.get_yaxis().set_tick_params(direction='out')
 else:
 ax.set_ylabel('%s Thing' % color, color=color, labelpad = +0 )
 ax.tick_params(axis='y', colors=color)
axes[0].set_xlabel('X-axis')
plt.show()

enter image description here

answered Aug 8, 2016 at 15:57
5

twinx. Short example:

fig1 = matplotlib.figure.Figure() # Make a figure
ax1 = fig1.add_subplot() # Add the primary axis
ax1.plot([100, 300, 200]) # Plot something
ax2 = ax1.twinx() # Add the secondary axis
ax2.plot([5000, 2000, 6000]) # Plot something with a different scale
display( fig1 ) # Display it (Jupyter only)

enter image description here

answered Mar 25, 2021 at 11:09
1

I've used this code and it successfully generated two Y-axes (primary & secondary) with desired scales reading data from Excel file:

X = df[['x-axis variable']]
Y1=df[['1st Y-Variable']]
Y2=df[['2nd Y-Variable']]
Y3=df[['3rd Y-Variable']]
fig, ax1 = plt.subplots(figsize=(10,6))
ax2 = ax1.twinx()
ax1.plot(X, Y1, 'g', label='Curve.1 name') #plotting on primary Y-axis
ax1.plot(X, Y2, 'm', label='Curve.2 name') #plotting on primary Y-axis
ax2.plot(X, wob, 'b', label='Curve.3 name') #plotting on **second** Y-axis
ax1.set_ylim(0, 350) #Define limit/scale for primary Y-axis
ax2.set_ylim(1000, 1300) #Define limit/scale for secondary Y-axis
plt.show()
answered Mar 25, 2021 at 14:44

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.