40

我使用 matplotlib 绘制散点图:

在此处输入图像描述

并根据matplotlib 的提示使用透明框标记气泡:如何在散点自动放置的箭头上注释点?

这是代码:

if show_annote:
    for i in range(len(x)):
        annote_text = annotes[i][0][0]  # STK_ID
        ax.annotate(annote_text, xy=(x[i], y[i]), xytext=(-10,3),
            textcoords='offset points', ha='center', va='bottom',
            bbox=dict(boxstyle='round,pad=0.2', fc='yellow', alpha=0.2),
            fontproperties=ANNOTE_FONT) 

以及由此产生的情节: 在此处输入图像描述

但是在减少重叠方面仍有改进的空间(例如标签框偏移固定为(-10,3))。是否有算法可以:

  1. 根据邻域的拥挤程度动态改变标签框的偏移量
  2. 远程动态放置标签框并在气泡和标签框之间添加箭头线
  3. 稍微改变标签方向
  4. label_box 重叠气泡比 label_box 重叠 label_box 好?

我只是想让图表易于人眼理解,所以一些重叠是可以的,而不是像http://en.wikipedia.org/wiki/Automatic_label_placement建议的那样严格的约束。并且图表中的气泡数量大部分时间都小于 150。

我发现所谓的Force-based label placement http://bl.ocks.org/MoritzStefaner/1377729 很有趣。我不知道是否有任何 python 代码/包可用于实现该算法。

我不是学术人员,也不是在寻找最佳解决方案,而且我的 python 代码需要标记许多图表,因此速度/内存在考虑范围内。

我正在寻找一种快速有效的解决方案。关于这个主题的任何帮助(代码、算法、提示、想法)?谢谢。

4

4 回答 4

27

以下内容基于tcaswell 的回答

Networkx 布局方法,例如nx.spring_layout重新调整位置,使它们都适合一个单位正方形(默认情况下)。甚至固定的位置data_nodes也被重新缩放。因此,要将 应用于pos原始scatter_data,必须执行 unshifting 和 unscaling 。

另请注意,它nx.spring_layout有一个k控制节点之间最佳距离的参数。随着k增加,注释与数据点的距离也会增加。

import numpy as np
import matplotlib.pyplot as plt
import networkx as nx
np.random.seed(2016)

N = 20
scatter_data = np.random.rand(N, 3)*10


def repel_labels(ax, x, y, labels, k=0.01):
    G = nx.DiGraph()
    data_nodes = []
    init_pos = {}
    for xi, yi, label in zip(x, y, labels):
        data_str = 'data_{0}'.format(label)
        G.add_node(data_str)
        G.add_node(label)
        G.add_edge(label, data_str)
        data_nodes.append(data_str)
        init_pos[data_str] = (xi, yi)
        init_pos[label] = (xi, yi)

    pos = nx.spring_layout(G, pos=init_pos, fixed=data_nodes, k=k)

    # undo spring_layout's rescaling
    pos_after = np.vstack([pos[d] for d in data_nodes])
    pos_before = np.vstack([init_pos[d] for d in data_nodes])
    scale, shift_x = np.polyfit(pos_after[:,0], pos_before[:,0], 1)
    scale, shift_y = np.polyfit(pos_after[:,1], pos_before[:,1], 1)
    shift = np.array([shift_x, shift_y])
    for key, val in pos.items():
        pos[key] = (val*scale) + shift

    for label, data_str in G.edges():
        ax.annotate(label,
                    xy=pos[data_str], xycoords='data',
                    xytext=pos[label], textcoords='data',
                    arrowprops=dict(arrowstyle="->",
                                    shrinkA=0, shrinkB=0,
                                    connectionstyle="arc3", 
                                    color='red'), )
    # expand limits
    all_pos = np.vstack(pos.values())
    x_span, y_span = np.ptp(all_pos, axis=0)
    mins = np.min(all_pos-x_span*0.15, 0)
    maxs = np.max(all_pos+y_span*0.15, 0)
    ax.set_xlim([mins[0], maxs[0]])
    ax.set_ylim([mins[1], maxs[1]])

