如何使用matplotlib为多个子图制作一个图例?


166

我正在绘制相同类型的信息,但针对不同的国家,使用matplotlib具有多个子图。也就是说,我在3x3网格上有9个图,所有图线都相同(当然,每条线的值不同)。

但是,我还没有想出如何仅在图上放置一个图例(因为所有9个子图都有相同的行)。

我怎么做?

Answers:


160

get_legend_handles_labels()您还可以在最后一个轴上调用一个不错的函数(如果对其进行迭代),该label=函数将从参数中收集所需的所有信息:

handles, labels = ax.get_legend_handles_labels()
fig.legend(handles, labels, loc='upper center')

13
这应该是最佳答案。
naught101 '17

1
这确实是一个更有用的答案!对于我来说,它就像在一个更复杂的情况下那样工作。
gmaravel

1
完美的答案!
多哈姆

4
如何删除子图的图例?
BND

5
只是为了增加这个好答案。如果您的绘图上有次要的y轴,并且需要将它们合并,请使用以下方法:handles, labels = [(a + b) for a, b in zip(ax1.get_legend_handles_labels(), ax2.get_legend_handles_labels())]
比尔

114

figlegend可能就是您要寻找的东西:http ://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.figlegend

此处的示例:http : //matplotlib.org/examples/pylab_examples/figlegend_demo.html

另一个例子:

plt.figlegend( lines, labels, loc = 'lower center', ncol=5, labelspacing=0. )

要么:

fig.legend( lines, labels, loc = (0.5, 0), ncol=5 )

1
我知道我想在图例中添加的行,但是我如何获取lines要放入参数的变量legend
patapouf_ai

1
@patapouf_ai lines是从中返回的结果的列表axes.plot()(即,每个axes.plot例程或类似例程均返回“行”)。另请参见链接的示例。

17

为了自动将单个图例定位在figure具有多个轴的轴上(如通过所获得的轴),subplots()以下解决方案非常有效:

plt.legend( lines, labels, loc = 'lower center', bbox_to_anchor = (0,-0.1,1,1),
            bbox_transform = plt.gcf().transFigure )

使用bbox_to_anchor和,bbox_transform=plt.gcf().transFigure您将定义一个与您的大小相同的新边框figure作为的参考loc。使用(0,-0.1,1,1)此装订线框稍微向下移动,以防止将图例放置在其他艺术家的上方。

OBS:使用之后fig.set_size_inches()和使用之前,请使用此解决方案fig.tight_layout()


1
或简单loc='upper center', bbox_to_anchor=(0.5, 0), bbox_transform=plt.gcf().transFigure,它肯定不会重叠。
达沃·乔西波维奇

2
我仍然不确定为什么,但是Evert的解决方案对我不起作用-传奇不断被切断。这个解决方案(连同davor的评论)非常干净地工作-传奇按预期放置并且完全可见。谢谢!
sudo make install

16

您只需要在循环外询问一次图例。

例如,在这种情况下,我有4个子图,它们具有相同的行和一个图例。

from matplotlib.pyplot import *

ficheiros = ['120318.nc', '120319.nc', '120320.nc', '120321.nc']

fig = figure()
fig.suptitle('concentration profile analysis')

for a in range(len(ficheiros)):
    # dados is here defined
    level = dados.variables['level'][:]

    ax = fig.add_subplot(2,2,a+1)
    xticks(range(8), ['0h','3h','6h','9h','12h','15h','18h','21h']) 
    ax.set_xlabel('time (hours)')
    ax.set_ylabel('CONC ($\mu g. m^{-3}$)')

    for index in range(len(level)):
        conc = dados.variables['CONC'][4:12,index] * 1e9
        ax.plot(conc,label=str(level[index])+'m')

    dados.close()

ax.legend(bbox_to_anchor=(1.05, 0), loc='lower left', borderaxespad=0.)
         # it will place the legend on the outer right-hand side of the last axes

