如何为子图设置公共轴标签

我有如下的情节:

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

我希望能够不仅为两个子图创建轴标签和标题,而且还可以为两个子图创建公共标签。例如,由于两个图具有相同的轴,我只需要一组x轴和y轴标签。但我确实希望每个次要情节都有不同的标题。

我尝试了几件事,但没有一件是正确的

824865 次浏览

您可以创建一个涵盖这两个子图的大子图,然后设置公共标签。

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

另一种方法是使用fig.text()直接设置公共标签的位置。

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

如果你不打算导出矢量图形,或者你已经设置了matplotlib后端来忽略无色轴,Wen-wei Liao的答案是很好的;否则隐藏的轴将显示在导出的图形中。

我的答案suplabel在这里类似于使用fig.text函数的fig.suptitle。因此,没有斧头艺术家被创造和被制作成无色的。 然而,如果你尝试多次调用它,你会得到文本被添加在彼此之上(就像fig.suptitle一样)。wenwei Liao的回答没有,因为fig.add_subplot(111)将返回相同的Axes对象,如果它已经创建

我的函数也可以在创建图之后调用。

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)

使用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")

下面是一种解决方案,您可以设置其中一个图的ylabel并调整它的位置,使其垂直居中。这样可以避免KYC提到的问题。

import numpy as np
import matplotlib.pyplot as plt


def set_shared_ylabel(a, ylabel, labelpad = 0.01):
"""Set a y label shared by multiple axes
Parameters
----------
a: list of axes
ylabel: string
labelpad: float
Sets the padding between ticklabels and axis label"""


f = a[0].get_figure()
f.canvas.draw() #sets f.canvas.renderer needed below


# get the center position for all plots
top = a[0].get_position().y1
bottom = a[-1].get_position().y0


# get the coordinates of the left side of the tick labels
x0 = 1
for at in a:
at.set_ylabel('') # just to make sure we don't and up with multiple labels
bboxes, _ = at.yaxis.get_ticklabel_extents(f.canvas.renderer)
bboxes = bboxes.inverse_transformed(f.transFigure)
xt = bboxes.x0
if xt < x0:
x0 = xt
tick_label_left = x0


# set position of label
a[-1].set_ylabel(ylabel)
a[-1].yaxis.set_label_coords(tick_label_left - labelpad,(bottom + top)/2, transform=f.transFigure)


length = 100
x = np.linspace(0,100, length)
y1 = np.random.random(length) * 1000
y2 = np.random.random(length)


f,a = plt.subplots(2, sharex=True, gridspec_kw={'hspace':0})
a[0].plot(x, y1)
a[1].plot(x, y2)
set_shared_ylabel(a, 'shared y label (a. u.)')

enter image description here

其他答案中的方法在符号较大时将无法正常工作。ylabel将与刻度重叠,在左侧被剪切或完全不可见/在图形之外。

我修改了Hagne的答案,使它适用于超过一列的子图,对于xlabel和ylabel,并且它移动了图以保持ylabel在图中可见。

def set_shared_ylabel(a, xlabel, ylabel, labelpad = 0.01, figleftpad=0.05):
"""Set a y label shared by multiple axes
Parameters
----------
a: list of axes
ylabel: string
labelpad: float
Sets the padding between ticklabels and axis label"""


f = a[0,0].get_figure()
f.canvas.draw() #sets f.canvas.renderer needed below


# get the center position for all plots
top = a[0,0].get_position().y1
bottom = a[-1,-1].get_position().y0


# get the coordinates of the left side of the tick labels
x0 = 1
x1 = 1
for at_row in a:
at = at_row[0]
at.set_ylabel('') # just to make sure we don't and up with multiple labels
bboxes, _ = at.yaxis.get_ticklabel_extents(f.canvas.renderer)
bboxes = bboxes.inverse_transformed(f.transFigure)
xt = bboxes.x0
if xt < x0:
x0 = xt
x1 = bboxes.x1
tick_label_left = x0


# shrink plot on left to prevent ylabel clipping
# (x1 - tick_label_left) is the x coordinate of right end of tick label,
# basically how much padding is needed to fit tick labels in the figure
# figleftpad is additional padding to fit the ylabel
plt.subplots_adjust(left=(x1 - tick_label_left) + figleftpad)


# set position of label,
# note that (figleftpad-labelpad) refers to the middle of the ylabel
a[-1,-1].set_ylabel(ylabel)
a[-1,-1].yaxis.set_label_coords(figleftpad-labelpad,(bottom + top)/2, transform=f.transFigure)


# set xlabel
y0 = 1
for at in axes[-1]:
at.set_xlabel('')  # just to make sure we don't and up with multiple labels
bboxes, _ = at.xaxis.get_ticklabel_extents(fig.canvas.renderer)
bboxes = bboxes.inverse_transformed(fig.transFigure)
yt = bboxes.y0
if yt < y0:
y0 = yt
tick_label_bottom = y0


axes[-1, -1].set_xlabel(xlabel)
axes[-1, -1].xaxis.set_label_coords((left + right) / 2, tick_label_bottom - labelpad, transform=fig.transFigure)

它适用于以下示例,而Hagne的回答不会绘制ylabel(因为它在画布之外),KYC的ylabel与tick标签重叠:

import matplotlib.pyplot as plt
import itertools


fig, axes = plt.subplots(3, 4, sharey='row', sharex=True, squeeze=False)
fig.subplots_adjust(hspace=.5)
for i, a in enumerate(itertools.chain(*axes)):
a.plot([0,4**i], [0,4**i])
a.set_title(i)
set_shared_ylabel(axes, 'common X', 'common Y')
plt.show()

或者,如果您对无色轴满意,我修改了Julian Chen的解决方案,使ylabel不会与tick标签重叠。

基本上,我们只需要设置无色的ylim,这样它就能匹配子图中最大的ylim,所以无色的tick labels为ylabel设置了正确的位置。

同样,我们必须缩小情节以防止剪辑。这里我已经硬编码了要收缩的数量,但你可以找到一个适合你的数字,或者像上面的方法一样计算它。

import matplotlib.pyplot as plt
import itertools


fig, axes = plt.subplots(3, 4, sharey='row', sharex=True, squeeze=False)
fig.subplots_adjust(hspace=.5)
miny = maxy = 0
for i, a in enumerate(itertools.chain(*axes)):
a.plot([0,4**i], [0,4**i])
a.set_title(i)
miny = min(miny, a.get_ylim()[0])
maxy = max(maxy, a.get_ylim()[1])


# add a big axes, hide frame
# set ylim to match the largest range of any subplot
ax_invis = fig.add_subplot(111, frameon=False)
ax_invis.set_ylim([miny, maxy])


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


# shrink plot to prevent clipping
plt.subplots_adjust(left=0.15)
plt.show()
# list loss and acc are your data
fig = plt.figure()
ax1 = fig.add_subplot(121)
ax2 = fig.add_subplot(122)


ax1.plot(iteration1, loss)
ax2.plot(iteration2, acc)


ax1.set_title('Training Loss')
ax2.set_title('Training Accuracy')


ax1.set_xlabel('Iteration')
ax1.set_ylabel('Loss')


ax2.set_xlabel('Iteration')
ax2.set_ylabel('Accuracy')

plt.setp()将做的工作:

# 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

matplotlib 3.4.0新增功能

现在有内置的方法来设置公共轴标签:


重现OP的loglog图(通用标签但独立标题):

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

你可以用“set”;坐标轴如下:

axes[0].set(xlabel="KartalOl", ylabel="Labeled")