Python 如何使用matplotlib使两个标记在图例中共享同一标签?

Python 如何使用matplotlib使两个标记在图例中共享同一标签?,python,matplotlib,label,legend,marker,Python,Matplotlib,Label,Legend,Marker,我想要的是这样的: 我得到的是: 那么,如何将标记合并到一个标签中呢? 当然,对于线条,对于线条,您可以通过在使用相同线型时不为第二条线条指定标签来实现,但是对于标记,您不能这样做,因为它们的形状不同。这里有一个新的解决方案,它将使用相同的标签绘制任何标记集合。我还没有弄明白如何使用线图中的标记,但是如果需要的话,可以在线图的顶部绘制散点图 from matplotlib import pyplot as plt import matplotlib.collections as mcol i

我想要的是这样的:

我得到的是:

那么,如何将标记合并到一个标签中呢?
当然,对于线条,对于线条,您可以通过在使用相同线型时不为第二条线条指定标签来实现,但是对于标记,您不能这样做,因为它们的形状不同。

这里有一个新的解决方案,它将使用相同的标签绘制任何标记集合。我还没有弄明白如何使用线图中的标记,但是如果需要的话,可以在线图的顶部绘制散点图

from matplotlib import pyplot as plt
import matplotlib.collections as mcol
import matplotlib.transforms as mtransforms
import numpy as np
from matplotlib.legend_handler import HandlerPathCollection
from matplotlib import cm


class HandlerMultiPathCollection(HandlerPathCollection):
    """
    Handler for PathCollections, which are used by scatter
    """
    def create_collection(self, orig_handle, sizes, offsets, transOffset):
        p = type(orig_handle)(orig_handle.get_paths(), sizes=sizes,
                              offsets=offsets,
                              transOffset=transOffset,
                              )
        return p

fig, ax = plt.subplots()
#make some data to plot
x = np.arange(0, 100, 10)
models = [.05 * x, 8 * np.exp(- .1 * x), np.log(x + 1), .01 * x]
tests = [model + np.random.rand(len(model)) - .5 for model in models]
#make colors and markers
colors = cm.brg(np.linspace(0, 1, len(models)))
markers = ['o', 'D', '*', 's']
markersize = 50
plots = []
#plot points and lines
for i in xrange(len(models)):
    line, = plt.plot(x, models[i], linestyle = 'dashed', color = 'black', label = 'Model')
    plot = plt.scatter(x, tests[i], c = colors[i], s = markersize, marker = markers[i])
    plots.append(plot)

#get attributes
paths = []
sizes = []
facecolors = []
edgecolors = []
for plot in plots:
    paths.append(plot.get_paths()[0])
    sizes.append(plot.get_sizes()[0])
    edgecolors.append(plot.get_edgecolors()[0])
    facecolors.append(plot.get_facecolors()[0])

#make proxy artist out of a collection of markers
PC = mcol.PathCollection(paths, sizes, transOffset = ax.transData, facecolors = colors, edgecolors = edgecolors)
PC.set_transform(mtransforms.IdentityTransform())
plt.legend([PC, line], ['Test', 'Model'], handler_map = {type(PC) : HandlerMultiPathCollection()}, scatterpoints = len(paths), scatteryoffsets = [.5], handlelength = len(paths))
plt.show()

我有一个解决方案给你,如果你愿意使用所有的圆圈标记和区分颜色只。可以使用圆集合来表示标记,然后将集合作为一个整体使用图例标签

示例代码:

import matplotlib.pyplot as plt
import matplotlib.collections as collections
from matplotlib import cm
import numpy as np

#make some data to plot
x = np.arange(0, 100, 10)
models = [.05 * x, 8 * np.exp(- .1 * x), np.log(x + 1), .01 * x]
tests = [model + np.random.rand(len(model)) - .5 for model in models]
#make colors
colors = cm.brg(np.linspace(0, 1, len(models)))
markersize = 50
#plot points and lines
for i in xrange(len(models)):
    line, = plt.plot(x, models[i], linestyle = 'dashed', color = 'black', label = 'Model')
    plt.scatter(x, tests[i], c = colors[i], s = markersize)
