Как создать дэшборд NeoDash для графовой базы данных Neo4j

Neo4j NeoDash примеры, Cypher-запросы к Neo4j , дэшборд NeoDash, анализ и визуализация графов, аналитика больших данных с графами, задачи на графах в бизнесе пример, Школа Больших Данных Учебный Центр Коммерсант

Создаем визуализации Cypher-запросов к своему графу в графовой базе данных Neo4j с помощью дэшборда NeoDash на примере анализа финансовых транзакций в банке.

Python-генерация графа в Neo4j с фейковыми данными

Поскольку NoSQL-СУБД Neo4j отлично подходит для задач графовой аналитики больших данных благодаря своей нативно графовой модели хранения данных, ее можно использовать для анализа связей между узлами и визуализации его результатов. Наглядно показать результаты выполнения запросов к графу позволяет веб-приложение NeoDash – инструмент для создания интерактивных дэшбордов с диаграммами, таблицами и графами. Он изначально работает с Cypher-запросами к Neo4j и оптимизирован для обработки узлов и связей. Протестировать возможности NeoDash я решила на примере анализа финансовых транзакций в банке, т.е. денежных переводов, совершаемых в какую-то дату между юридическими и физическими лицами.

Чтобы наполнить базу данных записями для тестирования, написала небольшой Python-скрипт генерации узлов и связей между ними. Узлы графа промаркированы метками Company для обозначения юрлиц и Person для физлиц. Каждый узел имеет название (в случае юрлиц) и имя (для физлица), а также номер расчетного счета. В рассматриваемом примере этих двух свойств для узлов будет достаточно. Связь между узлами означает совершенный денежный перевод, т.е. транзакцию на конкретную сумму и в определенную дату. Это два свойства связи. Как и в прошлый раз, для наполнения графа случайными значениями была использована Python-библиотека Faker, а сам скрипт генерации графа, запущенный в Google Colab, выглядит так:

##############################################ячейка №1 в Google Colab##############################################
#установка библиотек
!pip install neo4j
!pip install neo4jupyter
!pip install faker

#импорт модулей
from neo4j import GraphDatabase
import logging
from neo4j.exceptions import ServiceUnavailable
import pandas as pd
import networkx as nx
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import random
from random import randrange
from datetime import datetime, timedelta

# Импорт модуля faker
from faker import Faker
from faker.providers.address.ru_RU import Provider

#########################ячейка №2 в Google Colab############################
# Создание объекта Faker с использованием провайдера адресов для России
fake = Faker('ru_RU')
fake.add_provider(Provider)

#задаем крайние суммы транзакций
min_sum=500
max_sum=5000

#задаем даты перевода
start_date = pd.Timestamp(year=2024, month=1, day=1)
end_date = pd.Timestamp(year=2024, month=3, day=1)

def random_date(start_date, end_date):
    delta = end_date - start_date
    random_days = np.random.randint(0, delta.days + 1)
    return (start_date + timedelta(days=random_days)).date()

