为图例中的点设置固定大小

Gab*_*iel 31 python legend scatter-plot

我正在制作一些散点图,我想将图例中点的大小设置为固定的相等值.

现在我有这个:

import matplotlib.pyplot as plt
import numpy as np

def rand_data():
    return np.random.uniform(low=0., high=1., size=(100,))

# Generate data.
x1, y1 = [rand_data() for i in range(2)]
x2, y2 = [rand_data() for i in range(2)]


plt.figure()
plt.scatter(x1, y1, marker='o', label='first', s=20., c='b')
plt.scatter(x2, y2, marker='o', label='second', s=35., c='r')
# Plot legend.
plt.legend(loc="lower left", markerscale=2., scatterpoints=1, fontsize=10)
plt.show()
Run Code Online (Sandbox Code Playgroud)

产生这个:

在此输入图像描述

图例中点的大小是缩放的但不相同.如何在不影响图中大小的情况下将图例中点的大小固定为相等的值scatter

DrV*_*DrV 47

我看了一下源代码matplotlib.坏消息是,似乎没有任何简单的方法可以在图例中设置相同大小的点.散点图特别困难(错误:请参阅下面的更新).基本上有两种选择:

  1. 更改maplotlib代码
  2. 将变换添加到PathCollection表示图像中的点的对象中.变换(缩放)必须考虑原始大小.

这些都不是很有趣,虽然#1似乎更容易.scatter在这方面,这些情节尤其具有挑战性.

但是,我有一个hack可能你想要的东西:

import matplotlib.pyplot as plt
import numpy as np

def rand_data():
    return np.random.uniform(low=0., high=1., size=(100,))

# Generate data.
x1, y1 = [rand_data() for i in range(2)]
x2, y2 = [rand_data() for i in range(2)]

plt.figure()
plt.plot(x1, y1, 'o', label='first', markersize=np.sqrt(20.), c='b')
plt.plot(x2, y2, 'o', label='second', markersize=np.sqrt(35.), c='r')
# Plot legend.
lgnd = plt.legend(loc="lower left", numpoints=1, fontsize=10)

#change the marker size manually for both lines
lgnd.legendHandles[0]._legmarker.set_markersize(6)
lgnd.legendHandles[1]._legmarker.set_markersize(6)
plt.show()
Run Code Online (Sandbox Code Playgroud)

这给出了:

在此输入图像描述

这似乎是你想要的.

变化:

  • scatter更改为a plot,这会更改标记缩放(因此更改sqrt)并且无法使用更改标记大小(如果是这样)
  • 手动更改标记大小为图例中两个标记的6个点

正如您所看到的,这利用了隐藏的下划线属性(_legmarker)并且是丑陋的.它可能会在任何更新时崩溃matplotlib.

更新

哈,我找到了.一个更好的黑客:

import matplotlib.pyplot as plt
import numpy as np

def rand_data():
    return np.random.uniform(low=0., high=1., size=(100,))

# Generate data.
x1, y1 = [rand_data() for i in range(2)]
x2, y2 = [rand_data() for i in range(2)]

plt.figure()
plt.scatter(x1, y1, marker='o', label='first', s=20., c='b')
plt.scatter(x2, y2, marker='o', label='second', s=35., c='r')
# Plot legend.
lgnd = plt.legend(loc="lower left", scatterpoints=1, fontsize=10)
lgnd.legendHandles[0]._sizes = [30]
lgnd.legendHandles[1]._sizes = [30]
plt.show()
Run Code Online (Sandbox Code Playgroud)

现在_sizes(另一个下划线属性)可以解决问题.不需要触摸源,即使这是一个非常黑客.但现在你可以使用所有scatter优惠.

在此输入图像描述

  • 我只是想指出第二个 hack 不再起作用,至少对我来说(`python 3.5`,`matplotlib 1.5.1`)。也许他们改变了`matplotlib`的代码。不过第一个确实有效,非常感谢。 (3认同)
  • 很棒的提示。legendHandles 方法在 python 3.6 上不适用于我。但第一种方法仍然有效。 (3认同)

小智 15

与答案类似,假设您希望所有标记具有相同的大小:

