Визуализация графов GraphFrames с помощью Graphviz

Графы всегда легче видеть, как они выглядят, чем просто ими оперировать. В этой статье поговорим о визуализации графов 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 (graphviz)
Результат dot

Проблем этого метода в том, что после перекомпиляции с тем же названием, отображаться будет старая картинка. Поэтому компилировать нужно с измененным названием, например, 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)
Результат circo

Как видим, circo для нашего конкретного графа справился лучше, чем остальные.

В следующей статье поговорим о Graphviz как о библиотеки Python, проведем визуализацию на примере алгоритма PageRank. Ещё больше подробностей о графах GraphFramesб их визуализации вы узнаете на наших образовательных курсах в лицензированном учебном центре обучения и повышения квалификации руководителей и ИТ-специалистов (менеджеров, архитекторов, инженеров, администраторов, Data Scientist’ов и аналитиков Big Data) в Москве:

  1. GRAF: Графовые алгоритмы. Бизнес-приложения
  2. Графовые алгоритмы в Apache Spark

Записаться на курс

Смотреть раcписание

Источники
  1. https://graphviz.org/docs/layouts/

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

Поиск по сайту