¿Cómo mejorar la colocación de la etiqueta para el gráfico de dispersión matplotlib (código, algoritmo, sugerencias)?

Yo uso matplotlib para trazar un gráfico de dispersión:

enter image description here

Y etiquetar la burbuja con una caja transparente de acuerdo con la punta en matplotlib: cómo anotar el punto en una flecha de dispersión colocada automáticamente?

Aquí está el código:

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) 

y la ttwig resultante: enter image description here

Pero aún hay margen de mejora para reducir la superposición (por ejemplo, el desplazamiento de la caja de tags se fija como (-10,3)). ¿Hay algoritmos que pueden:

  1. cambia dinámicamente el desplazamiento de la caja de tags de acuerdo con la cantidad de gente de su vecindario
  2. coloque dinámicamente el recuadro de etiqueta de forma remota y agregue una línea de flecha entre el cuadro de burbuja y etiqueta
  3. algo cambiar la orientación de la etiqueta
  4. burbuja superpuesta label_box es mejor que label_box ¿overlapping label_box?

Solo quiero hacer que la gráfica sea fácil para los ojos humanos para comprender, por lo que es aceptable cierta superposición, no una restricción tan rígida como sugiere http://en.wikipedia.org/wiki/Automatic_label_placement . Y la cantidad de burbujas dentro del gráfico es menos de 150 la mayor parte del tiempo.

Encuentro que la Force-based label placement http://bl.ocks.org/MoritzStefaner/1377729 es bastante interesante. No sé si hay algún código / paquete python disponible para implementar el algoritmo.

No soy un tipo académico y no estoy buscando una solución óptima, y ​​mis códigos python necesitan etiquetar muchos muchos gráficos, por lo que la velocidad / memoria está dentro del scope de la consideración.

Estoy buscando una solución rápida y efectiva. ¿Alguna ayuda (código, algoritmo, sugerencias, pensamientos) sobre este tema? Gracias.

Es un poco áspero en los bordes (no puedo descifrar cómo escalar las fuerzas relativas de la red de muelles frente a la fuerza de repulsión, y el cuadro delimitador está un poco estropeado), pero este es un comienzo decente:

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

Imagen de muestra

Qué tan bien funciona depende un poco de cómo se agrupan sus datos.

Lo siguiente se basa en la respuesta de tcaswell .

Los métodos de diseño de Networkx como nx.spring_layout reescalan las posiciones para que quepan todas en un cuadrado unitario (por defecto). Incluso la posición de los data_nodes fijos se data_nodes . Por lo tanto, para aplicar la pos a scatter_data original, se debe realizar un cambio de escala y un ajuste incorrecto.

Tenga en cuenta también que nx.spring_layout tiene un parámetro k que controla la distancia óptima entre nodos. A medida que k aumenta, también lo hace la distancia de las anotaciones desde los puntos de datos.

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

con k=0.011 rendimientos

enter image description here y con k=0.008 rendimientos enter image description here

Otra opción que usa mi biblioteca adjustText , escrita especialmente para este propósito ( 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() 

enter image description here

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

enter image description here

No se repele de las burbujas, solo de sus centros y otros textos.