#create collection of circles corresponding to markers
circles = collections.CircleCollection([markersize] * len(models), facecolor = colors)
#make the legend -- scatterpoints needs to be the same as the number 
#of markers so that all the markers show up in the legend
plt.legend([circles, line], ['Test', 'Model'], scatterpoints = len(models), scatteryoffsets = [.5], handlelength = len(models))
plt.show()

我认为最好使用完整的图例-否则,您的读者如何知道这两个模型或两个数据集之间的差异?我会这样做:

但是,如果您真的想按自己的方式来做,您可以使用如下所示的自定义图例。您需要像他们一样创建自己的类,该类定义
legend\u artist
方法,然后根据需要添加正方形和圆形。以下是生成的绘图和用于生成它的代码:

#!/usr/bin/env python
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import numpy as np


# ==================================
# Define the form of the function
# ==================================
def model(x, A=190, k=1):
    return A * np.exp(-k*x/50)

# ==================================
# How many data points are generated
# ==================================
num_samples = 15

# ==================================
# Create data for plots
# ==================================
x_model = np.linspace(0, 130, 200)

x_data1 = np.random.rand(num_samples) * 130
x_data1.sort()

x_data2 = np.random.rand(num_samples) * 130
x_data2.sort()

data1 = model(x_data1, k=1) * (1 + np.random.randn(num_samples) * 0.2)
data2 = model(x_data2, k=2) * (1 + np.random.randn(num_samples) * 0.15)

model1 = model(x_model, k=1)
model2 = model(x_model, k=2)

# ==================================
# Plot everything normally
# ==================================
fig = plt.figure()
ax = fig.add_subplot('111')
ax.plot(x_data1, data1, 'ok', markerfacecolor='none', label='Data (k=1)')
ax.plot(x_data2, data2, 'sk', markeredgecolor='0.5', markerfacecolor='0.5', label='Data (k=2)')
ax.plot(x_model, model1, '-k', label='Model (k=1)')
ax.plot(x_model, model2, '--k', label='Model (k=2)')

# ==================================
# Format plot
# ==================================
ax.set_xlabel('Distance from heated face($10^{-2}$ m)')
ax.set_ylabel('Temperature ($^\circ$C)')
ax.set_xlim((0, 130))
ax.set_title('Normal way to plot')
ax.legend()
fig.tight_layout()

plt.show()


# ==================================
# ==================================
# Do it again, but with custom
# legend
# ==================================
# ==================================
class AnyObject(object):
    pass


class data_handler(object):
    def legend_artist(self, legend, orig_handle, fontsize, handlebox):
        scale = fontsize / 22
        x0, y0 = handlebox.xdescent, handlebox.ydescent
        width, height = handlebox.width, handlebox.height
        patch_sq = mpatches.Rectangle([x0, y0 + height/2 * (1 - scale) ], height * scale, height * scale, facecolor='0.5',
                edgecolor='0.5', transform=handlebox.get_transform())
        patch_circ = mpatches.Circle([x0 + width - height/2, y0 + height/2], height/2 * scale, facecolor='none',
                edgecolor='black', transform=handlebox.get_transform())

        handlebox.add_artist(patch_sq)
        handlebox.add_artist(patch_circ)
        return patch_sq

# ==================================
# Plot everything
# ==================================
fig = plt.figure()
ax = fig.add_subplot('111')
d1 = ax.plot(x_data1, data1, 'ok', markerfacecolor='none', label='Data (k=2)')
d2 = ax.plot(x_data2, data2, 'sk', markeredgecolor='0.5', markerfacecolor='0.5', label='Data (k=1)')
m1 = ax.plot(x_model, model1, '-k', label='Model (k=1)')
m2 = ax.plot(x_model, model2, '-k', label='Model (k=2)')

# ax.legend([d1], handler_map={ax.plot: data_handler()})
ax.legend([AnyObject(), m1[0]], ['Data', 'Model'], handler_map={AnyObject: data_handler()})

# ==================================
# Format plot
# ==================================
ax.set_xlabel('Distance from heated face($10^{-2}$ m)')
ax.set_ylabel('Temperature ($^\circ$C)')
ax.set_xlim((0, 130))
ax.set_title('Custom legend')
fig.tight_layout()

