matplotlib子图的通用xlabel / ylabel


140

我有以下情节:

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

现在我想给这个图提供通用的x轴标签和y轴标签。对于“ common”,我的意思是在整个子图的网格下方应有一个大的x轴标签,在右侧应有一个大的y轴标签。我在的文档中找不到关于此的任何内容plt.subplots,而我的谷歌搜索建议我需要做一个很大的工作plt.subplot(111)-但是我该如何使用5 * 2的子图将其放入其中plt.subplots呢?


2
随着问题的更新,以及下面答案中剩下的评论,这是stackoverflow.com/questions/6963035/…
Hooked

并非如此,因为我的问题是关于plt.subplots()的,并且您链接到的问题使用add_subplot-除非切换到add_subplot,否则我将无法使用该方法,我想避免这种情况。我可以使用plt.text解决方案,它是您链接中的替代解决方案,但这不是最优雅的解决方案。
jolindbe

详细地说,据我所知,plt.subplots不能在现有轴环境中生成一组子图,但是总是创建一个新图形。对?
jolindbe

一个最优雅的解决方案可以在这里找到:stackoverflow.com/questions/6963035/…–
H先生

您的链接是由Hooked用户于4年前提供的(仅在您的评论上方几句话)。如我之前所说,该解决方案与add_subplot有关,与plt.subplots()不相关。
jolindbe

Answers:


206

这看起来像您真正想要的。它对您的特定情况采用与该答案相同的方法:

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

具有公共轴标签的多个图


4
请注意,x标签的x坐标的0.5不会将标签放在中心子图的中心。您需要比yticklabel大一些。
dbliss 2016年

2
看一下这个答案,找出一种不使用的方法plt.text。创建子图,然后添加一个位图,使其不可见,并标记其x和y。
James Owers

谢谢,总的来说。使用时有任何破损解决方案tight_layout吗?
serv-inc

3
@ serv-inc tight_layout替换0.040似乎有效。
divenex

3
使用fig.text不是一个好主意。这搞砸了类似的事情plt.tight_layout()
和平的

53

由于我认为它足够相关且优雅(无需指定坐标来放置文本),因此我将其复制(稍作修改)另一个相关问题的答案

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', top=False, bottom=False, left=False, right=False)
plt.xlabel("common X")
plt.ylabel("common Y")

结果如下(使用matplotlib版本2.2.0):

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


3
由于简单,这应该是公认的答案。非常简单。仍然与matplotlib v3.x相关。
凯尔·斯旺森

我想知道如何与多个图形对象一起使用?fig.xlabel(“ foo”)不起作用。
恐怖Vacui

仅供参考:现在人们在StackOverflow中使用深色主题,几乎无法读取标签,因此最好导出带有白色背景的png
xyzzyqed

@xyzzyqed我不知道stackoverflow中是否存在“主题”之类的东西,我什至不记得我是如何导出该图的。导出时如何控制背景?
bli

2
该解决方案的唯一问题是,constrained_layout=True由于创建重叠的标签,因此在使用时不起作用。在这种情况下,您必须手动调整子图的边框。
baccandr

35

如果没有sharex=True, sharey=True得到:

在此处输入图片说明

有了它,你应该变得更好:

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

15

由于命令:

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

您使用的返回一个由图和轴实例列表组成的元组,已经足够做类似的事情了(注意,我已更改fig,axfig,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(),以避免标签重叠。


5
对不起,上面我还不清楚。我将“公共”一词表示为在所有图下方添加一个x标签,并在图左侧添加一个y标签,我更新了问题以反映这一点。
jolindbe

2
@JohanLindberg:关于您在此处和上方的评论:实际上plt.subplots()将创建一个新的图形实例。如果要坚持使用此命令,则可以轻松添加big_ax = fig.add_subplot(111),因为您已经有一个图形并且可以添加另一个轴。之后,您可以操纵big_ax它在Hooked链接中的显示方式。
Marius

感谢您的建议,但是如果这样做,我必须在plt.subplots()之后添加big_ax,然后使该子图位于其他所有内容之上-我可以使其透明化还是以某种方式将其发送回背面吗?即使像Hooked的链接中那样将所有颜色都设置为空,它仍然是一个白色框,覆盖了我的所有子图。
jolindbe

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
我收到一个错误:AttributeError: 'numpy.ndarray' object has no attribute 'set_xlabel'在语句中ax.set_xlabel('Common x-label')。你能弄清楚吗?
hengxin

5

如果通过在左下角为子图制作不可见的标签来保留公共标签的空间,效果会更好。从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'])

在此处输入图片说明 在此处输入图片说明


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

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时引发异常。

可能存在一个通用解决方案,其中考虑了数字之间的填充。


嘿,我想出了一种方法来解决您的问题,但是可以解决其中的一些问题。参见stackoverflow.com/a/44020303/4970632(如下)
卢克·戴维斯

2

更新:

该功能现在是我最近在pypi上发布的proplot matplotlib软件包的一部分。默认情况下,创建图形时,标签在轴之间“共享”。


原始答案:

我发现了一个更强大的方法:

如果您知道进行初始化的bottom和和topkwargs GridSpec,或者您另外知道Figure坐标中轴的边缘位置,则还可以Figure使用一些奇特的“变换”魔术在坐标中指定ylabel位置。例如:

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

...并且您应该看到标签仍然像往常一样适当地左右调整以免与刻度标签重叠-但现在它将调整为始终精确地位于所需的子图之间

此外,如果您甚至不使用set_positionylabel,则默认情况下,它会恰好显示在图形的中间。我猜这是因为最后绘制标签时matplotlib,对y-coordinate 使用0.5 而不检查基础坐标转换是否已更改。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.