show()

3
figlegend,由Evert提出,似乎是一个更好的解决方案;)
卡拉

11
问题fig.legend()是它需要标识所有的线(图)...因为对于每个子图,我正在使用循环生成线,所以我想克服的唯一解决方案是在创建空列表之前第二个循环,然后在创建时添加行...然后,我将此列表用作fig.legend()函数的参数。
卡拉


是什么dados呢?
Shyamkkhadka,

1
@Shyamkkhadka,在我的原始脚本中,dados是来自netCDF4文件的数据集(针对列表中定义的每个文件ficheiros)。在每个循环中,读取一个不同的文件,并将子图添加到图中。
卡拉

13

我注意到没有答案显示带有单个图例的图像,该图例引用了不同子图中的许多曲线,因此我必须向您展示一个……让您感到好奇……

在此处输入图片说明

现在,您想看一下代码,不是吗?

from numpy import linspace
import matplotlib.pyplot as plt

# Calling the axes.prop_cycle returns an itertoools.cycle

color_cycle = plt.rcParams['axes.prop_cycle']()

# I need some curves to plot

x = linspace(0, 1, 51)
f1 = x*(1-x)   ; lab1 = 'x - x x'
f2 = 0.25-f1   ; lab2 = '1/4 - x + x x' 
f3 = x*x*(1-x) ; lab3 = 'x x - x x x'
f4 = 0.25-f3   ; lab4 = '1/4 - x x + x x x'

# let's plot our curves (note the use of color cycle, otherwise the curves colors in
# the two subplots will be repeated and a single legend becomes difficult to read)
fig, (a13, a24) = plt.subplots(2)

a13.plot(x, f1, label=lab1, **next(color_cycle))
a13.plot(x, f3, label=lab3, **next(color_cycle))
a24.plot(x, f2, label=lab2, **next(color_cycle))
a24.plot(x, f4, label=lab4, **next(color_cycle))

# so far so good, now the trick

lines_labels = [ax.get_legend_handles_labels() for ax in fig.axes]
lines, labels = [sum(lol, []) for lol in zip(*lines_labels)]

# finally we invoke the legend (that you probably would like to customize...)

fig.legend(lines, labels)
plt.show()

两条线

lines_labels = [ax.get_legend_handles_labels() for ax in fig.axes]
lines, labels = [sum(lol, []) for lol in zip(*lines_labels)]

值得解释-为此,我将棘手的部分封装在一个函数中,仅用4行代码,但注释严重

def fig_legend(fig, **kwdargs):

    # generate a sequence of tuples, each contains
    #  - a list of handles (lohand) and
    #  - a list of labels (lolbl)
    tuples_lohand_lolbl = (ax.get_legend_handles_labels() for ax in fig.axes)
    # e.g. a figure with two axes, ax0 with two curves, ax1 with one curve
    # yields:   ([ax0h0, ax0h1], [ax0l0, ax0l1]) and ([ax1h0], [ax1l0])

    # legend needs a list of handles and a list of labels, 
    # so our first step is to transpose our data,
    # generating two tuples of lists of homogeneous stuff(tolohs), i.e
    # we yield ([ax0h0, ax0h1], [ax1h0]) and ([ax0l0, ax0l1], [ax1l0])
    tolohs = zip(*tuples_lohand_lolbl)

    # finally we need to concatenate the individual lists in the two
    # lists of lists: [ax0h0, ax0h1, ax1h0] and [ax0l0, ax0l1, ax1l0]
    # a possible solution is to sum the sublists - we use unpacking
    handles, labels = (sum(list_of_lists, []) for list_of_lists in tolohs)

    # call fig.legend with the keyword arguments, return the legend object

    return fig.legend(handles, labels, **kwdargs)

PS我认识到这sum(list_of_lists, [])是一种平整列表列表的方法,实际上是一种效率很低的方法,但是①我喜欢它的紧凑性,②通常在几个子图中有一些曲线,并且③Matplotlib和效率高吗?;-)


