如何在自动放置的散点箭头上注释点

发布于 2024-12-31 21:41:31 字数 332 浏览 5 评论 0 原文

如果我用 matplotlib 制作散点图:

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

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

if I make a scatter plot with matplotlib:

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

I'd like to annotate a given point (x, y) with an arrow pointing to it and a label. I know this can be done with annotate, but I'd like the arrow and its label to be placed "optimally" in such a way that if it's possible (given the current axis scales/limits) that the arrow and the label do not overlap with the other points. eg if you wanted to label an outlier point. is there a way to do this? it doesn't have to be perfect, but just an intelligent placement of the arrow/label, given only the (x,y) coordinates of the point to be labeled. thanks.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

笑,眼淚并存 2025-01-07 21:41:31

基本上,不,没有。

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

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

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

例如

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

在此处输入图像描述

Basically, no, there isn't.

Layout engines that handle placing map labels similar to this are surprisingly complex and beyond the scope of matplotlib. (Bounding box intersections are actually a rather poor way of deciding where to place labels. What's the point in writing a ton of code for something that will only work in one case out of 1000?)

Other than that, due to the amount of complex text rendering that matplotlib does (e.g. latex), it's impossible to determine the extent of text without fully rendering it first (which is rather slow).

However, in many cases, you'll find that using a transparent box behind your label placed with annotate is a suitable workaround.

E.g.

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

enter image description here

昔日梦未散 2025-01-07 21:41:31

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

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

聚苯乙烯
它使用边界框交叉点,但我想说相当成功!

Use adjustText (full disclosure, I wrote it).

Let's label the first 10 points. The only parameter I changed was lowering the force of repelling from the points, since there is so many of them and we want the algorithm to take a bit more time and place the annotations more carefully.

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

enter image description here
It's not ideal, but the points are really dense here and sometimes there is no way to place the text near to its target without overlapping any of them. But it's all automatic and easy to use, and also doesn't let labels overlap each other.

PS
It uses bounding box intersections, but rather successfully I'd say!

川水往事 2025-01-07 21:41:31

另一个使用基于 Phlya 包的示例/latest/Examples.html#Now-a-classical-mtcars-dataset;-example-from-ggrepel-package-for-R" rel="nofollow noreferrer">调整Text_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)

在此处输入图像描述

Another example using awesome Phlya's package based on 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)

enter image description here

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文