Графы всегда легче видеть, как они выглядят, чем просто ими оперировать. В этой статье поговорим о визуализации графов GraphFrames с помощью Graphviz. Читайте в этой статье: синтаксис Graphviz, а также отображение графов в Jupyter Notebook.
Немного о пакете Graphviz
Graphviz — это пакет для визуализации графов и сетей. Он включает коллекцию программ-фильтров:
dot
для рисования направленных графов,neato
для рисования ненаправленных графов,circo
для рисования круговых диаграмм и др [1]
Эти программы располагают элементы по-разному, мы далее это ещё увидим. Синтаксис Graphviz достаточно простой. Создавать направленные графы будем с помощью объекта digraph
. Например, граф с врешинами a
и b
и связью, идущей из первой до второй, в Graphviz выражается так:
digraph { a -> b }
Теперь если сохранить это в файле c расширением gv
, например, g.gv
, то его можно компилировать одной из программ-фильтров:
$ dot g.gv -Tpng -o g.png
Здесь мы экспортируем в png
, другие форматы также поддерживаются (дайте команду man dot
и вы увидите доступные форматы в разделе Output formats).
Форма вершин изменяется через атрибут node
, а форма связей (стрелок) — через edge
. Например, определение:
digraph { node [shape=box, fontsize=10]; edge[arrowsize=.4]; a -> b }
— сделает вершины прямоугольными с шрифтом размера 10, а у стрелок будет размер 0.4.
Отдельные связи также можно изменять под себя. Делается это сразу после описания связи в квадратных скобках. Ниже связь a->b
будет иметь подпись с размером 12.
digraph { a -> b [label="This is label", fontsize=12]; b -> c; }
Комментарии такие же, как и в С++: многострочный /* com */
или однострочный // com
.
Рисование графов GraphFrames
Самое простое, что мы можем сделать, так это записать в файл связи графа. Воспользуемся графом GraphFrames, который мы использовали в одной из прошлой статьи:
from graphframes import GraphFrame v = spark.createDataFrame([ (1, 'Anton', 23), (2, 'Anna', 27), (3, 'Andry', 24), (4, 'Alex', 32), (5, 'Boris', 55), (6, 'Vera', 64), ], ['id', 'name', 'age']) e = spark.createDataFrame([ (1, 2, 'friend'), (2, 1, 'friend'), (1, 3, 'friend'), (3, 1, 'friend'), (2, 3, 'friend'), (3, 2, 'friend'), (4, 2, 'follows'), (5, 6, 'follows'), ], ['src', 'dst', 'type']) g = GraphFrame(v, e)
С помощью поиска по шаблону мы можем воссоздать все связи между вершинами, а затем проитерировать по каждому полю (можно было бы просто пройтись по ребрам, поиск по шаблону испольуем для напоминания его синтаксиса):
ab = g.find("(a)-[]->(c)") rows = ab.collect() for row in rows: print(f"{row.a.name} -> {row.c.name}")
Результат:
Boris -> Vera Andry -> Anton Anna -> Anton Anton -> Andry Anna -> Andry Anton -> Anna Andry -> Anna Alex -> Anna
Именно так мы и поступим. Для наглядности снабдим табами \t
каждую связь и добавим перход на новую строку \n
в конце, а затем запишем в файл graph.gv
. Код на Python для записи графа GraphFrames в файл выглядит следующим образом:
with open("graph.gv","w") as f: res = '' for row in rows: res += f"\t{row.a.name} -> {row.c.name};\n" f.write("digraph G {\n" + res + "}")
Результат записи в файл (знак !
показывает, что запускается интерпретатор Bash изнутри Jupyter Notebook):
!cat graph.gv digraph G { Boris -> Vera; Andry -> Anton; Anna -> Anton; Anton -> Andry; Anna -> Andry; Anton -> Anna; Andry -> Anna; Alex -> Anna; }
Этот файл теперь можно откомпилировать с помощью программ-фильтров Graphviz:
!dot graph.gv -Tpng -o dot_graph.png
Как отобразить граф в виде изображения в Jupyter Notebook
В Jupyter Notebook вывести изображения можно разными способами. Мы поговорим о самых простых: через язык разметки Markdown и через модуль IPython.display
.
Чтобы сделать ячейку Markdown-ячейкой, либо нажмите ESC (ячейка должна быть подсвечена синим цветом), а затем нажмите M; либо в контекстном меню Cell -> Cell Type -> Markdown. Затем добавьте изображение в соответсвии с синтаксисом Markdown:
![Подпись к изображению](путь до него) В моем случае: ![Граф](dot_graph.png)
— и запустите ячейку.
Проблем этого метода в том, что после перекомпиляции с тем же названием, отображаться будет старая картинка. Поэтому компилировать нужно с измененным названием, например, dot_graph2.png
. Зато этот метод поддерживает формат SVG, в отличие от следующего.
Модуль IPython.display
содержит класс Image
, с помощью которого выводится изображение:
from IPython.display import Image Image(filename="dot_graph.png")
Используем другие фильтры Graphviz
Если результат визуализации нас смущает, то мы можем прибегнуть к другим фильтрам Graphviz. Фильтр neato
подходит для ненаправленных графов, а circo
для циклических и круговых диаграмм. Мы, тем не менее, посмотрим, как они отобразят наш граф.
!circo graph.gv -Tpng -o circo_graph.png !neato graph.gv -Tpng -o neato_graph.png
Как видим, circo
для нашего конкретного графа справился лучше, чем остальные.
В следующей статье поговорим о Graphviz как о библиотеки Python, проведем визуализацию на примере алгоритма PageRank. Ещё больше подробностей о графах GraphFramesб их визуализации вы узнаете на наших образовательных курсах в лицензированном учебном центре обучения и повышения квалификации руководителей и ИТ-специалистов (менеджеров, архитекторов, инженеров, администраторов, Data Scientist’ов и аналитиков Big Data) в Москве: