【问题标题】:matplotlib: how to annotate point on a scatter automatically placed arrow?matplotlib:如何在散点自动放置箭头上注释点?
【发布时间】:2012-02-22 21:41:27
【问题描述】:

如果我用 matplotlib 制作散点图:

plt.scatter(randn(100),randn(100))
# set x, y lims
plt.xlim([...])
plt.ylim([...])

我想用指向它的箭头和标签来注释给定点(x, y)。我知道这可以用annotate 来完成,但我希望箭头和它的标签以这样的方式“最佳”放置,如果可能的话(给定当前轴的比例/限制)箭头和标签不要与其他点重叠。例如,如果您想标记离群点。有没有办法做到这一点?它不必是完美的,而只是箭头/标签的智能放置,仅给出要标记的点的(x,y) 坐标。谢谢。

【问题讨论】:

  • 附带说明,scatter 不适用于您正在做的事情。当您想通过改变标记的颜色和/或大小来绘制 3 或 4 维数据时使用它。当你只想要积分时不要使用它。将它用于点本身并没有什么问题,但它会返回一个集合,这比 plot 返回的 Line2D 对象更复杂。

标签: python numpy matplotlib scipy


【解决方案1】:

基本上,不,没有。

处理与此类似的地图标签放置的布局引擎非常复杂,超出了 matplotlib 的范围。 (边界框交叉点实际上是决定标签放置位置的一种相当糟糕的方法。为仅在 1000 种情况下有效的东西编写大量代码有什么意义?)

除此之外,由于 matplotlib 执行的复杂文本渲染量(例如乳胶),如果不先完全渲染它就不可能确定文本的范围(这相当慢)。

但是,在许多情况下,您会发现在带有注释的标签后面使用透明框是一种合适的解决方法。

例如

import numpy as np
import matplotlib.pyplot as plt

np.random.seed(1)
x, y = np.random.random((2,500))

fig, ax = plt.subplots()
ax.plot(x, y, 'bo')

# The key option here is `bbox`. I'm just going a bit crazy with it.
ax.annotate('Something', xy=(x[0], y[0]), xytext=(-20,20), 
            textcoords='offset points', ha='center', va='bottom',
            bbox=dict(boxstyle='round,pad=0.2', fc='yellow', alpha=0.3),
            arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=0.5', 
                            color='red'))

plt.show()

【讨论】:

  • 我喜欢你的例子。也可以将注释放在框架之外,例如对textcoords 使用“轴分数”。放置的自动性质可以包装到一个创建注释的小方法中。如果该点最靠近底部中心,则将其放在x轴下方;如果它更靠近右边缘,则将其放在那里,甚至可能会自动旋转它....仍然需要一些技巧,因为它可能会隐藏刻度标签,或者被图形边缘裁剪,等等...
  • @Joe:你如何决定给xytext(在本例中为(-20,20))赋予什么价值?
  • @user248273 - 这是点的偏移量。这是任意的,但它不会取决于您的数据范围。注意 kwarg textcoords='offset points'。为textcoords 传递各种其他值控制如何解释xytext 中的数字。
【解决方案2】:

使用adjustText(完全公开,我写的)。

让我们标记前 10 个点。我更改的唯一参数是降低点的排斥力,因为它们太多了,我们希望算法花费更多时间并更仔细地放置注释。

import numpy as np
import matplotlib.pyplot as plt
from adjustText import adjust_text
np.random.seed(1)
x, y = np.random.random((2,500))

fig, ax = plt.subplots()
ax.plot(x, y, 'bo')
ts = []
for i in range(10):
    ts.append(plt.text(x[i], y[i], 'Something'+str(i)))
adjust_text(ts, x=x, y=y, force_points=0.1, arrowprops=dict(arrowstyle='->', 
color='red'))
plt.show()

这并不理想,但这里的点非常密集,有时无法将文本放置在目标附近而不重叠任何一个。但它都是自动的且易于使用,并且不会让标签相互重叠。

PS 它使用边界框交叉点,但我会说相当成功!

【讨论】:

    【解决方案3】:

    另一个使用很棒的Phlya的基于adjustText_mtcars的包的例子:

    from adjustText import adjust_text
    import matplotlib.pyplot as plt
                                                                                                                                    
    mtcars = pd.read_csv(
        "https://gist.githubusercontent.com/seankross/a412dfbd88b3db70b74b/raw/5f23f993cd87c283ce766e7ac6b329ee7cc2e1d1/mtcars.csv"
    )
                                                                                                                                    
    def plot_mtcars(adjust=False, force_points=1, *args, **kwargs):
        # plt.figure(figsize=(9, 6))
        plt.scatter(mtcars["wt"], mtcars["mpg"], s=15, c="r", edgecolors=(1, 1, 1, 0))
        texts = []
        for x, y, s in zip(mtcars["wt"], mtcars["mpg"], mtcars["model"]):
            texts.append(plt.text(x, y, s, size=9))
        plt.xlabel("wt")
        plt.ylabel("mpg")
        if adjust:
            plt.title(
                "force_points: %.1f\n adjust_text required %s iterations"
                % (
                    force_points,
                    adjust_text(
                        texts,
                        force_points=force_points,
                        arrowprops=dict(arrowstyle="-", color="k", lw=0.5),
                        **kwargs,
                    ),
                )
            )
        else:
            plt.title("Original")
        return plt
                                                                                                                                    
    fig = plt.figure(figsize=(12, 12))
                                                                                                                                    
    force_points = [0.5, 1, 2, 4]
    for index, k in enumerate(force_points):
        fig.add_subplot(2, 2, index + 1)
        plot_mtcars(adjust=True, force_points=k)
    

    【讨论】:

    • 我认为使用ax.scatter代替plt.scatter时不起作用。无限循环。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-12-08
    • 2021-05-19
    • 2012-05-09
    • 2016-10-09
    • 2020-05-05
    • 1970-01-01
    • 2016-08-20
    相关资源
    最近更新 更多