Я работаю над бумагой, похожей на эту . Мне нужно нарисовать ориентированный граф, похожий на тот, представленный в статье.
Я работаю в основном на Python и изучил различные варианты в matplotlib, seaborn и networkx, чтобы нарисовать аналогичный график, но не смог его решить. Входные данные узлов в моем случае представляют собой несколько списков двоичных чисел фиксированной длины для графа. Здесь я привожу пример двоичного числа длиной 5, но длина может доходить до 15. Так что вы можете себе представить, что количество всех возможных узлов может стать очень большим!
Например, списки, представляющие пути, могут выглядеть так:
all_possible = [f"{i:>05b}" for i in range(2**5)] # Code to generate all possible states
# Paths taken by the graph
path1 = ['start', '01101', '00001', '11000', '11100', '00010', '00100', 'end']
path2 = ['start', '10001', '00111', '01100', '01110', '10000', '11000', '00101', '11011', '01010', 'end']
path3 = ['start', '11100', '01001', '01100', '00011', '10111', '10000', '00001', 'end']
path4 = ['start', '01100', '01110', '11111', '11011', '10001', '11011', '11101', '00000', 'end']
path5 = ['start', '10001', '11100', '01101', '01001', '01000', '00101', '11001', '00101', '11100', 'end']
Вы можете представить все пути, начинающиеся с состояния start
и заканчивающиеся состоянием end
. Между ними они проходят через различные комбинации возможных состояний. Для path1
это похоже на start -> 01101 -> 00001 -> 11000 -> 11100 -> 00010 -> 00100 -> end
Теперь, когда все больше и больше ребер будут сливаться, например, с узлом 11100
, он должен стать больше или, по крайней мере, изменить цвет, чтобы указать большее число. То же самое касается ребер, т. е. если одно и то же ребро пересекается несколько раз, например, ребро 01100 -> 01110
должно быть шире, чтобы представить, сколько раз оно было пройдено.
До сих пор я пробовал разные решения, но наиболее близким было бы использование модуля networkx
:
def draw_complete_graph(all_graph_edges: list, fig_size: tuple = (50, 50), graph_img_name: str = "test_graph"):
G = nx.MultiDiGraph()
plt.figure(figsize=fig_size)
for single_edge in all_graph_edges:
G.add_edge(
single_edge[0],
single_edge[-1],
weight=all_graph_edges.count(single_edge),
width=all_graph_edges.count(single_edge),
)
width_list = list(nx.get_edge_attributes(G, "width").values())
nx.draw(
G,
with_labels=True,
node_shape = "o",
width=width_list,
connectionstyle = "arc3, rad = 0.1",
nodelist=["start", "end"],
)
plt.savefig(graph_img_name)
plt.close()
def get_all_edges(all_paths: list[list]) -> list:
graph_edges = list()
for single_path in all_paths:
graph_edges.append((single_path[0], single_path[1]))
for ix, node in enumerate(graph_edges[1:-2]):
graph_edges.append((node, single_path[ix + 1]))
return graph_edges
if __name__ == "__main__":
edges = get_all_edges([path1, path2, path3, path4, path5])
draw_complete_graph(edges)
Но это не дает мне такого результата.
Моей первой мыслью было, возможно ли вообще сделать что-то близкое к приведенному выше графику с Python3? Если возможно, пожалуйста, помогите мне и укажите, что еще я должен добавить, чтобы приблизиться к требуемому графику.
Спасибо, это отличная идея. Я постараюсь связаться с ними и обновить, если я найду что-нибудь! Также спасибо за правки, я не был уверен, поддерживает ли Seaborn это, поэтому я отметил его. Я надеюсь найти ответ, связанный с networkx.
Кроме того, это очень старая статья, поэтому я не уверен, что то, что они использовали, актуально или доступно сейчас. Но я просто хотел знать, воспроизводим ли этот тип графика с текущими модулями Python. Спасибо!
Вот пример использования netgraph. То же самое может быть достигнуто только с помощью networkx, но тогда вам нужно определить порядок, в котором узлы и ребра хранятся в экземпляре nx.Graph
, поскольку команды рисования networkx принимают только списки, а не словари, для таких свойств, как размер узла, ширина ребра, цвет узла и цвет ребра.
Здесь я использую преобразование log(x+1)
для сопоставления обходов узлов и ребер с размером/цветом узла и шириной/цветом ребра. Возможно, вам придется найти другое преобразование для ваших реальных данных. Руководящий принцип заключается в сопоставлении распределений с высокой асимметрией, таких как количество обходов, с плоскими распределениями, в которых уникальные значения, присутствующие в ваших данных, легко отделимы и не слишком сильно кластеризуются.
#!/usr/bin/env python
"""
Reproduce https://www.pnas.org/doi/10.1073/pnas.0305937101#fig2
"""
import numpy as np
import matplotlib.pyplot as plt
import networkx as nx
from matplotlib.colors import LogNorm
from matplotlib.cm import ScalarMappable
from netgraph import Graph, get_radial_tree_layout # pip install netgraph / conda install -c conda-forge netgraph
if __name__ == '__main__':
g = nx.balanced_tree(3, 3, create_using=nx.DiGraph)
node_positions = get_radial_tree_layout(list(g.edges))
# invert edges as the networkx constructor creates the tree inside-out
h = nx.DiGraph([(v2, v1) for (v1, v2) in g.edges])
# compute node and edge traversals
node_traversals = {node : 0 for node in h}
edge_traversals = {edge : 0 for edge in h.edges}
for node in h:
if node != 0: # center
paths = nx.all_simple_paths(h, node, 0)
for path in paths:
for node in path:
node_traversals[node] += 1
path_edges = list(zip(path[:-1], path[1:]))
for edge in path_edges:
edge_traversals[edge] += 1
fig, (ax1, ax2) = plt.subplots(1, 2)
# option 1: using node size and edge width
node_size = {node : np.log(count+1) for node, count in node_traversals.items()}
edge_width = {edge : np.log(count+1) for edge, count in edge_traversals.items()}
Graph(h, node_layout=node_positions, arrows=True,
node_size=node_size, edge_width=edge_width, ax=ax1)
# option 2: using colors
node_colormap = ScalarMappable(norm=LogNorm(vmin=1, vmax=np.max(list(node_traversals.values()))), cmap='copper')
edge_colormap = ScalarMappable(norm=LogNorm(vmin=1, vmax=np.max(list(edge_traversals.values()))), cmap='copper')
node_color = {node : node_colormap.to_rgba(count) for node, count in node_traversals.items()}
edge_color = {edge : edge_colormap.to_rgba(count) for edge, count in edge_traversals.items()}
Graph(h, node_layout=node_positions, arrows=True,
node_color=node_color, edge_color=edge_color, ax=ax2)
plt.show()
Большое спасибо. Я изучу модуль netgraph
один раз и прочитаю варианты, которые я могу применить к графику, чтобы удовлетворить мои дополнительные ограничения, которые не упомянуты в этом вопросе. Но это решение выглядит многообещающе для проблемы.
В статье есть контактная информация авторов, стоит только спросить у них, как производился сюжет, или хотя бы какие пакеты использовались.