# Определение класса
class App:

    # Конструктор класса
    def __init__(self, uri, user, password):
        # Инициализация драйвера Neo4j с переданными параметрами
        self.driver = GraphDatabase.driver(uri, auth=(user, password))

    # Метод класса для закрытия соединения с базой данных Neo4j
    def close(self):
        # Не забудьте закрыть соединение драйвера, когда закончите работу с ним
        self.driver.close()

    # Метод класса для создания графа
    def create_graph(self):
        with self.driver.session() as session:
            # Выполнение запроса на создание графа
            session.execute_write(self._create_graph)

    # Статический метод, который содержит запрос на создание графа
    @staticmethod
    def _create_graph(tx):
        tx.run(f"""
        CREATE (A:Company {{ name: '{fake.company()}', checking_account: '{fake.checking_account()}' }})
        CREATE (B:Person {{ name: '{fake.name()}', checking_account: '{fake.checking_account()}' }})
        CREATE (C:Company {{ name: '{fake.company()}', checking_account: '{fake.checking_account()}' }})
        CREATE (D:Person {{ name: '{fake.name()}', checking_account: '{fake.checking_account()}' }})
        CREATE (E:Company {{ name: '{fake.company()}', checking_account: '{fake.checking_account()}' }})
        CREATE (F:Person {{ name: '{fake.name()}', checking_account: '{fake.checking_account()}' }})
        CREATE (G:Person {{ name: '{fake.name()}', checking_account: '{fake.checking_account()}' }})
        CREATE (H:Company {{ name: '{fake.company()}', checking_account: '{fake.checking_account()}' }})
        CREATE (I:Person {{ name: '{fake.name()}', checking_account: '{fake.checking_account()}'}})
        CREATE (K:Company {{ name: '{fake.company()}', checking_account: '{fake.checking_account()}' }})
        CREATE (L:Company {{ name: '{fake.company()}', checking_account: '{fake.checking_account()}' }})
        CREATE (M:Company {{ name: '{fake.company()}', checking_account: '{fake.checking_account()}' }})
        CREATE (N:Person {{ name: '{fake.name()}', checking_account: '{fake.checking_account()}' }})
        CREATE (A)-[:transaction {{ summa: {random.randint(min_sum, max_sum)}, date: '{random_date(start_date, end_date)}'}}]->(B)
        CREATE (B)-[:transaction {{ summa: {random.randint(min_sum, max_sum)}, date: '{random_date(start_date, end_date)}'}}]->(C)
        CREATE (C)-[:transaction {{ summa: {random.randint(min_sum, max_sum)}, date: '{random_date(start_date, end_date)}'}}]->(I)
        CREATE (A)-[:transaction {{ summa: {random.randint(min_sum, max_sum)}, date: '{random_date(start_date, end_date)}'}}]->(D)
        CREATE (B)-[:transaction {{ summa: {random.randint(min_sum, max_sum)}, date: '{random_date(start_date, end_date)}'}}]->(D)
        CREATE (D)-[:transaction {{ summa: {random.randint(min_sum, max_sum)}, date: '{random_date(start_date, end_date)}'}}]->(E)
        CREATE (D)-[:transaction {{ summa: {random.randint(min_sum, max_sum)}, date: '{random_date(start_date, end_date)}'}}]->(B)
        CREATE (E)-[:transaction {{ summa: {random.randint(min_sum, max_sum)}, date: '{random_date(start_date, end_date)}'}}]->(I)
        CREATE (B)-[:transaction {{ summa: {random.randint(min_sum, max_sum)}, date: '{random_date(start_date, end_date)}'}}]->(F)
        CREATE (F)-[:transaction {{ summa: {random.randint(min_sum, max_sum)}, date: '{random_date(start_date, end_date)}'}}]->(G)
        CREATE (F)-[:transaction {{ summa: {random.randint(min_sum, max_sum)}, date: '{random_date(start_date, end_date)}'}}]->(E)
        CREATE (G)-[:transaction {{ summa: {random.randint(min_sum, max_sum)}, date: '{random_date(start_date, end_date)}'}}]->(H)
        CREATE (H)-[:transaction {{ summa: {random.randint(min_sum, max_sum)}, date: '{random_date(start_date, end_date)}'}}]->(I)
        CREATE (I)-[:transaction {{ summa: {random.randint(min_sum, max_sum)}, date: '{random_date(start_date, end_date)}'}}]->(A)
        CREATE (K)-[:transaction {{ summa: {random.randint(min_sum, max_sum)}, date:  '{random_date(start_date, end_date)}'}}]->(A)
        CREATE (K)-[:transaction {{ summa: {random.randint(min_sum, max_sum)}, date: '{random_date(start_date, end_date)}'}}]->(D)
        CREATE (K)-[:transaction {{ summa: {random.randint(min_sum, max_sum)}, date:  '{random_date(start_date, end_date)}'}}]->(H)
        CREATE (N)-[:transaction {{ summa: {random.randint(min_sum, max_sum)}, date: '{random_date(start_date, end_date)}'}}]->(E)
        CREATE (N)-[:transaction {{ summa: {random.randint(min_sum, max_sum)}, date: '{random_date(start_date, end_date)}'}}]->(B)
        CREATE (M)-[:transaction {{ summa: {random.randint(min_sum, max_sum)}, date: '{random_date(start_date, end_date)}'}}]->(C)
        CREATE (E)-[:transaction {{ summa: {random.randint(min_sum, max_sum)}, date: '{random_date(start_date, end_date)}'}}]->(M)
        CREATE (L)-[:transaction {{ summa: {random.randint(min_sum, max_sum)}, date: '{random_date(start_date, end_date)}'}}]->(G)
        """)

# Основная программа
if __name__ == "__main__":
    # Aura запросы используют зашифрованное соединение с использованием схемы URI "neo4j+s"
    uri = "neo4j+s://my-instance.databases.neo4j.io"
    user = "my-username"
    password = "my-password"
    # Создание экземпляра класса, передача параметров для инициализации драйвера Neo4j
    app = App(uri, user, password)
    # Вызов метода класса для создания графа
    app.create_graph()
    # Закрытие соединения с базой данных Neo4j
    app.close()

