Нарисуйте график с размером узла и шириной ребра, пропорциональными количеству повторений

Я работаю над бумагой, похожей на эту . Мне нужно нарисовать ориентированный граф, похожий на тот, представленный в статье.

Я работаю в основном на 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? Если возможно, пожалуйста, помогите мне и укажите, что еще я должен добавить, чтобы приблизиться к требуемому графику.

В статье есть контактная информация авторов, стоит только спросить у них, как производился сюжет, или хотя бы какие пакеты использовались.

Trenton McKinney 06.04.2023 01:50

Спасибо, это отличная идея. Я постараюсь связаться с ними и обновить, если я найду что-нибудь! Также спасибо за правки, я не был уверен, поддерживает ли Seaborn это, поэтому я отметил его. Я надеюсь найти ответ, связанный с networkx.

Asif Iqbal 06.04.2023 03:07

Кроме того, это очень старая статья, поэтому я не уверен, что то, что они использовали, актуально или доступно сейчас. Но я просто хотел знать, воспроизводим ли этот тип графика с текущими модулями Python. Спасибо!

Asif Iqbal 06.04.2023 03:10
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
3
84
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вот пример использования 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 один раз и прочитаю варианты, которые я могу применить к графику, чтобы удовлетворить мои дополнительные ограничения, которые не упомянуты в этом вопросе. Но это решение выглядит многообещающе для проблемы.

Asif Iqbal 06.04.2023 17:24

Другие вопросы по теме