RDFグラフをプロパティグラフに変換する

## 1. 本記事について
前回の投稿で、OracleのRDFグラフ機能の使い方に関する記事を投稿しました。
[OracleのRDFグラフ機能を使ってみる](https://qiita.com/kyata_01/items/08c2588c53f111547d27 "OracleのRDFグラフ機能を使ってみる")
今回はOracle Database内に構成されたRDFグラフを
別のグラフ構造であるプロパティグラフに変換する手順を記載します。

・ターゲット読者
⇒ グラフデータベースに興味がある方

## 2. RDFグラフとプロパティグラフの違い
下図に大まかな特徴をまとめています
グラフである点は同じですが、グラフの構造や用途が異なるものとなっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217525/d8b12a69-1401-5c9c-6556-3a85eeaf56cf.png)

## 3. 変換できると何が嬉しいのか
データの公開に適したRDFグラフをデータ分析向きのプロパティグラフへ
変換できることで、Web上に公開されたRDFのデータを
使って新たな知見を見出せる可能性が広がります。

さらにRDFグラフは（きちんと語彙が設計されていれば）別の人が作成した
RDFグラフとどんどん繋げていくことができます。※ Linked Open Data
なので、様々な組織や個人が公開しているRDFグラフを組み合わせたデータを
使って分析するようなことも可能です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217525/d134ffd2-195f-c4b2-37c3-d34f7f45bb6a.png)
RDFの形式で公開されているデータは徐々に増えてきており、
日本国内のものはこちらの記事でまとめられています。
[利用可能なSPARQLエンドポイントリスト(2021年7月版)](https://qiita.com/uedayou/items/9e4c6029a2cb6b76de9f "利用可能なSPARQLエンドポイントリスト(2021年7月版)")

行政機関が公開している政府統計や法人データなど、
分析のしがいのあるデータも多く存在しています。
個人的には[立命館大学ゲーム研究センターが公開しているデータ](https://collection.rcgs.jp/ "立命館大学ゲーム研究センターが公開しているデータ")に興味を惹かれます。
※ヒットしやすいプロデューサー × ゲームジャンル × プラットフォームの
組み合わせを見つけるとか...

## 4. 実際に変換してみる
### 環境について
前回の"RDFグラフを使ってみる"の投稿の作業は実施済で
駅ビルのRDFグラフがOracle Databaseにロードされた状態から開始します。

追加でプロパティグラフの機能を使用するためのセットアップ作業が必要となりますが、
こちらについては既にQiitaに記事があるため割愛します。
[Oracle Property Graph serverのセットアップとその活用方法](https://qiita.com/hashiyu7/items/29563ae7b5372e8a6fc2 "Oracle Property Graph serverのセットアップとその活用方法")
以下のコンポーネントが構成されていることが前提となります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217525/d0d7c7d1-7012-3862-decb-5c53d4eb8046.png)

### 変換の方法について
OracleではRDFグラフをプロパティグラフへ変換するためのJavaのAPIが
提供されています。
[Creating Property Graph Views on an RDF Graph](https://docs.oracle.com/en/database/oracle/property-graph/21.4/spgdg/creating-property-graph-views-rdf-graph.html "Creating Property Graph Views on an RDF Graph")
これを使った記事を書こうと思っていたのですが問題がありました。
このAPIが19c~追加された下記の新しいRDFグラフの保持方法に
まだ対応していないようでした。
[Support Added for Schema-Private Semantic Networks](https://docs.oracle.com/en/database/oracle/oracle-database/21/rdfrm/rdf-whats-new.html#GUID-7C2B156A-DF5A-40B9-94CD-332967ED9A5A "Support Added for Schema-Private Semantic Networks")
※ 一般スキーマでRDFグラフを保持できる機能。18c以前は"MDSYS"という専用のユーザーが保持。

駅ビルのRDFグラフはこちらで作成しまったので、今回は別のルートで変換することにします。

通常、Oracle Databaseにプロパティグラフを構成する際は
グラフの点を表すノード表と、グラフの線を表すエッジ表を作成します。
一方、RDFグラフも内部的には主語-述語-目的語の繋がりを表すリンク表と、
それぞれに入る値を持つバリュー表という似た表構造から成ります。

今回はRDFグラフのバリュー表とリンク表から、SQLを使って
プロパティグラフ用のノード表とエッジ表を作成することで変換を行います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217525/109d4e28-8a5d-ae24-22e1-d49dc69ba72d.png)

### ノード表、エッジ表の作成
プロパティグラフ用スキーマでOracle Databaseに接続して
作成していきます。スキーマ構成は以下の通りです。
RDFグラフ用スキーマ・・・"RDFUSER"
プロパティグラフ用スキーマ・・・"GRAPHUSER"

・駅ビルノード表作成

```sql
-- 駅ビルを表すノードを抽出
CREATE OR REPLACE VIEW v_station_building AS
WITH building_node AS 
(
SELECT value_id FROM RDFUSER.SEMNET01#RDF_VALUE$
WHERE value_name LIKE 'https://www.wikidata.org%'
),
-- "駅ビル名称"プロパティで接続されるノードを抽出
building_name AS
(
SELECT start_node_id, end_node_id FROM RDFUSER.SEMNET01#RDF_LINK$
WHERE p_value_id =
		(
		SELECT value_id FROM RDFUSER.SEMNET01#RDF_VALUE$
		WHERE value_name = 'http://linkdata.org/property/rdf1s7634i#Lja'
		)
),
-- 駅ビルノードに駅ビル名を結合
building_node_with_name AS (
SELECT a.value_id, b.end_node_id
FROM building_node a LEFT OUTER JOIN building_name b
ON a.value_id = b.start_node_id
)
SELECT c.value_id, d.value_name
FROM building_node_with_name c LEFT OUTER JOIN RDFUSER.SEMNET01#RDF_VALUE$ d
ON c.end_node_id = d.value_id;
```
・駅ビルの所在地ノード表作成

```SQL
CREATE OR REPLACE VIEW v_placement AS 
SELECT value_id, value_name FROM RDFUSER.SEMNET01#RDF_VALUE$
WHERE value_id IN
	(
	SELECT end_node_id FROM RDFUSER.SEMNET01#RDF_LINK$
	WHERE p_value_id IN 
		(
		SELECT value_id FROM RDFUSER.SEMNET01#RDF_VALUE$
        -- 所在地を表すプロパティ
		WHERE value_name = 'http://linkdata.org/property/rdf1s7634i#%E4%BD%8D%E7%BD%AE%E3%81%99%E3%82%8B%E8%A1%8C%E6%94%BF%E5%8C%BA%E7%94%BB'
		)
	);
```
・駅ビルの運営者ノード表作成

```SQL
CREATE OR REPLACE VIEW v_organization AS 
SELECT value_id, value_name FROM RDFUSER.SEMNET01#RDF_VALUE$
WHERE value_id IN
	(
	SELECT end_node_id FROM RDFUSER.SEMNET01#RDF_LINK$
	WHERE p_value_id IN 
		(
		SELECT value_id FROM RDFUSER.SEMNET01#RDF_VALUE$
        -- 運営者を表すプロパティ
		WHERE value_name = 'http://linkdata.org/property/rdf1s7634i#%E9%81%8B%E5%96%B6%E8%80%85'
		)
	);
```
・駅ビル -> 所在地エッジ表作成

```SQL
CREATE OR REPLACE VIEW e_placement2building AS
SELECT link_id, start_node_id, end_node_id
FROM RDFUSER.SEMNET01#RDF_LINK$
WHERE p_value_id IN 
	(
	SELECT value_id FROM RDFUSER.SEMNET01#RDF_VALUE$
	-- 所在地を表すプロパティ
	WHERE value_name = 'http://linkdata.org/property/rdf1s7634i#%E4%BD%8D%E7%BD%AE%E3%81%99%E3%82%8B%E8%A1%8C%E6%94%BF%E5%8C%BA%E7%94%BB'
	)
-- 駅ビルノードが始点のプロパティのみ
AND start_node_id IN (SELECT value_id FROM v_station_building);
```
・駅ビル -> 運営者エッジ表作成

```SQL
CREATE OR REPLACE VIEW e_organization2building AS
SELECT link_id, start_node_id, end_node_id
FROM RDFUSER.SEMNET01#RDF_LINK$
WHERE p_value_id IN 
	(
	SELECT value_id FROM RDFUSER.SEMNET01#RDF_VALUE$
	-- 運営者を表すプロパティ
	WHERE value_name = 'http://linkdata.org/property/rdf1s7634i#%E9%81%8B%E5%96%B6%E8%80%85'
	)
-- 駅ビルノードが始点のプロパティのみ
AND start_node_id IN (SELECT value_id FROM v_station_building);
```

思いつきでやってみたのですが、売上表みたいな一般的なRDB表から
ノード/エッジ表を作成するケースよりはかなり楽でした。

### プロパティグラフの作成
作成したノード/エッジ表からプロパティグラフを作成します。
pythonクライアントから"CREATE PROPERTY GRAPH..."文を実行することで作成します。
この文を実行することでノード/エッジ表をプロパティグラフとして扱うための
メタデータが作成されます。
[Property Graph Query Language](https://pgql-lang.org/ "Property Graph Query Language")

・DBコネクション取得

```python
import opg4py
import pypgx
from opg4py.pgql._pgql_connection import (
    PgqlConnection,
    PgqlPreparedStatement,
    PgqlStatement,
)

dbuser: str = "GRAPHUSER"
password: str = "<パスワード>"
jdbc_url: str = "jdbc:oracle:thin:@<ホスト名>:1521/<サービス名>"

pgqlConnection: PgqlConnection = opg4py.pgql.get_connection(
    dbuser, password, jdbc_url
)
```
・プロパティグラフの作成

```python
# 作成するグラフ名
graph_name = "STATION_BUILDING"

# CREATE PROPERY GRAPH文
# VERTEX TABLES / EDGE TABLESにノード/エッジ表の定義を記述
create_pgview = f"""
CREATE PROPERTY GRAPH {graph_name}
VERTEX TABLES (
  v_station_building KEY(value_id)
    LABEL STATION_BUILDING
    PROPERTIES(value_name),
  v_placement KEY(value_id)
    LABEL PLACEMENT
    PROPERTIES(value_name),
  v_organization KEY(value_id)
    LABEL ORGANIZATION
    PROPERTIES(value_name)
)
EDGE TABLES (
  e_placement2building KEY(link_id)
    SOURCE KEY (end_node_id) REFERENCES v_placement
    DESTINATION KEY (start_node_id) REFERENCES v_station_building
    LABEL PLACE
    NO PROPERTIES,
  e_organization2building KEY(link_id)
    SOURCE KEY (end_node_id) REFERENCES v_organization
    DESTINATION KEY (start_node_id) REFERENCES v_station_building
    LABEL ORGANIZE
    NO PROPERTIES
)
OPTIONS(PG_VIEW)
"""

pgqlStmt: PgqlStatement = pgqlConnection.create_statement()
pgqlStmt.execute(create_pgview)

pgqlStmt.close()
```
### 作成したグラフの確認
Property Graph Serverが構成されていれば、
"http://localhost:7007/ui" へアクセスして作成したグラフに
クエリ（PGQL）を投げて結果を可視化できます。
DBユーザー/パスワード/JDBCURLでログインします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217525/3add5b2f-907d-af1d-3f10-31236fe885d4.png)
・まずはノード/エッジ数を確認してみます。

```SQL
SELECT label(n), count(*)
FROM MATCH (n)
GROUP BY label(n)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217525/17f5a6c4-44de-acea-a6e7-217f2d1294a6.png)

```SQL
SELECT label(e), count(*)
FROM MATCH ()-[e]->()
GROUP BY label(e)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217525/63758789-d104-42d0-195f-c5d8e0b70b51.png)

・適当に100件取得してみます。所在地=東京都の次数が高そう...

```SQL
SELECT v1, e, v2
FROM MATCH (v1)-[e]->(v2)
LIMIT 100
```
![スクリーンショット 2022-01-16 035054.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217525/83487550-f8c9-d4e0-2ea8-91798940743f.png)
・所在地が東京都の駅ビルを取得

```SQL
SELECT v1, e, v2
FROM MATCH (v1:PLACEMENT)-[e:PLACE]->(v2:STATION_BUILDING)
WHERE v1.value_name = '東京都'
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217525/81221c23-a9dd-f431-921d-cd68e3aac8bb.png)

## 5. グラフアルゴリズムの実行
ここまではOracle Database内に構成されたプロパティグラフに対して
クエリを実行していました。
グラフ解析用のエンジンであるProperty Graph Serverを使うと、
Oracle Databaseからメモリ上にグラフをロードし、さまざまな
実装済のグラフアルゴリズムを実行できるようになります。

作成したグラフをProperty Graph Serverへロードし、
グラフのベクトル表現を取得するアルゴリズムであるDeepWalkを実行します。
（今回のグラフモデルにはDeepWalkはあまりフィットしないですが...例として）

```python
from opg4py.pgql._pgql_result_set import PgqlResultSet
from pypgx.api import PgxGraph, PgxSession, ServerInstance
from pypgx.pg.rdbms.graph_server import GraphServer, get_instance

base_url = "http://localhost:7007"
db_username = "GRAPHUSER"
password = "<パスワード>"

server_instance = get_instance(
    base_url,
    db_username,
    password
)
# セッション取得
session = server_instance.create_session("session")
# グラフのロード
graph = session.read_graph_by_name("STATION_BUILDING", "pg_view")
# Analystクラス（グラフアルゴリズムを呼び出す起点）の呼び出し
analyst = session.create_analyst()
# DeepWalkモデルの構築
deepwalk_model = analyst.deepwalk_builder(layer_size=50, walk_length=10)
deepwalk_model.fit(graph)
```
・アトレ品川と類似度の高いノードを取得

```python
graph.query_pgql(
    """
    SELECT n
    FROM MATCH (n:STATION_BUILDING)
    WHERE n.value_name = 'アトレ品川'
    """
    ).print()

ID=V_STATION_BUILDING(8040324384459740064)
```

```python
# 類似度の高いtop3を算出
deepwalk_model.compute_similars("V_STATION_BUILDING(8040324384459740064)", k=3).print()

+---------------------------------------------------------------+
| dstVertex                               | similarity          |
+---------------------------------------------------------------+
| V_STATION_BUILDING(8040324384459740064) | 1.0                 |
| V_STATION_BUILDING(695102777557466374)  | 0.6041746139526367  |
| V_STATION_BUILDING(8346997658380496096) | 0.47883322834968567 |
+---------------------------------------------------------------+

graph.get_vertex("V_STATION_BUILDING(695102777557466374)").get_property("VALUE_NAME")
graph.get_vertex("V_STATION_BUILDING(8346997658380496096)").get_property("VALUE_NAME")

'アキバ・トリム'
'柏髙島屋ステーションモール'
```
Deepwalkでベクトル表現を取得することで、機械学習の特徴量として使うこともでき、
RDFグラフ ⇒ プロパティグラフ ⇒ 機械学習というようなシナリオも実現可能です。

以上です。

## 参考
[Graph Developer's Guide for Property Graph](https://docs.oracle.com/en/database/oracle/property-graph/21.4/spgdg/ "Graph Developer's Guide for Property Graph")
[PyPGX 21.4.0 documentation](https://docs.oracle.com/cd/F41824_01/latest/index.html "PyPGX 21.4.0 documentation")