Matplotlib子图的公共xlabel/ylabel

262

我有以下的图表:

fig,ax = plt.subplots(5,2,sharex=True,sharey=True,figsize=fig_size)

现在我想为这个图设置公共的 x 轴标签和 y 轴标签。所谓“公共”,是指整个子图网格下方应该有一个大的 x 轴标签,右侧应该有一个大的 y 轴标签。我在 plt.subplots 的文档中找不到相关内容,我的搜索结果表明我需要首先创建一个大的 plt.subplot(111) - 但是如何使用 plt.subplots 将我的 5*2 子图放入其中呢?


4
根据问题的更新和下面回答中留下的评论,这是一个重复的问题,重复的链接为https://dev59.com/A2w05IYBdhLWcg3w4125。 - Hooked
2
并不是,因为我的问题是关于plt.subplots()的,而你链接的问题使用了add_subplot - 除非我切换到add_subplot,否则我不能使用该方法,而我想避免这种情况。我可以使用在你的链接中作为替代解决方案给出的plt.text解决方案,但它并不是最优雅的解决方案。 - jolindbe
具体来说,据我所知,plt.subplots无法在现有的轴环境中生成一组子图,而总是创建一个新的图形。对吗? - jolindbe
1
你的链接是由用户Hooked在4年前提供的(就在你的评论上方)。正如我之前所说,那个解决方案适用于add_subplot,而不是plt.subplots()。 - jolindbe
对于那些想知道为什么几个评论链接到另一个略有不同的问题的人,原因是其中一个答案实际上与使用plt.subplots的情况相关:https://dev59.com/A2w05IYBdhLWcg3w4125#36542971 它涉及使用add_subplots添加一个较大的子图(将接收标签),创建初始子图之后。不需要“切换”到add_subplot来创建初始子图。 - bli
显示剩余2条评论
8个回答

332

这看起来就是你实际想要的。它对你的特定情况应用了这个答案的相同方法:

import matplotlib.pyplot as plt

fig, ax = plt.subplots(nrows=3, ncols=3, sharex=True, sharey=True, figsize=(6, 6))

fig.text(0.5, 0.04, 'common X', ha='center')
fig.text(0.04, 0.5, 'common Y', va='center', rotation='vertical')

具有公共坐标轴标签的多个图形


5
请注意,x轴标签的x坐标为0.5并不能将标签放置在中心子图的中心位置。您需要稍微调整该值以考虑yticklabels的影响。 - abcd
3
看一下这个答案,它提供了一种不使用 plt.text 的方法。你需要创建子图,但之后添加一个无形的小图,并给它标上 x 和 y 的标签。 - James Owers
1
谢谢,总体上工作正常。在使用tight_layout时有任何解决方法吗? - serv-inc
6
使用tight_layout替换0.04为0似乎可行。 - divenex
19
使用 fig.text 不是一个好主意。这会影响到像 plt.tight_layout() 这样的东西。 - Peaceful

167

鉴于我认为这篇文章足够相关和优雅(不需要指定坐标来放置文本),因此我(稍作调整)复制了另一个相关问题的答案(链接)

import matplotlib.pyplot as plt
fig, axes = plt.subplots(5, 2, sharex=True, sharey=True, figsize=(6,15))
# add a big axis, hide frame
fig.add_subplot(111, frameon=False)
# hide tick and tick label of the big axis
plt.tick_params(labelcolor='none', which='both', top=False, bottom=False, left=False, right=False)
plt.xlabel("common X")
plt.ylabel("common Y")

这会导致以下结果(使用matplotlib版本2.2.0):

5行2列子图,具有共同的x和y轴标签


23
由于简单明了,这个答案应该被接受。非常直截了当。对于 matplotlib v3.x 仍然适用。 - Kyle Swanson
1
我想知道如何在多个图形对象中使用它?fig.xlabel("foo")无法工作。 - Horror Vacui
2
FYI:现在人们在StackOverflow中使用暗色主题,标签几乎无法阅读,因此最好将您的PNG导出为白色背景。 - xyzzyqed
3
这种解决方案唯一的问题是,在使用 constrained_layout=True 时无法正常工作,因为会产生标签重叠的问题。在这种情况下,您需要手动调整子图的边界。 - baccandr
完全删除隐藏轴使用:plt.axis('off') - marou
显示剩余3条评论

156

Matplotlib v3.4新特性 (pip install matplotlib --upgrade)

supxlabelsupylabel

    fig.supxlabel('common_x')
    fig.supylabel('common_y')

查看示例:

