pyplot common axes labels for subplots

751,201

Solution 1

You can create a big subplot that covers the two subplots and then set the common labels.

import random
import matplotlib.pyplot as plt

x = range(1, 101)
y1 = [random.randint(1, 100) for _ in range(len(x))]
y2 = [random.randint(1, 100) for _ in range(len(x))]

fig = plt.figure()
ax = fig.add_subplot(111)    # The big subplot
ax1 = fig.add_subplot(211)
ax2 = fig.add_subplot(212)

# Turn off axis lines and ticks of the big subplot
ax.spines['top'].set_color('none')
ax.spines['bottom'].set_color('none')
ax.spines['left'].set_color('none')
ax.spines['right'].set_color('none')
ax.tick_params(labelcolor='w', top=False, bottom=False, left=False, right=False)

ax1.loglog(x, y1)
ax2.loglog(x, y2)

# Set common labels
ax.set_xlabel('common xlabel')
ax.set_ylabel('common ylabel')

ax1.set_title('ax1 title')
ax2.set_title('ax2 title')

plt.savefig('common_labels.png', dpi=300)

common_labels.png

Another way is using fig.text() to set the locations of the common labels directly.

import random
import matplotlib.pyplot as plt

x = range(1, 101)
y1 = [random.randint(1, 100) for _ in range(len(x))]
y2 = [random.randint(1, 100) for _ in range(len(x))]

fig = plt.figure()
ax1 = fig.add_subplot(211)
ax2 = fig.add_subplot(212)

ax1.loglog(x, y1)
ax2.loglog(x, y2)

# Set common labels
fig.text(0.5, 0.04, 'common xlabel', ha='center', va='center')
fig.text(0.06, 0.5, 'common ylabel', ha='center', va='center', rotation='vertical')

ax1.set_title('ax1 title')
ax2.set_title('ax2 title')

plt.savefig('common_labels_text.png', dpi=300)

common_labels_text.png

Solution 2

One simple way using subplots:

import matplotlib.pyplot as plt

fig, axes = plt.subplots(3, 4, sharex=True, sharey=True)
# add a big axes, hide frame
fig.add_subplot(111, frameon=False)
# hide tick and tick label of the big axes
plt.tick_params(labelcolor='none', top=False, bottom=False, left=False, right=False)
plt.grid(False)
plt.xlabel("common X")
plt.ylabel("common Y")

Solution 3

plt.setp() will do the job:

# plot something
fig, axs = plt.subplots(3,3, figsize=(15, 8), sharex=True, sharey=True)
for i, ax in enumerate(axs.flat):
    ax.scatter(*np.random.normal(size=(2,200)))
    ax.set_title(f'Title {i}')

# set labels
plt.setp(axs[-1, :], xlabel='x axis label')
plt.setp(axs[:, 0], ylabel='y axis label')

enter image description here

Solution 4

New in matplotlib 3.4.0

There are now built-in methods to set common axis labels:


To reproduce OP's loglog plots (common labels but separate titles):

x = np.arange(0.01, 10.01, 0.01)
y = 2 ** x

fig, (ax1, ax2) = plt.subplots(2, 1, constrained_layout=True)
ax1.loglog(y, x)
ax2.loglog(x, y)

# separate subplot titles
ax1.set_title('ax1.title')
ax2.set_title('ax2.title')

# common axis labels
fig.supxlabel('fig.supxlabel')
fig.supylabel('fig.supylabel')

suplabel demo

Solution 5

Wen-wei Liao's answer is good if you are not trying to export vector graphics or that you have set up your matplotlib backends to ignore colorless axes; otherwise the hidden axes would show up in the exported graphic.

My answer suplabel here is similar to the fig.suptitle which uses the fig.text function. Therefore there is no axes artist being created and made colorless. However, if you try to call it multiple times you will get text added on top of each other (as fig.suptitle does too). Wen-wei Liao's answer doesn't, because fig.add_subplot(111) will return the same Axes object if it is already created.

My function can also be called after the plots have been created.

def suplabel(axis,label,label_prop=None,
             labelpad=5,
             ha='center',va='center'):
    ''' Add super ylabel or xlabel to the figure
    Similar to matplotlib.suptitle
    axis       - string: "x" or "y"
    label      - string
    label_prop - keyword dictionary for Text
    labelpad   - padding from the axis (default: 5)
    ha         - horizontal alignment (default: "center")
    va         - vertical alignment (default: "center")
    '''
    fig = pylab.gcf()
    xmin = []
    ymin = []
    for ax in fig.axes:
        xmin.append(ax.get_position().xmin)
        ymin.append(ax.get_position().ymin)
    xmin,ymin = min(xmin),min(ymin)
    dpi = fig.dpi
    if axis.lower() == "y":
        rotation=90.
        x = xmin-float(labelpad)/dpi
        y = 0.5
    elif axis.lower() == 'x':
        rotation = 0.
        x = 0.5
        y = ymin - float(labelpad)/dpi
    else:
        raise Exception("Unexpected axis: x or y")
    if label_prop is None: 
        label_prop = dict()
    pylab.text(x,y,label,rotation=rotation,
               transform=fig.transFigure,
               ha=ha,va=va,
               **label_prop)
