В рамках практического обучения аналитиков данных и специалистов по Data Science реальным задачам современных бизнес-приложений, сегодня разберем актуальную и острую для многих стран тему по промышленному использованию природных ресурсов в современных непростых условиях. Строим граф европейской газотранспортной системы в Neo4j.
Создание графа европейской газотранспортной системы в Neo4j
Российский природный газ поступает в страны Европы по трубопроводам и составляет около трети всего газа, используемого на той территории. Этот российский энергоресурс играет значительную роль в энергетическом балансе европейских стран. Газотранспортные сети состоят из различных компонентов: трубопроводы, компрессоры, станции и пр. Отчет Ольденбургского Института сетевых энергетических систем DLR за 2020 год, опубликованный в результате 3-хлетнего проекта SciGRID_gas, финансируемого Федеральным министерством экономики и энергетики Германии, содержит следующие сведения о компонентах газотранспортной сети:
- Node (узел). В газовой сети газ течет из одной точки в другую, которые задаются через их координаты. Элементы всех других компонентов, например, компрессорные станции и электростанции, имеют связанный узел, который позволяет выполнять географическую привязку каждого элемента. В целом, термин узел соответствует аналогичному понятию из теории графов.
- PipeLine (газопровод), которые позволяют передавать газ от одного узла к другому. Географически они привязаны к упорядоченному списку узлов.
- PipeSegment (сегмент газопровода) почти идентичны PipeLine, однако они могут соединять только два узла. Любой элемент PipeLine с тремя или более узлами можно преобразовать в несколько элементов PipeSegment.
- Compressor (компрессор) – компрессорная станция, которая повышают давление газа и позволяют ему течь от одного узла к другому. Газокомпрессорная станция содержит несколько газоперекачивающих агрегатов (турбин).
- LNG (СПГ) — сжиженный природный газ. В Европе есть несколько терминалов СПГ и хранилищ СПГ, так как часть газа доставляется в Европу судами.
- Storage (хранилище), где может храниться избыточный газ, например, на старых газовых месторождениях или в соляных пещерах. Этот газ можно использовать в периоды низкого предложения или высокого спроса.
- Consumer (потребитель) – пользователь газа, от домохозяйств до промышленных предприятий, кроме электростанций.
- PowerPlant (электростанция) – этот термин обозначает газ, используемый только электростанциями.
- Production (добыча) – месторождения внутри страны, где газ добывается из-под земли. Большая часть газа, используемого в Европе, поступает из-за пределов ЕС. Теме не менее, по всей Европе есть несколько небольших площадок по добыче газа.
- BorderPoint (пограничный пункт) – объект на границах между странами, которые в основном используются для измерения расхода газа из одной страны в другую.
Ниже на рисунке показана газотранспортная европейская сеть и ее компоненты.
При том, что газотранспортная система состоит из разных компонентов, их все можно описать по единой общей структуре:
- id (идентификатор) – строка, которая является идентификатором элемента и должна быть уникальной;
- name (название) – строка, например, Compressor Radeland. В большинстве случаев не задается.
- source_id — список строк, которые являются источниками данных элемента. Поскольку несколько элементов из разных источников могли быть объединены в один элемент, знание исходных источников данных может пригодится.
- node_id — идентификатор узла с географической привязкой, с которым связан элемент сети. Для компрессора это будет всего один node_id. А для газопровода эта запись будет списком как минимум из двух значений node_id, идентифицирующих начальный и конечный узлы.
- lat — значение широты элемента. Для элементов типа PipeLine и PipeSegment это будет список значений широты. Во всем проекте SciGRID_gas используется проекционная система World Geodetic 1984 (epsg:4326).
- long — значение долготы элемента, для газопроводов и их сегментов будет целый список таких значений. Аналогично широте, используется проекционная система World Geodetic
- country_code (код страны) — строка, указывающая двузначный код страны ISO связанного узла элементов или списка узлов в случае PipeLine или PipeSegment.
- comment – произвольный комментарий, связанный с элементом, не применяется в большинстве случаев.
- tag – теги, словарь данных, зарезервированный для некоммерческого веб-картографического веб-проекта OpenStreetMap (OSM). Он содержит все связанные пары ключ-значение элемента OSM.
Перед тем, как создать сам граф знаний европейской газотранспортной системы, сперва определим ограничения, а затем приступим к созданию компонентов. Для этого в рамках графовой СУБД Neo4j будем использовать ее SQL-подобный язык запросов Cypher:
CREATE CONSTRAINT BorderPoints IF NOT EXISTS ON (n:BorderPoint) ASSERT n.id IS UNIQUE; CREATE CONSTRAINT Compressors IF NOT EXISTS ON (n:Compressor) ASSERT n.id IS UNIQUE; CREATE CONSTRAINT Consumers IF NOT EXISTS ON (n:Consumer) ASSERT n.id IS UNIQUE; CREATE CONSTRAINT LNGs IF NOT EXISTS ON (n:LNG) ASSERT n.id IS UNIQUE; CREATE CONSTRAINT Nodes IF NOT EXISTS ON (n:Node) ASSERT n.id IS UNIQUE; CREATE CONSTRAINT PowerPlants IF NOT EXISTS ON (n:PowerPlant) ASSERT n.id IS UNIQUE; CREATE CONSTRAINT Productions IF NOT EXISTS ON (n:Production) ASSERT n.id IS UNIQUE; CREATE CONSTRAINT Storages IF NOT EXISTS ON (n:Storage) ASSERT n.id IS UNIQUE;
Отдельно создадим пограничные пункты, импортировав их из файла-источника данных:
LOAD CSV WITH HEADERS FROM 'https://raw.githubusercontent.com/iamvarol/blogposts/main/medium/europe_gas_network/data/IGGIELGNC3_BorderPoints.csv' as row FIELDTERMINATOR ';' WITH row.id as id, replace(row.name, '"','') as name, row.country_code as country_code, apoc.convert.fromJsonList(row.source_id) as source_ids, apoc.convert.fromJsonList(row.node_id) as node_ids, point({latitude:toFloat(row.lat), longitude:toFloat(row.long)}) as loc, apoc.convert.fromJsonMap(replace(row.param, 'None', 'null')) as params MERGE(b:BorderPoint {id:id}) ON CREATE SET b.name = name, b.country_code = country_code, b.source_id = source_ids[0], b.node_id = node_ids[0], b.loc = loc, b += params
Как быстро загрузить данные в Neo4j с помощью наглядного веб-интерфейса Data Importer, читайте в нашей новой статье.
Теперь создадим узлы как точки соединения газопроводов:
LOAD CSV WITH HEADERS FROM 'https://raw.githubusercontent.com/iamvarol/blogposts/main/medium/europe_gas_network/data/IGGIELGNC3_Nodes.csv' as row FIELDTERMINATOR ';' WITH row.id as id, replace(row.name, '"','') as name, row.country_code as country_code, apoc.convert.fromJsonList(row.source_id) as source_ids, apoc.convert.fromJsonList(row.node_id) as node_ids, point({latitude:toFloat(row.lat), longitude:toFloat(row.long)}) as loc, apoc.convert.fromJsonMap(replace(row.param, 'None', 'null')) as params MERGE(n:Node {id:id}) ON CREATE SET n.name = name, n.country_code = country_code, n.source_id = source_ids[0], n.node_id = node_ids[0], n.loc = loc, n += params
Соединим созданные узлы:
CALL apoc.periodic.iterate(" LOAD CSV WITH HEADERS FROM 'https://raw.githubusercontent.com/iamvarol/blogposts/main/medium/europe_gas_network/data/IGGIELGNC3_PipeSegments.csv' as row FIELDTERMINATOR ';' WITH row.id as id, row.name as name, apoc.convert.fromJsonList(row.country_code) as countries, apoc.convert.fromJsonList(row.node_id) as nodes, apoc.convert.fromJsonMap(replace(row.param, 'None', 'null')) as params MATCH (start_p:Node {id:nodes[0]}) MATCH (end_p:Node {id:nodes[1]}) RETURN start_p, end_p, id, name, params, countries "," MERGE (start_p)-[pipe:PIPE {id:id}]->(end_p) ON CREATE SET pipe.name = name, pipe += params ", {batchSize:2, parallel:true} )
Создав граф знаний, можно приступать к разведочному анализу данных (Exploratory Data Analysis, EDA) с помощью имеющей в Neo4j библиотеки Graph Data Science со множеством специализированных алгоритмов. Как работают некоторые из них, рассмотрим далее.
Разведочный анализ данных
Сперва подсчитает количество узлов во всем графе европейской газотранспортной системы. Для этого напишем следующий Cypher-запрос:
CALL db.labels() YIELD label CALL apoc.cypher.run('MATCH (:`'+label+'`) RETURN count(*) as count',{}) YIELD value RETURN label as nodes, value.count as nodeCount ORDER BY nodeCount DESC
Определить количество отношений, связывающих узлы, поможет следующий запрос:
CALL db.relationshipTypes() YIELD relationshipType as type CALL apoc.cypher.run('MATCH ()-[:`'+type+'`]->() RETURN count(*) as count',{}) YIELD value RETURN type as relationship, value.count as relationshipCount
Чтобы использовать алгоритмы библиотеки Graph Data Science, следует сперва создать соответствующий объект и подключиться к нему:
import pandas as pd from graphdatascience import GraphDataScience uri = "<uri>" pwd = "<pwd>" user= 'neo4j' gds = GraphDataScience(uri, auth=("neo4j", pwd))
После создания проекции именованного графа, для каждого алгоритма библиотеки Graph Data Science (GDS) следует выбрать один из 4-х режимов выполнения:
- stream — возвращает результаты алгоритма в виде потока записей без изменения базы данных;
- write — записывает результаты алгоритма в Neo4j и возвращает одну запись сводной статистики;
- mutate — записывает результаты алгоритма в спроецированный граф и создает единую форму сводной статистики. Этот режим особенно полезен при разработке предикторов для машинного обучения (feature engineering), когда надо включить некоторые величины, рассчитанные GDS, в проекцию графа. Режим мутации не изменяет саму базу данных, а записывает результаты вычислений в каждый узел спроецированного графа для будущих вычислений, что пригодится при использовании сложных алгоритмов или аналитических конвейеров.
- stats – возвращает одну запись сводной статистики, но не записывает ее ни в Neo4j, ни в спроецированный граф.
В дополнение к вышеупомянутым четырем режимам можно использовать оценку, чтобы предсказать, сколько памяти будет использовать данный алгоритм. Подробнее об этом мы рассказывали здесь.
Возвращаясь к задаче анализа европейской газотранспортной системы, определим 10 самых важных узлов в ней по алгоритму ранжирования веб-страниц PageRank (PR). Он измеряет значимость каждого узла в графе, вычисляя рейтинг на основе количества входящих отношений (ссылок). Для нашей задачи фрагмент кода будет выглядеть так:
G, res = gds.graph.project( 'pipes', 'Node', "PIPE" ) res = gds.pageRank.stream(G) res.sort_values(by='score', ascending=False)[:10]
Также с помощью алгоритма Page Rank в Neo4j-библиотеке Graph Data Science можно найти наиболее важные точки соединения газопроводов Европы и показать соответствующий им максимальный поступающий годовой объем газа:
CALL gds.pageRank.write('pipes', { writeProperty: 'pagerank' } ) YIELD nodePropertiesWritten, ranIterations MATCH (n:Node)<-[r:PIPE]-(:Node) RETURN n.id AS id, n.country_code AS countryCode, round(n.pagerank,2) AS pagerank, round(sum(r.max_cap_M_m3_per_d),2) as maximumAnnualGasVolume ORDER BY pagerank DESC, id ASC LIMIT 10
С практической точки зрения в рамках конкурентного анализа также интересно вычислить расстояние до компрессоров или хранилищ от различных потенциальных поставщиков газа. Сделать это можно, введя в рассматриваемый граф газотранспортной европейской системы дополнительные узлы и отношения и применив методы вычисления расстояний, например, алгоритм Дейкстры. Он также входит в состав библиотеки Graph Data Science и пример его использования в типовой логистической задаче мы описывали здесь.
Визуализировать результаты этих и других исследований можно с помощью NeoDash — приложения для быстрого построения наглядных дэшбордов из графов Neo4j, включая различные диаграммы и карты. После создания дэшборда его можно сохранить в базе данных в качестве узла и снова запустить.
Больше практических примеров использования Neo4j и других инструментов графовой аналитики больших данных для реальных бизнес-задач вы узнаете на специализированных курсах в нашем лицензированном учебном центре обучения и повышения квалификации для разработчиков, менеджеров, архитекторов, инженеров, администраторов, Data Scientist’ов и аналитиков больших данных в Москве:
Источники