import matplotlib.pyplot as plt

for tl, cl in zip([True, False, False], [False, False, True]):
    fig = plt.figure(constrained_layout=cl, tight_layout=tl)

    gs = fig.add_gridspec(2, 3)

    ax = dict()

    ax['A'] = fig.add_subplot(gs[0, 0:2])
    ax['B'] = fig.add_subplot(gs[1, 0:2])
    ax['C'] = fig.add_subplot(gs[:, 2])

    ax['C'].set_xlabel('Booger')
    ax['B'].set_xlabel('Booger')
    ax['A'].set_ylabel('Booger Y')
    fig.suptitle(f'TEST: tight_layout={tl} constrained_layout={cl}')
    fig.supxlabel('XLAgg')
    fig.supylabel('YLAgg')
    
    plt.show()

这里输入图片描述 这里输入图片描述 这里输入图片描述

查看更多


2
整洁,但似乎在plt.tight_layout()方面存在重叠问题。当我尝试更改f.supxlabel()的x参数时,边距也会出现问题。 - wordsforthewise
4
请确保使用Python 3.7或更高版本,以便能够安装matplotlib的版本3.4。 - Zaccharie Ramzi
您仍需要使用文本元素为不同位置添加多个Supy标签。 - Ari Cooper-Davis
2
如果tight_layout存在重叠问题,请尝试提供rect参数,例如增加左边距:fig.tight_layout(rect=(0.025,0,1,1)) - moi
2
这是最佳的规范解决方案。 - Jonas Daverio

46

没有 sharex=True, sharey=True,你会得到:

enter image description here

使用它,你应该会得到更好的效果:

fig, axes2d = plt.subplots(nrows=3, ncols=3,
                           sharex=True, sharey=True,
                           figsize=(6,6))

for i, row in enumerate(axes2d):
    for j, cell in enumerate(row):
        cell.imshow(np.random.rand(32,32))

plt.tight_layout()

在此输入图片描述

但如果您想添加额外的标签,只应将它们添加到边缘图中:

fig, axes2d = plt.subplots(nrows=3, ncols=3,
                           sharex=True, sharey=True,
                           figsize=(6,6))

for i, row in enumerate(axes2d):
    for j, cell in enumerate(row):
        cell.imshow(np.random.rand(32,32))
        if i == len(axes2d) - 1:
            cell.set_xlabel("noise column: {0:d}".format(j + 1))
        if j == 0:
            cell.set_ylabel("noise row: {0:d}".format(i + 1))

plt.tight_layout()

在此输入图片描述

为每个图添加标签会破坏它的美观(也许有一种自动检测重复标签的方法,但我不知道有哪一种)。


如果例如,图形的数量是未知的(例如,您有一个适用于任何子图数量的通用绘图函数),那么这将更加困难。 - naught101

17

自从执行以下命令:

fig,ax = plt.subplots(5,2,sharex=True,sharey=True,figsize=fig_size)

你使用的返回一个由图形和一系列轴实例组成的元组,已经足够像这样做(请注意,我已经将fig,ax更改为fig,axes):

fig,axes = plt.subplots(5,2,sharex=True,sharey=True,figsize=fig_size)

for ax in axes:
    ax.set_xlabel('Common x-label')
    ax.set_ylabel('Common y-label')

如果你想更改特定子图的某些细节,可以通过axes[i]访问它,其中i遍历您的子图。

包括一个

fig.tight_layout()
在文件末尾,在plt.show()之前,为了避免标签重叠。

6
抱歉,我之前表述有点不清楚。在这里,“common”指的是所有图形下方都只有一个 x 标签,并且所有图形左侧都只有一个 y 标签。我已更新问题以反映这一点。 - jolindbe
2
@JohanLindberg:关于您在此处和上面的评论:确实,plt.subplots()将创建一个新的图形实例。如果您想坚持使用此命令,可以轻松添加big_ax = fig.add_subplot(111),因为您已经有了一个图形并且可以添加另一个轴。之后,您可以按照Hooked中显示的方式操纵big_ax - Marius
2
@JohanLindberg,你说得对,我没有检查过。但是你可以通过以下方式轻松将大坐标轴的背景颜色设置为“none”:big_ax.set_axis_bgcolor('none') 你还应该将labelcolor设置为“none”(与Hooked提供的示例相反):big_ax.tick_params(labelcolor='none', top='off', bottom='off', left='off', right='off') - Marius
2
我在语句ax.set_xlabel('Common x-label')中遇到错误:AttributeError: 'numpy.ndarray' object has no attribute 'set_xlabel'。你能想出怎么解决吗? - hengxin
为了在 @Marius 的解决方案中添加一个缺失的元素,在初始化时使用 big_ax = fig.sub_plot(111, frameon=False) 来抑制边框。 - Sid
显示剩余3条评论