lgnd = plt.legend(loc="lower left", scatterpoints=1, fontsize=10)
for handle in lgnd.legendHandles:
    handle.set_sizes([6.0])
Run Code Online (Sandbox Code Playgroud)

使用MatPlotlib 2.0.0


小智 11

您可以制作一个类似于您选择的标记的 Line2D 对象,但您选择的标记大小不同,并使用它来构建图例。这很好,因为它不需要在轴中放置对象(可能会触发调整大小事件),并且不需要使用任何隐藏属性。唯一真正的缺点是您必须从对象和标签列表中明确构建图例,但这是一个有据可查的 matplotlib 功能,因此使用起来非常安全。

from matplotlib.lines import Line2D
import matplotlib.pyplot as plt
import numpy as np

def rand_data():
    return np.random.uniform(low=0., high=1., size=(100,))

# Generate data.
x1, y1 = [rand_data() for i in range(2)]
x2, y2 = [rand_data() for i in range(2)]

plt.figure()
plt.scatter(x1, y1, marker='o', label='first', s=20., c='b')
plt.scatter(x2, y2, marker='o', label='second', s=35., c='r')

# Create dummy Line2D objects for legend
h1 = Line2D([0], [0], marker='o', markersize=np.sqrt(20), color='b', linestyle='None')
h2 = Line2D([0], [0], marker='o', markersize=np.sqrt(20), color='r', linestyle='None')

# Set axes limits
plt.gca().set_xlim(-0.2, 1.2)
plt.gca().set_ylim(-0.2, 1.2)

# Plot legend.
plt.legend([h1, h2], ['first', 'second'], loc="lower left", markerscale=2,
           scatterpoints=1, fontsize=10)
plt.show()
Run Code Online (Sandbox Code Playgroud)

结果图


Ste*_*ell 7

虽然我的用例很独特,但使用@ DrV解决方案并没有太大成功.由于点的密度,我使用最小的标记大小,即plt.plot(x, y, '.', ms=1, ...),并希望图例符号更大.

我按照我在matplotlib论坛上发现的建议:

  1. 绘制数据(无标签)
  2. 记录轴限制(xlimits = plt.xlim())
  3. 使用符合图例的符号颜色和大小来绘制远离实际数据的虚假数据
  4. 恢复轴限制(plt.xlim(xlimits))
  5. 创造传奇

结果如下(为此,点实际上不那么重要): 在此输入图像描述

希望这有助于其他人.

  • 这是一个美丽的情节 (4认同)

Imp*_*est 6

这里只是另一种选择。这样做的优点是它不会使用任何“私有”方法,并且甚至可以与图例中存在的散点之外的其他对象一起使用。关键是将 scatter 映射PathCollection到 aHandlerPathCollection并为其设置更新函数。

def update(handle, orig):
    handle.update_from(orig)
    handle.set_sizes([64])

plt.legend(handler_map={PathCollection : HandlerPathCollection(update_func=update)})
Run Code Online (Sandbox Code Playgroud)

完整代码示例:

import matplotlib.pyplot as plt
import numpy as np; np.random.seed(42)
from matplotlib.collections import PathCollection
from matplotlib.legend_handler import HandlerPathCollection, HandlerLine2D

colors = ["limegreen", "crimson", "indigo"]
markers = ["o", "s", r"$\clubsuit$"]
labels = ["ABC", "DEF", "XYZ"]
plt.plot(np.linspace(0,1,8), np.random.rand(8), marker="o", markersize=22, label="A line")
for i,(c,m,l) in enumerate(zip(colors,markers,labels)):
    plt.scatter(np.random.rand(8),np.random.rand(8), 
                c=c, marker=m, s=10+np.exp(i*2.9), label=l)

def updatescatter(handle, orig):
    handle.update_from(orig)
    handle.set_sizes([64])

def updateline(handle, orig):
    handle.update_from(orig)
    handle.set_markersize(8)

plt.legend(handler_map={PathCollection : HandlerPathCollection(update_func=updatescatter),
                        plt.Line2D : HandlerLine2D(update_func = updateline)})

plt.show()
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明