Row titles for matplotlib subplot

Another easy cheat is to give the title of the middle column as subplot row XX\n\nPlot title No.YY


An idea is to create three "big subplots", to give each of them a title, and make them invisible. On the top of that you can create your matrix of smaller subplots.

This solution is entirely based on this post, except that more attention has been paid to actually removing the background subplot.

enter image description here

import matplotlib.pyplot as plt

fig, big_axes = plt.subplots( figsize=(15.0, 15.0) , nrows=3, ncols=1, sharey=True) 

for row, big_ax in enumerate(big_axes, start=1):
    big_ax.set_title("Subplot row %s \n" % row, fontsize=16)

    # Turn off axis lines and ticks of the big subplot 
    # obs alpha is 0 in RGBA string!
    big_ax.tick_params(labelcolor=(1.,1.,1., 0.0), top='off', bottom='off', left='off', right='off')
    # removes the white frame
    big_ax._frameon = False


for i in range(1,10):
    ax = fig.add_subplot(3,3,i)
    ax.set_title('Plot title ' + str(i))

fig.set_facecolor('w')
plt.tight_layout()
plt.show()

New in matplotlib 3.4.0

Row titles can now be implemented as subfigure suptitles:

The new subfigure feature allows creating virtual figures within figures with localized artists (e.g., colorbars and suptitles) that only pertain to each subfigure.

See how to plot subfigures for further details.


How to reproduce OP's reference figure:

  • Either Figure.subfigures (most straightforward)

    Create 3x1 fig.subfigures where each subfig gets its own 1x3 subfig.subplots and subfig.suptitle:

    fig = plt.figure(constrained_layout=True)
    fig.suptitle('Figure title')
    
    # create 3x1 subfigs
    subfigs = fig.subfigures(nrows=3, ncols=1)
    for row, subfig in enumerate(subfigs):
        subfig.suptitle(f'Subfigure title {row}')
    
        # create 1x3 subplots per subfig
        axs = subfig.subplots(nrows=1, ncols=3)
        for col, ax in enumerate(axs):
            ax.plot()
            ax.set_title(f'Plot title {col}')
    
  • Or Figure.add_subfigure (onto existing subplots)

    If you already have 3x1 plt.subplots, then add_subfigure into the underlying gridspec. Again each subfig will get its own 1x3 subfig.subplots and subfig.suptitle:

    # create 3x1 subplots
    fig, axs = plt.subplots(nrows=3, ncols=1, constrained_layout=True)
    fig.suptitle('Figure title')
    
    # clear subplots
    for ax in axs:
        ax.remove()
    
    # add subfigure per subplot
    gridspec = axs[0].get_subplotspec().get_gridspec()
    subfigs = [fig.add_subfigure(gs) for gs in gridspec]
    
    for row, subfig in enumerate(subfigs):
        subfig.suptitle(f'Subfigure title {row}')
    
        # create 1x3 subplots per subfig
        axs = subfig.subplots(nrows=1, ncols=3)
        for col, ax in enumerate(axs):
            ax.plot()
            ax.set_title(f'Plot title {col}')
    

Output of either example (after some styling):


It is better to firstly plot your real subplots and then plot empty subplots above them, thus you will have a more precise title align. And to do it precisely we need plt.GridSpec() (link).

It is best seen in columns subtitles:

# modified code of @snake_chrmer
fig, big_axes = plt.subplots(figsize=(9, 3) , nrows=1, ncols=3, sharey=True) 

for title, big_ax in zip(['First', 'Second', 'Third'], big_axes):
    big_ax.set_title(f'{title}\n', fontweight='semibold')
    big_ax.set_frame_on(False)
    big_ax.axis('off')


for i in range(1, 7):
    ax = fig.add_subplot(1,6,i)
    ax.set_title('Plot title ' + str(i))

fig.set_facecolor('w')
plt.tight_layout()
plt.show()

enter image description here

# my solition
import matplotlib.pyplot as plt
from matplotlib.gridspec import SubplotSpec