8

如果您为左下角的子图预留常用标签的空间,通过创建不可见的子图标签,效果会更好。从rcParams中传递fontsize也是一个好方法。这样,常用标签将随着您的rc设置而改变大小,轴也将被调整以留出常用标签的空间。

fig_size = [8, 6]
fig, ax = plt.subplots(5, 2, sharex=True, sharey=True, figsize=fig_size)
# Reserve space for axis labels
ax[-1, 0].set_xlabel('.', color=(0, 0, 0, 0))
ax[-1, 0].set_ylabel('.', color=(0, 0, 0, 0))
# Make common axis labels
fig.text(0.5, 0.04, 'common X', va='center', ha='center', fontsize=rcParams['axes.labelsize'])
fig.text(0.04, 0.5, 'common Y', va='center', ha='center', rotation='vertical', fontsize=rcParams['axes.labelsize'])

enter image description here enter image description here


1
很好地使用了隐形标签!谢谢。 - colelemonz

6

更新:

这个功能现在已经成为我最近在pypi上发布的proplot matplotlib package的一部分。默认情况下,在制作图形时,标签是在子图之间“共享”的。


原始答案:

我发现了一种更加健壮的方法:

如果你知道一个GridSpec初始化所需的bottomtop参数,或者以其他方式知道你的轴在Figure坐标系中的边缘位置,你还可以使用一些复杂的“transform”技巧指定Figure坐标系中的ylabel位置。

例如:

import matplotlib.pyplot as plt
import matplotlib.transforms as mtransforms
bottom, top = 0.1, 0.9
fig, axs = plt.subplots(nrows=2, ncols=1, bottom=bottom, top=top)
avepos = 0.5 * (bottom + top)
transform = mtransforms.blended_transform_factory(mtransforms.IdentityTransform(), fig.transFigure)  # specify x, y transform
axs[0].yaxis.label.set_transform(transform)  # changed from default blend (IdentityTransform(), axs[0].transAxes)
axs[0].yaxis.label.set_position((0, avepos))
axs[0].set_ylabel('Hello, world!')

...你应该看到标签仍然适当地左右调整以避免与标签重叠,就像正常情况一样,但也会将自己定位在所需的子图之间。

值得注意的是,如果省略 set_position 调用,则 ylabel 将准确显示在图形的中间。我猜这是因为当标签最终被绘制时,matplotlib 使用 0.5 作为 y 坐标,而没有检查底层的坐标转换是否已经改变。


3
我在制作一个图表网格时遇到了类似的问题。这些图表由两个部分(上部和下部)组成。y轴标签应该居中于两个部分之上。
我不想使用依赖于知道外部图形位置的解决方案(如fig.text()),因此我操纵了set_ylabel()函数的y位置。通常情况下,它是0.5,表示添加到绘图的中间位置。由于我的代码中部分之间的填充(hspace)为零,因此我可以计算出相对于上部分的两个部分的中间部分。
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec

# Create outer and inner grid
outerGrid = gridspec.GridSpec(2, 3, width_ratios=[1,1,1], height_ratios=[1,1])
somePlot = gridspec.GridSpecFromSubplotSpec(2, 1,
               subplot_spec=outerGrid[3], height_ratios=[1,3], hspace = 0)

# Add two partial plots
partA = plt.subplot(somePlot[0])
partB = plt.subplot(somePlot[1])

# No x-ticks for the upper plot
plt.setp(partA.get_xticklabels(), visible=False)

# The center is (height(top)-height(bottom))/(2*height(top))
# Simplified to 0.5 - height(bottom)/(2*height(top))
mid = 0.5-somePlot.get_height_ratios()[1]/(2.*somePlot.get_height_ratios()[0])
# Place the y-label
partA.set_ylabel('shared label', y = mid)

plt.show()

图片

缺点:

  • 水平距离是基于顶部的,底部刻度可能会延伸到标签中。

  • 公式没有考虑部分之间的空间。

  • 当顶部部分的高度为0时,会抛出异常。

可能有一个通用的解决方案,可以考虑图形之间的填充。


嘿,我找到了一种方法来解决这个问题,非常符合你的答案,但可能会解决其中的一些问题;请参见https://dev59.com/tGQo5IYBdhLWcg3wUd-L#44020303(下面) - Luke Davis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接