Share:
751,201

Related videos on Youtube

farqwag25
Author by

farqwag25

Updated on January 16, 2022

Comments

  • farqwag25
    farqwag25 over 2 years

    I have the following plot:

    import matplotlib.pyplot as plt
    
    fig2 = plt.figure()
    ax3 = fig2.add_subplot(2,1,1)
    ax4 = fig2.add_subplot(2,1,2)
    ax4.loglog(x1, y1)
    ax3.loglog(x2, y2)
    ax3.set_ylabel('hello')
    

    I want to be able to create axes labels and titles not just for each of the two subplots, but also common labels that span both subplots. For example, since both plots have identical axes, I only need one set of x and y- axes labels. I do want different titles for each subplot though.

    I tried a few things but none of them worked right

  • PhML
    PhML about 11 years
    The suptitle function use the fig.text() version. So this might be the "official" way to do it ?
  • 1''
    1'' over 9 years
    It's worth emphasizing that ax has to be created before ax1 and ax2, otherwise the big plot will cover up the small plots.
  • Næreen
    Næreen over 6 years
    ax.grid(False) or plt.grid(False) is also needed if the global plotting parameters include a (visible) grid.
  • Næreen
    Næreen over 6 years
    ax.grid(False) or plt.grid(False) is also needed if the global plotting parameters include a (visible) grid.
  • M. Toya
    M. Toya over 6 years
    It seems that the first approach does not work anymore with recent versions of matplotplib (I use 2.0.2): labels added to the enclosing axe are not visible.
  • asmeurer
    asmeurer over 6 years
    labelcolor='none' doesn't disable the labels for pgf output at least. You can make it work with labelcolor=(1, 1, 1) (if the background is white). But I guess they are still there, like if you select them in the output you can copy them.
  • ZYX
    ZYX about 6 years
    Adding which="both" argument to the plt.tick_params() line helped me remove the major tick marks still remaining
  • Fardin Abdi
    Fardin Abdi almost 6 years
    How to add y_labels to each individual subplot?
  • Arthur Dent
    Arthur Dent almost 6 years
    This is the best answer imo. It's easy to implement and the labels don't overlap because of the labelpad option.
  • dankal444
    dankal444 over 5 years
    In case you need both, common label and subplots labels, you may add some spacing between them so that they not overlap. plt.ylabel("Common Y", labelpad=20)
  • Kareem Jeiroudi
    Kareem Jeiroudi about 5 years
    Thanks! fig.text(0.06, 0.5, 'common ylabel', ha='center', va='center', rotation='vertical') worked for me.
  • Evidlo
    Evidlo about 5 years
    I'm doing this for a (5, 1) subplot and my ylabel is way off at the left edge of the window instead of near the subplots.
  • Kareem Jeiroudi
    Kareem Jeiroudi about 5 years
    You got an upvote. but please always explain what the code is doing, attach an image or show an example, because it definitely took a bit of time to get it.
  • pcko1
    pcko1 almost 5 years
    sharey=True, sharex=True are not really needed, otherwise a neat answer!
  • Ted
    Ted almost 5 years
    Change 'off' to False with newer versions of Matplotlib (I have 2.2.2)
  • usernumber
    usernumber over 4 years
    And then how do you add the plots? for ax in axes: ax.plot(x, y) doesn't seem to do any good.
  • yngve
    yngve over 4 years
    This is a good answer, note also the optional labelpad to in particular the ylabel command. This is needed for example when the subplots are logscale to move the label a bit away from the axis. Similarly you can give a negative number if you feel it is placed too far away from the axes
  • pfabri
    pfabri almost 4 years
    Is there a way to also set the font size/weight with this method?
  • Manuel F
    Manuel F over 3 years
    @pfabri ... plt.setp(axs[-1, :], xlabel='x axis label') only modifies the label text (apparently). if you want to customize its text parameters do, for instance, ax.set_xlabel(None, size=12, weight='demibold', color='xkcd:lime green', labelpad=0.33) inside the for loop. check matplotlib.text.Text for more **kwargs.
  • Surya Narayanan
    Surya Narayanan almost 3 years
    I got some overlapping x ticks from the (111) subplot and my other subplots. I used plt.tick_params(labelcolor="none", bottom=False, left=False) after fig.add_subplot(111, frame_on=False) from kite.com/python/answers/…
  • vanPelt2
    vanPelt2 over 2 years
    Very excited about this new functionality, thank you for revisiting this answer and pointing it out!
  • Mead
    Mead over 2 years
    The first solution worked for me, but I needed labelcolor='none' rather than labelcolor='w' in ax.tick_params(...) otherwise I saw residuals from the big plot.