Наука о графических данных с помощью Python/NetworkX
Опубликовано: 2022-03-11Мы завалены данными. Постоянно расширяющиеся базы данных и электронные таблицы изобилуют скрытой бизнес-аналитикой. Как мы можем анализировать данные и делать выводы, когда их так много? Графики (сети, а не гистограммы) обеспечивают элегантный подход.
Мы часто используем таблицы для общего представления информации. Но графы используют специализированную структуру данных: вместо строки таблицы узел представляет элемент. Ребро соединяет два узла, чтобы указать их отношения.
Эта структура графических данных позволяет нам наблюдать за данными под уникальными углами, поэтому наука о графовых данных используется во всех областях, от молекулярной биологии до социальных наук:
Право на изображение предоставлено: ALBANESE, Federico, et al. «Прогнозирование смены людей с помощью интеллектуального анализа текста и графического машинного обучения в Twitter». (24 августа 2020 г.): arXiv: 2008.10749 [cs.SI]
Так как же разработчики могут использовать науку о графовых данных? Давайте обратимся к наиболее часто используемому языку программирования для обработки данных: Python.
Начало работы с графами «Теория графов» в Python
Разработчикам Python доступно несколько библиотек графических данных, таких как NetworkX, igraph, SNAP и graph-tool. Помимо плюсов и минусов, у них очень похожие интерфейсы для обработки и обработки структур данных графа Python.
Мы будем использовать популярную библиотеку NetworkX. Он прост в установке и использовании и поддерживает алгоритм обнаружения сообщества, который мы будем использовать.
Создать новый график с помощью NetworkX очень просто:
import networkx as nx G = nx.Graph() Но G еще не является графом, так как не имеет узлов и ребер.
Как добавить узлы в график
Мы можем добавить узел в сеть, объединив возвращаемое значение Graph() с .add_node() (или .add_nodes_from() для нескольких узлов в списке). Мы также можем добавить к узлам произвольные характеристики или атрибуты, передав словарь в качестве параметра, как показано на node 4 и node 5 :
G.add_node("node 1") G.add_nodes_from(["node 2", "node 3"]) G.add_nodes_from([("node 4", {"abc": 123}), ("node 5", {"abc": 0})]) print(G.nodes) print(G.nodes["node 4"]["abc"]) # accessed like a dictionaryЭто выведет:
['node 1', 'node 2', 'node 3', 'node 4', 'node 5'] 123Но без ребер между узлами они изолированы, и набор данных ничем не лучше простой таблицы.
Как добавить ребра к графику
Подобно технике для узлов, мы можем использовать .add_edge() с именами двух узлов в качестве параметров (или .add_edges_from() для нескольких ребер в списке) и дополнительно включать словарь атрибутов:
G.add_edge("node 1", "node 2") G.add_edge("node 1", "node 6") G.add_edges_from([("node 1", "node 3"), ("node 3", "node 4")]) G.add_edges_from([("node 1", "node 5", {"weight" : 3}), ("node 2", "node 4", {"weight" : 5})])Библиотека NetworkX поддерживает подобные графы, где каждое ребро может иметь вес. Например, в графе социальной сети, где узлы — это пользователи, а ребра — взаимодействия, вес может означать, сколько взаимодействий происходит между данной парой пользователей — очень важный показатель.
NetworkX перечисляет все ребра при использовании G.edges , но не включает их атрибуты. Если нам нужны атрибуты ребра, мы можем использовать G[node_name] , чтобы получить все, что подключено к узлу, или G[node_name][connected_node_name] , чтобы получить атрибуты определенного ребра:
print(G.nodes) print(G.edges) print(G["node 1"]) print(G["node 1"]["node 5"])Это выведет:
['node 1', 'node 2', 'node 3', 'node 4', 'node 5', 'node 6'] [('node 1', 'node 2'), ('node 1', 'node 6'), ('node 1', 'node 3'), ('node 1', 'node 5'), ('node 2', 'node 4'), ('node 3', 'node 4')] {'node 2': {}, 'node 6': {}, 'node 3': {}, 'node 5': {'weight': 3}} {'weight': 3}Но читать наш первый график таким образом нецелесообразно. К счастью, есть гораздо лучшее представление.
Как создавать изображения из графиков (и взвешенных графиков)
Визуализация графа очень важна: она позволяет нам быстро и четко увидеть отношения между узлами и структурой сети.
Быстрый вызов nx.draw(G) — это все, что нужно:
Давайте сделаем более тяжелые ребра соответственно толще с помощью нашего nx.draw() :
weights = [1 if G[u][v] == {} else G[u][v]['weight'] for u,v in G.edges()] nx.draw(G, width=weights)Мы указали толщину по умолчанию для невесомых краев, как видно из результата:
Наши методы и алгоритмы построения графиков скоро станут более сложными, поэтому следующим шагом будет использование более известного набора данных.
Наука о графических данных с использованием данных из фильма «Звездные войны: Эпизод IV»
Чтобы упростить интерпретацию и понимание наших результатов, мы будем использовать этот набор данных. Узлы представляют важные персонажи, а ребра (которые здесь не взвешиваются) означают совместное появление в сцене.
Примечание. Набор данных взят у Габасовой Э. (2016). Социальная сеть Звездных войн. DOI: https://doi.org/10.5281/zenodo.1411479.
Во-первых, мы визуализируем данные с помощью nx.draw(G_starWars, with_labels = True) :
Персонажи, которые обычно появляются вместе, такие как R2-D2 и C-3PO, кажутся тесно связанными. Напротив, мы видим, что Дарт Вейдер не делится сценами с Оуэном.
Макеты визуализации Python NetworkX
Почему каждый узел находится там же, где и на предыдущем графике?
Это результат алгоритма spring_layout по умолчанию. Он имитирует силу пружины, притягивающей соединенные узлы и отталкивающей разъединенные. Это помогает выделить хорошо связанные узлы, которые оказываются в центре.
В NetworkX есть и другие макеты, которые используют другие критерии для позиционирования узлов, например, circular_layout :
pos = nx.circular_layout(G_starWars) nx.draw(G_starWars, pos=pos, with_labels = True)Результат:

Этот макет нейтрален в том смысле, что расположение узла не зависит от его важности — все узлы представлены одинаково. (Круговая компоновка также может помочь визуализировать отдельные связанные компоненты — подграфы, имеющие путь между любыми двумя узлами, — но здесь весь граф представляет собой один большой связанный компонент.)
Оба макета, которые мы видели, имеют некоторую степень визуального беспорядка, потому что края могут свободно пересекать другие края. Но Камада-Каваи, другой силовой алгоритм, такой как spring_layout , позиционирует узлы так, чтобы минимизировать энергию системы.
Это уменьшает пересечение ребер, но имеет свою цену: он медленнее, чем другие макеты, и поэтому не рекомендуется для графов с большим количеством узлов.
У этого есть специальная функция рисования:
nx.draw_kamada_kawai(G_starWars, with_labels = True)Вместо этого получается эта форма:
Без какого-либо специального вмешательства алгоритм поместил главных персонажей (таких как Люк, Лея и C-3PO) в центр, а менее заметных (таких как Кейми и генерал Додонна) — на границу.
Визуализация графика с определенной компоновкой может дать нам интересные качественные результаты. Тем не менее, количественные результаты являются жизненно важной частью любого анализа данных, поэтому нам необходимо определить некоторые показатели.
Анализ узла: степень и PageRank
Теперь, когда мы можем четко визуализировать нашу сеть, нам может быть интересно охарактеризовать узлы. Существует несколько метрик, описывающих характеристики узлов и, в нашем примере, персонажей.
Одной из основных метрик узла является его степень: сколько у него ребер. Степень узла персонажа « Звездных войн » измеряет, со сколькими другими персонажами они делили сцену.
Функция degree() может вычислить степень персонажа или всей сети:
print(G_starWars.degree["LUKE"]) print(G_starWars.degree)Вывод обеих команд:
15 [('R2-D2', 9), ('CHEWBACCA', 6), ('C-3PO', 10), ('LUKE', 15), ('DARTH VADER', 4), ('CAMIE', 2), ('BIGGS', 8), ('LEIA', 12), ('BERU', 5), ('OWEN', 4), ('OBI-WAN', 7), ('MOTTI', 3), ('TARKIN', 3), ('HAN', 6), ('DODONNA', 3), ('GOLD LEADER', 5), ('WEDGE', 5), ('RED LEADER', 7), ('RED TEN', 2)]Сортировка узлов от высшего к низшему по степени может быть выполнена с помощью одной строки кода:
print(sorted(G_starWars.degree, key=lambda x: x[1], reverse=True))Выход:
[('LUKE', 15), ('LEIA', 12), ('C-3PO', 10), ('R2-D2', 9), ('BIGGS', 8), ('OBI-WAN', 7), ('RED LEADER', 7), ('CHEWBACCA', 6), ('HAN', 6), ('BERU', 5), ('GOLD LEADER', 5), ('WEDGE', 5), ('DARTH VADER', 4), ('OWEN', 4), ('MOTTI', 3), ('TARKIN', 3), ('DODONNA', 3), ('CAMIE', 2), ('RED TEN', 2)]Будучи суммой, степень не принимает во внимание детали отдельных ребер. Соединяется ли данное ребро с изолированным узлом или с узлом, связанным со всей сетью? Алгоритм Google PageRank объединяет эту информацию, чтобы оценить, насколько «важным» является узел в сети.
Метрику PageRank можно интерпретировать как агент, перемещающийся случайным образом от одного узла к другому. Узлы с лучшими связями имеют больше путей, ведущих через них, поэтому агент будет чаще посещать их.
Такие узлы будут иметь более высокий PageRank, который мы можем рассчитать с помощью библиотеки NetworkX:
pageranks = nx.pagerank(G_starWars) # A dictionary print(pageranks["LUKE"]) print(sorted(pageranks, key=lambda x: x[1], reverse=True))Это печатает ранг Люка и наших персонажей, отсортированных по рангу:
0.12100659993223405 ['OWEN', 'LUKE', 'MOTTI', 'DODONNA', 'GOLD LEADER', 'BIGGS', 'CHEWBACCA', 'LEIA', 'BERU', 'WEDGE', 'RED LEADER', 'RED TEN', 'OBI-WAN', 'DARTH VADER', 'CAMIE', 'TARKIN', 'HAN', 'R2-D2', 'C-3PO']Оуэн — персонаж с самым высоким PageRank, превосходящим Люка, имевшего самую высокую степень. Анализ: хотя Оуэн не является персонажем, который разделяет большинство сцен с другими персонажами, он является персонажем, который разделяет сцены со многими важными персонажами, такими как сам Люк, R2-D2 и C-3PO.
Наоборот, C-3PO, персонаж с третьей по величине степенью, имеет самый низкий PageRank. Несмотря на то, что у C-3PO много связей, многие из них связаны с неважными персонажами.
Вывод: использование нескольких метрик может дать более глубокое представление о различных характеристиках узлов графа.
Алгоритмы обнаружения сообщества
При анализе сети может быть важно разделить сообщества : группы узлов, которые тесно связаны друг с другом, но минимально связаны с узлами вне своего сообщества.
Для этого существует несколько алгоритмов. Большинство из них встречаются в неконтролируемых алгоритмах машинного обучения, потому что они присваивают метку узлам без необходимости их предварительной маркировки.
Одним из самых известных является распространение меток . В нем каждый узел начинается с уникальной метки в сообществе из одного. Метки узлов итеративно обновляются в соответствии с большинством меток соседних узлов.
Метки распространяются по сети до тех пор, пока все узлы не разделят метку с большинством своих соседей. Группы узлов, тесно связанных друг с другом, в конечном итоге имеют одинаковую метку.
С библиотекой NetworkX запуск этого алгоритма занимает всего три строки Python:
from networkx.algorithms.community.label_propagation import label_propagation_communities communities = label_propagation_communities(G_starWars) print([community for community in communities])Выход:
[{'R2-D2', 'CAMIE', 'RED TEN', 'RED LEADER', 'OBI-WAN', 'DODONNA', 'LEIA', 'WEDGE', 'HAN', 'OWEN', 'CHEWBACCA', 'GOLD LEADER', 'LUKE', 'BIGGS', 'C-3PO', 'BERU'}, {'DARTH VADER', 'TARKIN', 'MOTTI'}]В этом списке наборов каждый набор представляет сообщество. Читатели, знакомые с фильмом, заметят, что алгоритму удалось идеально отделить «хороших парней» от «плохих парней», осмысленно различая персонажей без использования каких-либо истинных (общественных) ярлыков или метаданных.
Интеллектуальные идеи с использованием науки о графических данных в Python
Мы видели, что начать работу с инструментами обработки графовых данных проще, чем может показаться. Как только мы представим данные в виде графика с помощью библиотеки NetworkX в Python, несколько коротких строк кода могут пролить свет. Мы можем визуализировать наш набор данных, измерять и сравнивать характеристики узлов и узлов кластера разумно с помощью алгоритмов обнаружения сообщества.
Умение извлекать выводы и идеи из сети с помощью Python позволяет разработчикам интегрироваться с инструментами и методологией, которые обычно используются в пайплайнах сервисов обработки данных. От поисковых систем до планирования полетов и электротехники — эти методы легко применимы к широкому спектру контекстов.
Рекомендуемая литература по науке о графических данных
Алгоритмы обнаружения сообщества
Чжао Ян, Рене Альгешеймер и Клаудио Тессоне. «Сравнительный анализ алгоритмов обнаружения сообщества в искусственных сетях». Научные отчеты, 6, вып. 30750 (2016).
Графическое глубокое обучение
Томас Кипф. «Графические сверточные сети». 30 сентября 2016 г.
Приложения науки о графических данных
Альбанезе, Федерико, Леандро Ломбарди, Эстебан Фейерштейн и Пабло Баленсуэла. «Прогнозирование смены людей с помощью интеллектуального анализа текста и графического машинного обучения в Twitter». (24 августа 2020 г.): arXiv: 2008.10749 [cs.SI].
Коэн, Элиор. «Встреча PyData в Тель-Авиве: Node2vec». YouTube. 22 ноября 2018 г. Видео, 21:09. https://www.youtube.com/watch?v=828rZgV9t1g.
