Отображение графа на Python с networkx

от автора

Граф — это форма визуализации, позволяющая показывать и анализировать отношения между сущностями. Например, рисунок ниже показывает вклад редакторов Википедии на различных языках энциклопедии в июле 2013 года:


Можно сделать несколько наблюдений:

  • Английский (en) — основной язык, на который переводятся все остальные языки; в то же время многие англоязычные материалы переводятся на другие языки.
  • Китайский (zh) переводится на японский (ja), но не наоборот.
  • И китайский, и японский материалы переведены на английский, и наоборот.

Я же расскажу о том, как для отображения графов использовать пакет networkx.

Установка networkx

Чтобы установить этот пакет, используйте команду pip:

!pip install networkx

Терминология

Прежде чем начать отрисовку графа, полезно знать некоторые основы.
На рисунке ниже показывается направленный граф, также известный как диграф, ребра которого имеют обозначенные стрелками направления.

  • Узел — фундаментальный элемент графа, общеизвестный под названием вершина.
  • Ребро — соединение узлов графа.
  • Неориентированный граф не имеет направления между узлами, то есть не имеет стрелок, а его ребра двунаправлены.

Создание графа

Давайте шаг за шагом создадим граф.

Во-первых, создадим объект класса networkx.classes.graph.Graph:

import networkx as nx  G = nx.Graph() print(G)  # Graph with 0 nodes and 0 edges

Класс nx.Craph() создает неориентированный граф. Если захочется создать ориентированный, используйте класс nx.DiGraph(directed=True), который возвращает объект networkx.classes.digraph.DiGraph.

В этой статье поговорим об ориентированных графах.

Добавим узлы

Фрагмент кода ниже добавляет три узла без ребер:

G.add_node("Singapore") G.add_node("San Francisco") G.add_node("Tokyo")  print(G) # Graph with 3 nodes and 0 edges

Помимо функции add_node() для добавления индивидуальных узлов, чтобы добавить множество узлов, можно воспользоваться функцией add_nodes_from():

G.add_nodes_from(["Riga", "Copenhagen"]) print(G) # Graph with 5 nodes and 0 edges

Сейчас у графа 5 узлов.

Добавим ребра

Теперь, когда узлы определены, определим ребра, чтобы соединить их:

G.add_edge("Singapore","San Francisco") G.add_edge("San Francisco","Tokyo") G.add_edges_from(     [         ("Riga","Copenhagen"),         ("Copenhagen","Singapore"),         ("Singapore","Tokyo"),         ("Riga","San Francisco"),         ("San Francisco","Singapore"),     ] )  print(G) # Graph with 5 nodes and 6 edges

Как и узлы, ребра можно добавлять по одному, при помощи add_edge(), или группами — при помощи add_edges_from() со списком кортежей, представляющих каждый узел.

Рисуем граф

Я покажу основы отображения сетевых графов при помощи пакета networkx. Начнем:

nx.draw(G)

Вы увидите что-то такое:

Запомните, что граф будет другим при каждом вызове draw():

Вот другое изображение того же графа:

Отображение меток

Само собой, граф без меток не очень полезен, если вообще полезен, поэтому давайте отрисуем метки:

nx.draw(G, with_labels = True)

Функция draw() с параметром with_labels — эквивалент функций в списке ниже:

  • nx.draw_networkx_nodes() — рисует все узлы графа;
  • nx.draw_networkx_labels() — рисует метки на каждом узле;
  • nx.draw_networkx_edges() — рисует ребра, соединяющие узлы.

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

И теперь мы видим метку каждого узла:

Применение макетов

Помните, что функция draw() каждый раз использует разные макеты? Так вот, для графа можно указать конкретный макет:

pos = nx.circular_layout(G) nx.draw(G, pos, with_labels = True)

Узлы упорядочены так, что по ним можно очертить круг:

Кроме того, график с круговой компоновкой можно нарисовать с помощью nx.draw_circular(), а не nx.draw():

nx.draw_circular(G, with_labels = True)

Можно попробовать другие макеты:

  • nx.draw_kamada_kawai(G, with_labels = True);
  • nx.draw_planar(G, with_labels = True);
  • nx.draw_random(G, with_labels = True);
  • nx.draw_spectral(G, with_labels = True);
  • nx.draw_spring(G, with_labels = True);
  • nx.draw_shell(G, with_labels = True);

