Визуализация графа взаимосвязей на карте
Теория шести рукопожатий – социологическая теория, согласно которой двух любых людей разделяет всего пять уровней знакомых. Любители отечественного кинематографа могли увидеть её воплощение в одном из популярных новогодних фильмов – люди из разных уголков страны оказывались связаны друг с другом.
527 показов
4.7K открытий
Но что делать, если необходимо в реальной задаче отобразить подобные связи на карте? В таком случае, первым делом на ум приходит GraphMining.
GraphMining (далее –GM) – одно из направлений анализа данных, которое позволяет представить комплексные данные в виде графов.
В Python наиболее популярными библиотеками для GM являются NetworkX, pyviz и graph-tool. С их помощью можно формировать и кастомизировать различные виды графов, а, так же, вычислять множество метрик для анализа. Однако, есть проблема: стандартные библиотеки GM не работают с картами, а библиотеки для работы с картами не формируют графы. На самом деле существует очевидное и простое решение, которое я опишу далее.
В начале – импорт необходимых библиотек:
import folium import pandas as pd import numpy as np
Допустим, что у меня имеется датасет с аггрегированной информацией о переводах от одного клиента другому:
data = pd.read_csv(‘data.csv’, sep = ‘;’)
Id_send – идентификатор отправителя;
Id_recei – идентификатор получателя;
lat_send – широта точки местоположения клиента отправителя;
lon_send – долгота точки местоположения клиента отправителя;
lat_rec – широта точки местоположения клиента получателя;
lon_rec – долгота точки местоположения клиента получателя;
opers_cnt – количество операций по направлению от id_send к id_recei;
opers_sum – сумма операций по направлению от id_send к id_recei.
Предлагаю рассмотреть описание датасета:
В 75% строк количество операций от отправителя к получателю 5 или меньше. Отфильтрую данные, оставив наиболее сильные связи:
data_clean = data[data[‘opers_cnt’]>5]
Далее, необходимо получить набор точек (nodes) с идентификаторами клиентов и их координатами и посчитать общую сумму операций у клиента – отправлений и поступлений:
Нормализую объем операций, данный столбец будет использоваться в качестве параметра размера точки:
nodes[‘opers_sum_scaled’] = (nodes[‘opers_sum’]-nodes[‘opers_sum’].min()) / (nodes[‘opers_sum’].max()-nodes[‘opers_sum’].min())*20
Обогащаю информацией о суммах отправлений и поступлений каждого идентификатора:
id_send_opers = (data_clean.groupby([‘id_send’])[‘opers_sum’].sum() .reset_index() .rename(columns = <'id_send':'id','opers_sum':'send_sum'>)) id_rec_opers = (data_clean.groupby([‘id_recei’])[‘opers_sum’].sum() .reset_index() .rename(columns = )) nodes = nodes.merge(id_send_opers, on =’id’, how = ‘left’) nodes = nodes.merge(id_rec_opers, on =’id’, how = ‘left’) nodes = nodes.fillna(0)'id_send':'id','opers_sum':'send_sum'>
Получил всю необходимую информацию для нанесения точек на карту:
Далее эти точки необходимо соединить – формирую список ребер:
edges = (pd.DataFrame(np.unique(np.array([‘-‘.join(sorted(edge)) for edge in zip(for_edges[‘id_send’],for_edges[‘id_recei’])])))[0] .str.split(‘-‘, expand = True).rename(columns=(<0:'id_x', 1:'id_y'>))) coords_list = nodes[[‘id’,’lat’,’lon’]] edges = edges.merge(coords_list.rename(columns=), on =’id_x’, how = ‘left’) edges = edges.merge(coords_list.rename(columns=), on =’id_y’, how = ‘left’)0:'id_x',>
Для визуализации буду использовать библиотеку folium, создаю карту:
wm = folium.Map(tiles=’cartodbpositron’, prefer_canvas = True, control_scale = True, disable_3d = True)
Наношу слой с точками на карту:
for index, row in nodes.iterrows(): pers_id = row[0] lat = row[1] lon = row[2] wei = row[4] op_sum_total = round(row[3]/1000) op_sum_send = round(row[5]/1000) op_sum_rec = round(row[6]/1000) html = ( «ID: » «Суммарный объем операций: т.р.
» «Отправлено: т.р.
» «Получено: т.р.
» ).format(pers_id=pers_id, op_sum_total=op_sum_total, op_sum_send=op_sum_send, op_sum_rec=op_sum_rec) folium.CircleMarker([lat, lon], radius=1, color=»#571e63″, weight=wei, opacity = 0.6, fill_opacity = 0, popup= folium.Popup(html, max_width = 1000) ).add_to(wm)
Цикл бежит по строчкам из датасета, и на карте отмечается точка в зависимости от координат, указанных в строке. Также динамически определяется текст для pop-up, который будет отображаться при нажатии на точку, и размер ноды. В зависимости от задачи можно менять параметры, например, не задавать динамический размер точек (weight).
5 графов, которые должен знать каждый Data Scientist
Если вы анализируете поведение пользователей, выстраиваете сети или решаете другие динамические задачи, вам не обойтись без структур, которые специально созданы для визуализации взаимоотношений между различными объектами. Речь, разумеется, о графах. Рассказываем про пять разных типов графов: как они применяются в Data Science и как реализованы в Python.

Освойте профессию «Data Scientist»
Графы в Python
Граф состоит из отдельных точек (вершин) и соединяющих их линий (ребер). Вершины могут обозначать любые объекты: пользователей сайта, компьютеры корпоративной сети, населенные пункты на карте. Ребра отображают связи между этими объектами: каких пользователей тот или иной человек добавил в друзья, какие компьютеры объединяются прямым подключением, между какими городами проложены дороги. Такая схема позволяет решать множество прикладных задач, и для каждой из них есть много подходящих графов.
Профессия / 24 месяца
Data Scientist
Дата-сайентисты решают поистине амбициозные задачи. Научитесь создавать искусственный интеллект, обучать нейронные сети, менять мир и при этом хорошо зарабатывать. Программа рассчитана на новичков и плавно введет вас в Data Science.
4 992 ₽/мес 9 983 ₽/мес

Такие структуры гораздо сложнее привычных таблиц баз данных. Представьте себе, что вам нужно создать карту значимых объектов обычного городского района, чтобы понять, где чаще всего бывают его жители. Вам нужно отметить все магазины, школы, МФЦ, и определить вес каждой из точки. Такой код непросто написать с нуля, а полученный результат очень сложно визуализировать в пригодном для работы формате — хаос повседневности формирует очень запутанные связи, даже если мы говорим про район, а не про город или страну. К счастью, Python-программистам не приходится заниматься подготовительной работой. Они могут воспользоваться специальной библиотекой, которая объединяет все необходимые инструменты для создания и анализа графов — NetworkX. Мы также используем этот пакет в следующих примерах.

Станьте дата-сайентистом и решайте амбициозные задачи с помощью нейросетей
1. Разбивка вершин на группы
Анализ связных компонентов (connected components) позволяет вам определить внутренние множества внутри вашего графа. Например, вы можете разбить посетителей интернет-магазина на группы по их предпочтениям или географической принадлежности. Или связать между собой подозрительные транзакции в банковской системе, чтобы предположить, что все эти действия провела одна группа мошенников.
Технически алгоритм объединяет в себе два метода, с помощью которых можно найти кратчайший путь между вершинами графа: поиск по ширине (Breadth First Search, BFS) и поиск по глубине (Depth First Search, DFS). Самые старательные ученики Data Science могут подробнее прочитать о них здесь, а мы сразу перейдем к коду. Допустим, вам нужно распределить города по континентам, используя информацию о расстоянии между ними. Исходный граф выглядит следующим образом:
Сначала мы создаем список граней графа, где расстояния будут использоваться в качестве весов:
Теперь строим граф с помощью Networkx:
Остается применить к полученной структуре алгоритм связных компонент:
Готово — мы получили три группы городов, объединенных по континентальному принципу. Эту модель можно применять для самых разных целей. Если бы вместо городов у нас были, например, идентификаторы пользователей, а вместо расстояний — их траты в разных товарных категориях, алгоритм сгруппировал бы их по предпочтениям.
2. Поиск кратчайшего пути
Вычисление наименьшего расстояния между двумя точками — одна из старейших задач программирования. Один из наилучших алгоритмов для ее решения еще в конце 50-х разработал нидерландский математик Эдсгер Дейкстра (Edsger Dijkstra), причем человечество может благодарить за это открытие… его невесту. Как рассказывал сам Дейкстра, однажды его так утомили походы по магазинам вместе с молодой женой, что во время краткой передышки он задумался — как оптимизировать этот процесс? Буквально за 20 минут он разработал метод, который теперь называется алгоритмом Дейкстры. Метод прост, как все гениальное. Для начала Data Scientist узнает расстояния от начальной точки до каждой точки в пути. Эти значения присваиваются граням графа. Далее алгоритм поочередно проходит по каждой вершине, на каждом шаге определяя наименьшее расстояние до очередной точки. Когда все точки посещены, вы получаете наименьший маршрут. По словам самого математика, метод оказался так прост потому, что у него под рукой не было ни клочка бумаги — все калькуляции приходилось вести в голове. Такой способ не дает углубиться в вычисления, поэтому итоговое решение настолько просто, насколько это вообще возможно. Теперь о том, как алгоритм Дейкстры реализован в Python. Для решения задачи достаточно двух строчек кода:
Мы также можем найти кратчайшее расстояние между всеми парами:
В нашем примере мы определили маршруты между городами, но этот же метод можно использовать и для выстраивания связей между пользователями соцсети, и для планирования оптимального маршрута покупателя в торговом зале, и для любых прочих подобных проблем.
Читайте также 8 причин стать дата-сайентистом в 2023 году
3. Поиск оптимального пути
Всем знакомы головоломки, в которых нужно начертить какую-либо фигуру, не проходя дважды через одну точку. В профессии Data Scientist такие задачи возникают, когда нужно проложить оптимальный путь для кабеля, помочь торговым представителям или мастерам по обслуживанию банкоматов спланировать маршрут, и так далее. В каждом случае помогает алгоритм минимального остовного дерева (Minimum Spanning Tree). Этот термин обозначает подграф, который объединяет ребра с минимально возможной суммой весов. Это не всегда означает, что одна вершина не может иметь больше двух ребер — главное значение имеет итоговая сумма их значений.
Чтобы построить такую структуру, используйте команду: 
4. Рейтинг вершин графа
Еще одна нередкая задача — найти, какие вершины имеют самое важное значение в рамках того или иного явления. Например, поисковики определяют вес разных интернет-страниц, академики считают, какие научные работы чаще всего ссылаются исследователи, социальные сети отслеживают пользователей с наибольшим количеством подписок. На примере социальных сетей мы и рассмотрим метод. Достаем из хранилища данных список пользователей Фейсбук*:
И построим граф:
Теперь посмотрим, у каких пользователей наибольшее влияние. Алгоритм Pagerank умеет сам это делать, опираясь на количество онлайн-друзей:
Чтобы узнать пользователей с наибольшим весом, мы используем этот код:
А чтобы определить «самого крутого парня», можно построить подграф:
*деятельность компании Meta Platforms Inc., которой принадлежит Инстаграм / Фейсбук, запрещена на территории РФ в части реализации данной (-ых) социальной (-ых) сети (-ей) на основании осуществления ею экстремистской деятельности
5. Центральность вершин
Понятие центральности обозначает расстояние какой-либо вершины графа до его центра. Измерение центральности — это еще один способ определить влияние разных участников базы данных. В отличие от предыдущего метода, определение центральности направлено на выявление не самой главной вершины, а локальных «лидеров», которых может быть и пять, и десять. Таким образом Data Scientist может изучать распространение информации в сообществе (определять изначальное сообщение и следить за его распространением через несколько точек входа), отслеживать развитие болезней (строить карту заражений с нулевыми пациентами), контролировать телекоммуникационные и прочие сети. Существует множество метрик центральности. В нашем примере мы расскажем, как найти на графе вершины, которые чаще всего связывают между собой разрозненные группы точек. На практике это может быть перевалочный пункт на географическом маршруте, «бутылочное горлышко» в каких-либо процессах, человек-посредник, который знакомит между собой других людей. В теории графов это и называется степенью посредничества (Betweenness Centrality). Чтобы найти такие вершины, используйте следующий код:
Чем больше диаметр круга, тем больше его степень посредничества. Если устранить эти узлы из системы, большой граф разобьется на несколько малых. Мы рассмотрели всего пять графов, но даже такой краткий экскурс дает представление о том, какой это мощный инструмент Data Science. Изучайте графы, используйте их в своих проектах, и вы получите множество инсайтов о том, как работает наш постоянно меняющийся мир.
Data Scientist
Дата-сайентисты решают поистине амбициозные задачи. Научитесь создавать искусственный интеллект, обучать нейронные сети, менять мир и при этом хорошо зарабатывать. Программа рассчитана на новичков и плавно введет вас в Data Science.
Строим интерактивные графы с помощью NetworkX и Plolty

Думаю, каждый Data Scientist знаком с библиотекой matplotlib, однако это не единственная библиотека для визуализации в python. Бывают такие задачи, когда нужна более “живая” визуализация и нет времени на написание кода для идеального графика с ее помощью. Тогда на ум приходит Plotly (ссылка: https://plotly.com/python/). Plotly — это открытая библиотека, с помощью которой можно быстро строить красивые, а главное интерактивные графики прямо в jupyter-notebook. Данная разработка основывается на JavaScript-библиотеке D3.js. К плюсам данной библиотеки можно отнести связь к одним из удобнейших веб-фреймворком Dash, который появился недавно с помощью создателей Plotly.
Графы, пожалуй, идут в числе первых изображений в matplotlib, которые нуждаются в интерактивности. В нашем случае с помощью интерактивных графов можно получить достаточно много инсайдов из данных. В связку к Plotly мы добавим одну из самых популярных библиотек для работы с графами — NetworkX (ссылка: https://networkx.org/). Данная библиотека создана на python и предназначена для работы с сетевыми структурами и также является бесплатной.
Сочетание Plotly и NetworkX открывает для нас огромные возможности для отличной визуализации графов. В данной статье мы построим визуализацию, на которой будет изображен граф, размеры узла которого будут зависеть от количества его связей, а при наведении курсора на узел будем показывать количеств этих самых связей.
Импортируем нужные библиотеки.
import plotly.graph_objects as go import networkx as nx
Создадим граф, который возвращает случайный граф, вершины которого будут располагаться в единичном кубе. Два узла соединяются ребром, если расстояние между узлами не превышает определенного радиуса.
G = nx.random_geometric_graph(100, 0.125)
Отрисуем самым обычным способом в NetworkX, чтобы сравнить с графом, который отрисуем чуть позже.
nx.draw(G, node_size = 10)

Соберем координаты для дальнейшего построения связей на графике с помощью Plotly. Добавим их на график.
edge_x = [] edge_y = [] for edge in G.edges(): x0, y0 = G.nodes[edge[0]]['pos'] x1, y1 = G.nodes[edge[1]]['pos'] edge_x.append(x0) edge_x.append(x1) edge_x.append(None) edge_y.append(y0) edge_y.append(y1) edge_y.append(None) edge_trace = go.Scatter( x = edge_x, y = edge_y, line = dict(width = 0.5, color = '#888'), hoverinfo = 'none', mode = 'lines')
Проделаем аналогичную операцию для узлов.
node_x = [] node_y = [] for node in G.nodes(): x, y = G.nodes[node]['pos'] node_x.append(x) node_y.append(y) node_trace = go.Scatter( x = node_x, y = node_y, mode = 'markers', hoverinfo = 'text', marker = dict( color = [], size = 10 ), line_width = 2)
Добавим той самой интерактивности нашему графу. При наведении курсора на узел он будет показывать количество связей с другими узлами. Также размер маркера узла будет зависеть от количества связей. Для некоторых задач это позволит найти наиболее “дружелюбные” элементы графа. В целом, можно добавить очень много полезной и ценной информации, допустим, номер узла или инициалы человека, если использовать графы в контексте задач, связанных с взаимодействием людей.

node_adjacencies = [] node_text = [] for node, adjacencies in enumerate(G.adjacency()): node_adjacencies.append(len(adjacencies[1])) node_text.append('Connections: ' + str(len(adjacencies[1]))) node_trace.marker.size = node_adjacencies node_trace.text = node_text
Далее нарисуем полученный граф и посмотрим на результат.
fig = go.Figure(data=[edge_trace, node_trace], layout=go.Layout( title='
Тестовый граф для NTA', titlefont_size = 16, showlegend = False, xaxis=dict(showgrid = False, zeroline = False, showticklabels = False), yaxis=dict(showgrid = False, zeroline = False, showticklabels = False)) ) fig.show()

Данную визуализацию можно сохранить в HTML и изучать уже без использования python, что является огромным плюсом в работе с Plotly, ведь не всегда люди, которым для анализа нужны данные графы, уверенно владеют python. Отдельно стоит отметить верхнюю панель для навигации по графу, она включает в себя все самое нужное: увеличение/уменьшение области просмотра, сохранение участка графика в png, а также выделение определенных областей для более точного исследования.

В конце хочется сказать, что тандем этих двух библиотек позволяет закрыть почти все потребности в повседневной работе с графами.
Отображение графа на 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, >
Вот и все на сегодня. А на наших курсах — полезная теория и много практики:

- Профессия «Белый хакер» (13 месяцев)
- Профессия Fullstack-разработчик на Python (16 месяцев)
Краткий каталог курсов
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-разработчик
- Профессия Веб-разработчик
Мобильная разработка
- Профессия iOS-разработчик
- Профессия Android-разработчик
Java и C#
- Профессия Java-разработчик
- Профессия QA-инженер на JAVA
- Профессия C#-разработчик
- Профессия Разработчик игр на Unity
От основ — в глубину
- Курс «Алгоритмы и структуры данных»
- Профессия C++ разработчик
- Профессия «Белый хакер»
А также