fig, ax = plt.subplots()
ax.scatter(scatter_data[:, 0], scatter_data[:, 1],
           c=scatter_data[:, 2], s=scatter_data[:, 2] * 150)
labels = ['ano_{}'.format(i) for i in range(N)]
repel_labels(ax, scatter_data[:, 0], scatter_data[:, 1], labels, k=0.008)

plt.show()

k=0.011产量

在此处输入图像描述k=0.008产量 在此处输入图像描述

于 2016-01-09T18:09:45.540 回答
26

使用我的库的另一个选项adjustText,专门为此目的编写(https://github.com/Phlya/adjustText)。

from adjustText import adjust_text
np.random.seed(2016)

N = 50
scatter_data = np.random.rand(N, 3)
fig, ax = plt.subplots()
ax.scatter(scatter_data[:, 0], scatter_data[:, 1],
           c=scatter_data[:, 2], s=scatter_data[:, 2] * 150)
labels = ['ano_{}'.format(i) for i in range(N)]
texts = []
for x, y, text in zip(scatter_data[:, 0], scatter_data[:, 1], labels):
    texts.append(ax.text(x, y, text))
plt.show()

在此处输入图像描述

np.random.seed(2016)

N = 50
scatter_data = np.random.rand(N, 3)
fig, ax = plt.subplots()
ax.scatter(scatter_data[:, 0], scatter_data[:, 1],
           c=scatter_data[:, 2], s=scatter_data[:, 2] * 150)
labels = ['ano_{}'.format(i) for i in range(N)]
texts = []
for x, y, text in zip(scatter_data[:, 0], scatter_data[:, 1], labels):
    texts.append(ax.text(x, y, text))
adjust_text(texts, force_text=0.05, arrowprops=dict(arrowstyle="-|>",
                                                    color='r', alpha=0.5))
plt.show()

在此处输入图像描述

它不会排斥气泡,只会排斥它们的中心和其他文本。

于 2017-01-07T10:08:35.600 回答
22

边缘有点粗糙(我不太清楚如何衡量弹簧网络的相对强度与排斥力的关系,并且边界框有点搞砸了),但这是一个不错的开始:

import networkx as nx

N = 15
scatter_data = rand(3, N)
G=nx.Graph()

data_nodes = []
init_pos = {}
for j, b in enumerate(scatter_data.T):
    x, y, _ = b
    data_str = 'data_{0}'.format(j)
    ano_str = 'ano_{0}'.format(j)
    G.add_node(data_str)
    G.add_node(ano_str)
    G.add_edge(data_str, ano_str)
    data_nodes.append(data_str)
    init_pos[data_str] = (x, y)
    init_pos[ano_str] = (x, y)

pos = nx.spring_layout(G, pos=init_pos, fixed=data_nodes)
ax = gca()
ax.scatter(scatter_data[0], scatter_data[1], c=scatter_data[2], s=scatter_data[2]*150)

for j in range(N):
    data_str = 'data_{0}'.format(j)
    ano_str = 'ano_{0}'.format(j)
    ax.annotate(ano_str,
                xy=pos[data_str], xycoords='data',
                xytext=pos[ano_str], textcoords='data',
                arrowprops=dict(arrowstyle="->",
                                connectionstyle="arc3"))

all_pos = np.vstack(pos.values())
mins = np.min(all_pos, 0)
maxs = np.max(all_pos, 0)

ax.set_xlim([mins[0], maxs[0]])
ax.set_ylim([mins[1], maxs[1]])

draw()

示例图像

它的工作效果在某种程度上取决于您的数据是如何聚集的。

于 2013-04-07T06:35:53.393 回答
0

我们可以为此使用 plotly。但是如果有很多数据,我们不能帮助正确放置重叠。相反,我们可以放大和缩小。

import plotly.express as px
df = px.data.tips()

df = px.data.gapminder().query("year==2007 and continent=='Americas'")


fig = px.scatter(df, x="gdpPercap", y="lifeExp", text="country", log_x=True, size_max=100, color="lifeExp",
                 title="Life Expectency")
fig.update_traces(textposition='top center')

fig.show()

输出:

在此处输入图像描述

于 2020-07-14T21:16:38.827 回答