Разметка ребер

Ребра можно отметить при помощи nx.draw_networkx_edge_labels(). Фрагмент кода ниже размечает два ребра трех узлов:

pos = nx.circular_layout(G) nx.draw(G, pos, with_labels = True) nx.draw_networkx_edge_labels(     G,      pos,     edge_labels={         ("Singapore","Tokyo"): '2 flights daily',          ("San Francisco","Singapore"): '5 flights daily',     },     font_color='red' )

Ориентированный граф

Иногда полезно построить ориентированный граф. В нашем примере ребра могут представлять рейсы между двумя городами. Ориентированный граф позволяет увидеть, какие рейсы идут из одного города в другой. Следующий фрагмент кода показывает наш пример в виде ориентированного графа:

import networkx as nx  #---directed graph--- G = nx.DiGraph(directed=True)  # add nodes G.add_node("Singapore") G.add_node("San Francisco") G.add_node("Tokyo") G.add_nodes_from(["Riga", "Copenhagen"])  # add edges G.add_edge("Singapore","San Francisco") G.add_edge("San Francisco","Tokyo") G.add_edges_from(     [         ("Riga","Copenhagen"),         ("Copenhagen","Singapore"),         ("Singapore","Tokyo"),         ("Riga","San Francisco"),         ("San Francisco","Singapore"),     ] ) # set layout pos = nx.circular_layout(G)  # draw graph nx.draw(G, pos, with_labels = True)  # draw edge labels nx.draw_networkx_edge_labels(     G, pos,     edge_labels={         ("Singapore","Tokyo"): '2 flights daily',          ("San Francisco","Singapore"): '5 flights daily',     },     font_color='red' )

Теперь вы видите, что есть рейсы из Сингапура в Сан-Франциско и наоборот; с другой стороны, есть рейсы из Риги в Сан-Франциско, но не наоборот:

Настройка узлов

По умолчанию узлы имеют синий цвет и довольно маленький размер. Настроить узлы и цвет ребра можно, передав словарь в функцию draw():

options = {     'node_color': 'yellow',     # color of node     'node_size': 3500,          # size of node     'width': 1,                 # line width of edges     'arrowstyle': '-|>',        # array style for directed graph     'arrowsize': 18,            # size of arrow     'edge_color':'blue',        # edge color }  nx.draw(G, pos, with_labels = True, arrows=True, **options)

Сейчас узлы желтые и они больше, а ребра синие:

Очерчивание узлов

Если вы хотите обозначить узлы, вам нужно сделать это вручную, используя matplotlib. Следующий фрагмент кода задает размер рисунка 10 на 10 дюймов (ширина и высота), а затем функцией set_edgecolor() рисует контур каждого узла:

pos = nx.circular_layout(G)  options = {     'node_color': 'yellow',     'node_size': 8500,     'width': 1,     'arrowstyle': '-|>',     'arrowsize': 18, }  nx.draw(G, pos, with_labels = True, arrows=True, **options)  ax = plt.gca() ax.collections[0].set_edgecolor("#000000")

Теперь каждый узел обведен черным:

Если не установить размер рисунка, граф будет выглядеть так:

Раскрашивание узлов

Чтобы раскрасить каждый узел разными цветами, можно определить цветовую палитру, такую как в bokeh, и установить значение ключу словаря node_color, затем передав его в draw():

from networkx import * import matplotlib.pyplot as plt from bokeh.palettes import Spectral  plt.figure(figsize=(8, 8))  pos = nx.circular_layout(G)  options = {     'node_color': Spectral[5],  # first 5 colors from the Spectral palette     'node_size': 8500,     'width': 1,     'arrowstyle': '-|>',     'arrowsize': 18, }  nx.draw(G, pos=pos, with_labels = True, arrows=True, **options)  ax = plt.gca() ax.collections[0].set_edgecolor("#000000")

И теперь узлы графа раскрашены разными цветами:

Если захочется указать свой цвет, установите его вручную:

options = {     'node_color': ['yellow','magenta','lightblue','lightgreen','pink'],     'node_size': 8500,     'width': 1,     'arrowstyle': '-|>',     'arrowsize': 18, }

Вот и все на сегодня. А на наших курсах — полезная теория и много практики:


ссылка на оригинал статьи https://habr.com/ru/company/skillfactory/blog/721838/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *