Граф — это форма визуализации, позволяющая показывать и анализировать отношения между сущностями. Например, рисунок ниже показывает вклад редакторов Википедии на различных языках энциклопедии в июле 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,
}
Вот и все на сегодня. А на наших курсах — полезная теория и много практики:
Data Science и Machine Learning
- Профессия Data Scientist
- Профессия Data Analyst
- Курс «Математика для Data Science»
- Курс «Математика и Machine Learning для Data Science»
- Курс по Data Engineering
- Курс «Machine Learning и Deep Learning»
- Курс по Machine Learning
Python, веб-разработка
- Профессия Fullstack-разработчик на Python
- Курс «Python для веб-разработки»
- Профессия Frontend-разработчик
- Профессия Веб-разработчик
Мобильная разработка
Java и C#
- Профессия Java-разработчик
- Профессия QA-инженер на JAVA
- Профессия C#-разработчик
- Профессия Разработчик игр на Unity
От основ — в глубину
А также