3

虽然游戏时间比较晚,但我将在此处提供另一种解决方案,因为这仍然是显示在Google上的第一个链接之一。使用matplotlib 2.2.2,可以使用gridspec功能来实现。在下面的示例中,目标是以2x2的方式排列四个子图,图例显示在底部。在底部创建一个“人造”轴,以将图例放置在固定位置。然后关闭“人造”轴,因此仅显示图例。结果:https : //i.stack.imgur.com/5LUWM.png

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

#Gridspec demo
fig = plt.figure()
fig.set_size_inches(8,9)
fig.set_dpi(100)

rows   = 17 #the larger the number here, the smaller the spacing around the legend
start1 = 0
end1   = int((rows-1)/2)
start2 = end1
end2   = int(rows-1)

gspec = gridspec.GridSpec(ncols=4, nrows=rows)

axes = []
axes.append(fig.add_subplot(gspec[start1:end1,0:2]))
axes.append(fig.add_subplot(gspec[start2:end2,0:2]))
axes.append(fig.add_subplot(gspec[start1:end1,2:4]))
axes.append(fig.add_subplot(gspec[start2:end2,2:4]))
axes.append(fig.add_subplot(gspec[end2,0:4]))

line, = axes[0].plot([0,1],[0,1],'b')           #add some data
axes[-1].legend((line,),('Test',),loc='center') #create legend on bottommost axis
axes[-1].set_axis_off()                         #don't show bottommost axis

fig.tight_layout()
plt.show()

3

如果您将子图与条形图一起使用,则每个条形具有不同的颜色。自己动手制作艺术品可能会更快mpatches

假设您有四个颜色不同的条形图,r m c k可以按如下所示设置图例

import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
labels = ['Red Bar', 'Magenta Bar', 'Cyan Bar', 'Black Bar']


#####################################
# insert code for the subplots here #
#####################################


# now, create an artist for each color
red_patch = mpatches.Patch(facecolor='r', edgecolor='#000000') #this will create a red bar with black borders, you can leave out edgecolor if you do not want the borders
black_patch = mpatches.Patch(facecolor='k', edgecolor='#000000')
magenta_patch = mpatches.Patch(facecolor='m', edgecolor='#000000')
cyan_patch = mpatches.Patch(facecolor='c', edgecolor='#000000')
fig.legend(handles = [red_patch, magenta_patch, cyan_patch, black_patch],labels=labels,
       loc="center right", 
       borderaxespad=0.1)
plt.subplots_adjust(right=0.85) #adjust the subplot to the right for the legend

1
+1最好的!我以这种方式使用了它,直接添加到,plt.legend以便为所有子图创建一个图例
用户

结合自动手柄和手工标签的速度更快:handles, _ = plt.gca().get_legend_handles_labels(),然后fig.legend(handles, labels)
smcs

1

此答案是@Evert在图例位置的补充。

由于图例和子图标题的重叠,我对@Evert解决方案的首次尝试失败。

实际上,重叠是由引起的fig.tight_layout(),这会更改子图的布局而无需考虑图形图例。但是,这fig.tight_layout()是必需的。

为了避免重叠,我们可以通过告知fig.tight_layout()为人物的图例保留空格fig.tight_layout(rect=(0,0,1,0.9))

.tight_layout()参数的描述


1

要建立在@gboffi和Ben Usman的答案之上:

在不同的子图中具有相同颜色和标签的不同线条的情况下,可以沿

labels_handles = {
  label: handle for ax in fig.axes for handle, label in zip(*ax.get_legend_handles_labels())
}

fig.legend(
  labels_handles.values(),
  labels_handles.keys(),
  loc="upper center",
  bbox_to_anchor=(0.5, 0),
  bbox_transform=plt.gcf().transFigure,
)
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.