После выполнения этого скрипта созданный граф появится в БД Neo4j, которая у меня развернута на бесплатном тарифе облачной платформы AuraDB.

Граф в Neo4j
Граф в Neo4j

Получив граф в Neo4j, далее создадим к нему интерактивный дашборд в NeoDash.

Создание дашборда в NeoDash

Чтобы создать дэшборд в NeoDash, который будет визуализировать результаты анализа данных в существующей базе Neo4j, надо сперва подключиться к ней, введя учетные данные в GUI инструмента интерактивных панелей.

Подключение к Neo4j из Neodash
Подключение к Neo4j из Neodash

Затем можно создавать свою интерактивную панель, настроив отображение для каждого запроса. Например, я решила сперва показать весь граф, потом отобразить переводы между конкретными двумя вершинами и те переводы, сумма транзакции которых больше 1000. Для этого в каждом элементе панели следует написать Cypher-запрос.

Например, для отображения всего графа транзакций запрос будет таким:

MATCH p=()-[:transaction]->() RETURN p;

Для отображения суммы переводов между вершиной A и вершиной I, таким:

MATCH path = (A)-[t:transaction]->(I)
RETURN [node IN nodes(path) | node.name] AS Перевод,
       reduce(sum = 0, r IN relationships(path) | sum + r.summa) AS СУММА
ORDER BY СУММА DESC
LIMIT 10

А показать переводы на сумму более 1000 рублей поможет такой Cypher-запрос:

MATCH 
p=()-[t:transaction]->() 
WHERE t.summa>100
RETURN p;

Также я отдельно вывела графы транзакций между всеми физическими лицами, всеми юрлицами, и переводы от физлица к юрлицу и наоборот.

Дэшборд Neodash
Дэшборд Neodash

Каждый элемент дэшборда можно настроить по своему вкусу, выбрав отображение в виде графа, таблицы или диаграммы. Также можно выбрать цветовую схему, задать ширину столбцов для таблицы, указать название элемента панели и определить возможность сохранения изображений и CSV-файлов, созданных в результате выполнения Cypher-запросов к Neo4j. Помимо сохранения созданного дэшборда в базе данных также можно его загрузить на локальную машину в виде JSON-файла. Например, мой дэшборд в виде JSON выглядит так:

{
  "uuid": "d75b989b-6a98-4445-b6c9-9094a6dea1fe",
  "title": "Транзакции в Банке",
  "version": "2.4",
  "settings": {
    "pagenumber": 0,
    "editable": true,
    "fullscreenEnabled": false,
    "parameters": {},
    "theme": "dark",
    "downloadImageEnabled": true
  },
  "pages": [
    {
      "title": "Переводы",
      "reports": [
        {
          "id": "3a5f4d8f-8a43-44f8-b39e-77f3436770b3",
          "title": "Весь граф",
          "query": "MATCH p=()-[:transaction]->() RETURN p;\n\n\n",
          "width": 8,
          "height": 4,
          "x": 0,
          "y": 0,
          "type": "graph",
          "selection": {
            "Company": "name",
            "Person": "name"
          },
          "settings": {},
          "schema": [
            [
              "Company",
              "checking_account",
              "name"
            ],
            [
              "Person",
              "checking_account",
              "name"
            ]
          ]
        },
        {
          "id": "33a63ad7-939d-4832-a80d-cd3c5a882806",
          "title": "Переводы >1000",
          "query": "MATCH \np=()-[t:transaction]->() \nWHERE t.summa>100\nRETURN p;\n\n\n",
          "width": 6,
          "height": 4,
          "x": 18,
          "y": 0,
          "type": "graph",
          "selection": {
            "Company": "name",
            "Person": "name"
          },
          "settings": {},
          "schema": [
            [
              "Company",
              "checking_account",
              "name"
            ],
            [
              "Person",
              "checking_account",
              "name"
            ]
          ]
        },
        {
          "id": "d47a5255-f4af-4d6d-a3a7-17422be5d46b",
          "title": "Между физлицами",
          "query": "MATCH p=(:Person)-[t:transaction]->(:Person) \nRETURN p;",
          "width": 6,
          "height": 4,
          "x": 0,
          "y": 4,
          "type": "graph",
          "selection": {
            "Person": "name"
          },
          "settings": {},
          "schema": [
            [
              "Person",
              "checking_account",
              "name"
            ]
          ]
        },
        {
          "id": "d24a72eb-1204-45fe-a3f1-18dcd5dc9ebc",
          "title": "Между юрлицами",
          "query": "MATCH p=(:Company)-[t:transaction]->(:Company) \nRETURN p;\n\n\n",
          "width": 6,
          "height": 4,
          "x": 6,
          "y": 4,
          "type": "graph",
          "selection": {
            "Company": "name"
          },
          "settings": {},
          "schema": [
            [
              "Company",
              "checking_account",
              "name"
            ]
          ]
        },
        {
          "id": "cb3ac16d-8ff1-49b4-9080-111063e5d8e2",
          "title": "Юрлица->Физлицам",
          "query": "MATCH p=(:Company)-[t:transaction]->(:Person) \nRETURN p;",
          "width": 6,
          "height": 4,
          "x": 12,
          "y": 4,
          "type": "graph",
          "selection": {
            "Company": "name",
            "Person": "name"
          },
          "settings": {},
          "schema": [
            [
              "Company",
              "checking_account",
              "name"
            ],
            [
              "Person",
              "checking_account",
              "name"
            ]
          ]
        },
        {
          "id": "84c8f95b-4b5a-488e-a052-43fc136e1f0c",
          "title": "Физлица->Юрлицам",
          "query": "MATCH p=(:Person)-[t:transaction]->(:Company) \nRETURN p;\n\n\n",
          "width": 6,
          "height": 4,
          "x": 18,
          "y": 4,
          "type": "graph",
          "selection": {
            "Person": "name",
            "Company": "name"
          },
          "settings": {},
          "schema": [
            [
              "Person",
              "checking_account",
              "name"
            ],
            [
              "Company",
              "checking_account",
              "name"
            ]
          ]
        },
        {
          "id": "feb6d96d-1adf-4417-86f2-d8a4adb184a3",
          "title": "Переводы",
          "query": "MATCH path = (A)-[t:transaction]->(I)\nRETURN [node IN nodes(path) | node.name] AS Перевод,\n       reduce(sum = 0, r IN relationships(path) | sum + r.summa) AS СУММА\nORDER BY СУММА DESC\nLIMIT 10",
          "width": 10,
          "height": 4,
          "x": 8,
          "y": 0,
          "type": "table",
          "selection": {},
          "settings": {
            "compact": true,
            "transposed": false,
            "columnWidthsType": "Fixed (px)",
            "columnWidths": "[490, 70]",
            "allowDownload": true
          },
          "schema": []
        }
      ]
    }
  ],
  "parameters": {},
  "extensions": {
    "active": true,
    "activeReducers": [],
    "access-control-management": {
      "active": true
    },
    "forms": {
      "active": true
    },
    "actions": {
      "active": true
    },
    "styling": {
      "active": true
    },
    "advanced-charts": {
      "active": true
    }
  }
}

Подводя итог практическому знакомству с Neodash, отмечу, что этот инструмент довольно удобен для визуализации результатов запросов к графам. Однако, чтобы расшарить созданную интерактивную панель, предоставив доступ к ней сторонним пользователям, надо выделить этим пользователям учетные данные и разрешения (гранты) на запросы к БД. Как это сделать, расскажу в следующий раз. А здесь вы узнаете про тонкости выполнения запросов Cypher и операторы их анализа.

Научиться работать с графовыми СУБД и строить дэшборды к ним для аналитики больших данных вы сможете на специализированных курсах нашего лицензированного учебного центра обучения и повышения квалификации для разработчиков, менеджеров, архитекторов, инженеров, администраторов, Data Scientist’ов и аналитиков Big Data в Москве:

Я даю свое согласие на обработку персональных данных и соглашаюсь с политикой конфиденциальности.
Контакты авторизированного учебного центра
«Школа Больших Данных»
Адрес:
127576, г. Москва, м. Алтуфьево, Илимская ул. 5 корпус 2, офис 319, БЦ «Бизнес-Депо»
Часы работы:
Понедельник - Пятница: 09.00 – 18.00
Остались вопросы?
Звоните нам +7 (495) 414-11-21 или отправьте сообщение через контактную форму. Также вы можете найти ответы на ваши вопросы в нашем сборнике часто задаваемых вопросов.
Оставьте сообщение, и мы перезвоним вам в течение рабочего дня
Я даю свое согласие на обработку персональных данных и соглашаюсь с политикой конфиденциальности.
Или напишите нам в соц.сетях
Поиск по сайту