plt.show()


可以通过在不使用任何标签的情况下打印数据,然后单独添加标签来完成此操作:

from matplotlib import pyplot as plt
from numpy import random

xs = range(10)
data = random.rand(10, 2)    
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
kwargs = {'color': 'r', 'linewidth': 2, 'linestyle': '--'}

ax.plot(xs, data, **kwargs)
ax.plot([], [], label='Model', **kwargs)
ax.legend()
plt.show()

我还发现非常有用(下面的代码),这是处理此问题的一种更简单的方法。它基本上是使用一个图例句柄列表,使第一个句柄的一个标记不可见,并将其与第二个句柄的标记重叠。通过这种方式,两个标记彼此相邻,并带有一个标签

在上面的代码中,使用
TupleHandler
创建图例句柄 只需在两个手柄上刷过漆(蓝色后面有红色方块) 如果你仔细看,就画圆圈。你要做的是做第二个 第一个手柄的标记和第二个手柄的第一个标记 不可见。不幸的是,
TupleHandler
是最近添加的 你需要一个特殊的函数来获取所有的句柄,否则 可以使用
Legend.legendHandles
属性(它只显示第一个 TupleHandler的句柄


请注意,在matplotlib的最新版本中,您可以使用以下内容实现此目的:


这是一个有用的替代方案,但是,当以黑白打印时,仍然很难区分彼此。:)这看起来不错,但当我运行您的示例代码时,它显示“TypeError:“data\u handler”对象不可调用”…您正在运行哪种版本的Python?将数据处理程序解释为一个类似乎有问题,您是否运行良好。。。我不知道该告诉你什么,这个错误对我来说没有意义,因为数据处理程序是一个类,而不是一个函数,所以我不知道为什么你的解释器试图调用它。它对我有用。也许可以尝试升级您的python安装?在我的python版本中,它也是一个类。对象是类的实例。我有一些困惑:1.AnyObject:data_handler()?这两个物体之间有关系吗?2.在类数据_处理程序(对象)中,为什么只返回patch_sq?3.AnyObject:data_handler()?它需要任何参数输入吗?谢谢你,Joel~啊哈,你真的是一个专家,甚至就你的直觉而言,我安装了新的matplotlib,然后一切都好了。
fig, ax = plt.subplots()
p1 = ax.scatter([0.1],[0.5],c='r',marker='s')
p2 = ax.scatter([0.3],[0.2],c='b',marker='o')
l = ax.legend([(p1,p2)],['points'],scatterpoints=2)
def get_handle_lists(l):
    """returns a list of lists of handles.
    """
    tree = l._legend_box.get_children()[1]

    for column in tree.get_children():
        for row in column.get_children():
            yield row.get_children()[0].get_children()
handles_list = list(get_handle_lists(l))
handles = handles_list[0] # handles is a list of two PathCollection.
                          # The first one is for red squares, and the second
                          # is for blue circles.
handles[0].set_facecolors(["r", "none"]) # for the fist
                   # PathCollection, make the
                   # second marker invisible by
                   # setting their facecolor and
                   # edgecolor to "none."
handles[0].set_edgecolors(["k", "none"])
handles[1].set_facecolors(["none", "b"])
handles[1].set_edgecolors(["none", "k"])
fig
import matplotlib.pyplot as plt
from matplotlib.legend_handler import HandlerTuple
fig, ax1 = plt.subplots(1, 1)

# First plot: two legend keys for a single entry
p2, = ax1.plot([3, 4], [2, 3], 'o', mfc="white", mec="k")
p1, = ax1.plot([1, 2], [5, 6], 's', mfc="gray", mec="gray")
# `plot` returns a list, but we want the handle - thus the comma on the left
p3, = ax1.plot([1, 5], [4, 4], "-k")
p4, = ax1.plot([2, 6], [3, 2], "-k")

# Assign two of the handles to the same legend entry by putting them in a tuple
# and using a generic handler map (which would be used for any additional
# tuples of handles like (p1, p3)).
l = ax1.legend([(p1, p2), p3], ['data', 'models'],
               handler_map={tuple: HandlerTuple(ndivide=None)})

plt.savefig("demo.png")