def create_subtitle(fig: plt.Figure, grid: SubplotSpec, title: str):
    "Sign sets of subplots with title"
    row = fig.add_subplot(grid)
    # the '\n' is important
    row.set_title(f'{title}\n', fontweight='semibold')
    # hide subplot
    row.set_frame_on(False)
    row.axis('off')


rows = 1 
cols = 6
fig, axs = plt.subplots(rows, cols, figsize=(9, 3))
for i, ax in enumerate(axs.flatten()):
    ax.set_title(f'Plot title {i}')
grid = plt.GridSpec(rows, cols)
create_subtitle(fig, grid[0, 0:2], 'First')
create_subtitle(fig, grid[0, 2:4], 'Second')
create_subtitle(fig, grid[0, 4:6], 'Third')
fig.tight_layout()
fig.set_facecolor('w')

enter image description here

# original problem
rows = 3
cols = 3
fig, axs = plt.subplots(rows, cols, figsize=(9, 9))
for i, ax in enumerate(axs.flatten()):
    ax.set_title(f'Plot title {i}')
grid = plt.GridSpec(rows, cols)
create_subtitle(fig, grid[0, ::], 'First')
create_subtitle(fig, grid[1, ::], 'Second')
create_subtitle(fig, grid[2, ::], 'Third')
fig.tight_layout()
fig.set_facecolor('w')

enter image description here

UPD

It is more logical and comprehensible to create subgrid for a set of subplots just to title them. The subgrig gives a wast space for modifications:

import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec

rows = 1
cols = 3

fig = plt.figure(figsize=(9, 3))
# grid for pairs of subplots
grid = plt.GridSpec(rows, cols)

for i in range(rows * cols):
    # create fake subplot just to title pair of subplots
    fake = fig.add_subplot(grid[i])
    #  '\n' is important
    fake.set_title(f'Fake #{i}\n', fontweight='semibold', size=14)
    fake.set_axis_off()

    # create subgrid for two subplots without space between them
    # <https://matplotlib.org/2.0.2/users/gridspec.html>
    gs = gridspec.GridSpecFromSubplotSpec(1, 2, subplot_spec=grid[i], wspace=0)

    # real subplot #1
    ax = fig.add_subplot(gs[0])
    ax.set_title(f'Real {i}1')
    # hide ticks and labels
    ax.tick_params(left=False, labelleft=False, labelbottom=False, bottom=False)

    # real subplot #2
    ax = fig.add_subplot(gs[1], sharey=ax)
    ax.set_title(f'Real {i}2')
    # hide ticks and labels
    ax.tick_params(left=False, labelleft=False, labelbottom=False, bottom=False)

fig.patch.set_facecolor('white')
fig.suptitle('SUPERTITLE', fontweight='bold', size=16)
fig.tight_layout()

enter image description here

Original problem:

rows = 3
cols = 1

fig = plt.figure(figsize=(9, 9))
# grid for pairs of subplots
grid = plt.GridSpec(rows, cols)

for i in range(rows * cols):
    # create fake subplot just to title set of subplots
    fake = fig.add_subplot(grid[i])
    # '\n' is important
    fake.set_title(f'Fake #{i}\n', fontweight='semibold', size=14)
    fake.set_axis_off()

    # create subgrid for two subplots without space between them
    # <https://matplotlib.org/2.0.2/users/gridspec.html>
    gs = gridspec.GridSpecFromSubplotSpec(1, 3, subplot_spec=grid[i])

    # real subplot #1
    ax = fig.add_subplot(gs[0])
    ax.set_title(f'Real {i}1')

    # real subplot #2
    ax = fig.add_subplot(gs[1], sharey=ax)
    ax.set_title(f'Real {i}2')
    
    # real subplot #3
    ax = fig.add_subplot(gs[2], sharey=ax)
    ax.set_title(f'Real {i}3')

fig.patch.set_facecolor('white')
fig.suptitle('SUPERTITLE', fontweight='bold', size=16)
fig.tight_layout()

enter image description here