matplotlib 子图的行标题

Posted

技术标签:

【中文标题】matplotlib 子图的行标题【英文标题】:Row titles for matplotlib subplot 【发布时间】:2015-02-10 03:53:35 【问题描述】:

在matplotlib中,除了为整个图设置的标题和为每个单独的图设置的标题之外,是否可以为每一行子图设置一个单独的标题?这将对应于下图中的橙色文本。

如果没有,您将如何解决这个问题?在左侧创建一个单独的空子图列并用橙色文本填充它们?

我知道可以使用text()annotate() 手动定位每个标题,但这通常需要大量调整,而且我有很多子图。有没有更流畅的解决方案?

【问题讨论】:

【参考方案1】:

一个想法是创建三个“大子图”,给每个子图一个标题,并使它们不可见。最重要的是,您可以创建较小的子图矩阵。

这个解决方案完全基于this post,只是更加注重实际去除背景子图。

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()

【讨论】:

是的,我看到了这个答案,但觉得必须一直添加隐藏的大子图会有点烦人。也不确定它是否会影响渲染或文件大小。但是看到你的回答,我想这并不太复杂。我建议进行一些编辑以包含情节标题,并在情节之间为两级标题留出适当的垂直空间。 这很好,但破坏了 sharex 和 sharey 选项。 要关闭所有轴线和刻度,只需执行big_ax.axis('off') 不幸的是大多数 matplotlib 格式化方法看起来像 hack... 伙计们,观看big_ax.set_title() 中的\n,我花了一个小时试图理解为什么我的所有标题都在一行【参考方案2】:

从 matplotlib 3.4.0 开始,行标题现在可以实现为 subfigure suptitles

新的 Figure.subfiguresFigure.add_subfigure 允许在人物中创建虚拟人物 [with] 本地化人物艺术家(例如,颜色条和字幕),这些人物仅适用于每个子人物。

matplotlib 库包含 how to plot subfigures 上的演示。


复制OP的参考图:

使用Figure.subfigures(更直接)

创建fig.subfigures (3x1),其中每个subfig 都有自己的subfig.subplots (1x3) 和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'Subplot row 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')

或使用Figure.add_subfigure(在现有的pyplot.subplots上)

如果您已经有一个plt.subplots 图形(3x1),那么将add_subfigures 转换为底层gridspec。同样,每个subfig 都会有自己的subfig.subplots (1x3) 和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'Subplot row 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')

任一示例的输出(经过一些样式/格式设置):

【讨论】:

嗨,我收到以下错误 fig = plt.figure(constrained_layout=True) fig.suptitle('Figure title') subfigs = fig.subfigures(nrows=3, ncols=1 ) AttributeError: 'Figure' 对象没有属性 'subfigures'。为什么? @Nachengue 你的matplotlib.__version__ 是什么?子图至少需要 3.4.0【参考方案3】:

另一个简单的作弊方法是把中间一栏的标题写成subplot row XX\n\nPlot title No.YY

【讨论】:

它不适用于偶数列【参考方案4】:

最好先绘制你的真实子图,然后在它们上面绘制空子图,这样你会有更精确的标题对齐。而要做到这一点,我们需要plt.GridSpec() (link)。

最好在栏目字幕中看到:

# 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()

# 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')

# 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')

更新

为一组子图创建子网格只是为了给它们命名更合乎逻辑和易于理解。 subgrig 为修改提供了浪费的空间:

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 i1')
    # 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 i2')
    # 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()

原来的问题:

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 i1')

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

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

【讨论】:

以上是关于matplotlib 子图的行标题的主要内容,如果未能解决你的问题,请参考以下文章

更改 matplotlib 子图的大小

删除 Matplotlib,Python 中某些子图的 y 轴刻度标签

在 matplotlib 中调整单个子图的大小

matplotlib饼图用主图的实际标题替换最后一个饼图子图标题[重复]

python+matplotlib绘制具有多个子图的图表

python+matplotlib绘制具有多个子图的图表