Neo4j + Pythonの使い方あれこれ

# はじめに
この記事は、[AMBL株式会社 Advent Calendar 2022](https://qiita.com/advent-calendar/2022/ambl-rd)の15日目の記事になります。是非、他の記事も読んで見て下さい〜:santa_tone2:

最近、業務でグラフ型DBであるNeo4jを使う機会があったのですが、筆者はグラフDBを扱うのが初めてだった事もあり、色々と四苦八苦した部分もあり...

今回の記事は、シンプルにNeo4jにPythonを用いてデータ登録するまでの流れを簡単にメモ書きしていきます。


# 環境設定
今回実装していた筆者の環境は、macOS Big Sur (version 11.6)になります。
まずは必要なツールを、ターミナル上でインストールします。

とは言っても、特にこれといって面倒な作業はありません。
下記の `homebrew` コマンドを叩けばOKです。
```
$ brew install neo4j
```
pythonは3.7以降のversionであれば、問題なく動きます。
今回使用するライブラリは以下の二つです。
```
$ pip3 install neo4j
$ pip3 install pandas
```
pythonからNeo4jを操作するために必要なdriverは`neo4j`ライブラリ1個で完結しています。
めっちゃ便利ですね。
今回は登録するデータをCSV形式で持っている、という前提で進めていきますので、CSVを読み込むようにpandasを使用します。（楽なので）

# Neo4j 起動

上記のコマンドが正常に完了したら、neo4jのサーバを起動します。
```
サーバ起動コマンド
$ neo4j start

サーバ終了コマンド
$ neo4j stop

サーバ状態確認コマンド
$ neo4j status
```

正常に起動すれば、下記のようなログが表示されます。
最後に`There may be a short delay until the server is ready.`と表示されていれば問題ないです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2346414/0dedc7df-c424-de7c-df17-4f5c318a2bd7.png)

ちなみに、default設定でneo4jのサーバを起動した際はローカルにNeo4jサーバが立ち上げられます。
その際の利用ポートの設定状態は、下記の通りです。

|Port|説明|
|-|-|
|7687|Neo4j本体|
|7474|管理コンソール(Neo4j Browser)のホスティング用|

では、管理コンソール画面に移動してみます。
ブラウザ上で`http://localhost:7474/`にアクセスしましょう。

# Neo4j コンソール画面

`http://localhost:7474/`にアクセスすると、初期状態ではこのような画面が表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2346414/61c1b0d9-b2ba-eff5-b5ab-d20b520981f6.png)

ここでは、ログインIDとpasswordを求められます。
ログイン情報の初期設定値はこちらになります↓

|ID|Password|
|-|-|
|neo4j|neo4j|

＊ ログイン後、passwordの変更求められます。ご対応ください。

ログインができれば、コンソール画面に正常に遷移します。
とは言っても、この段階では特にデータ登録などされていないので、空っぽのDBがあるだけです。
では、早速Pythonからデータ登録をしていきましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2346414/44937f93-e777-4552-c7c2-c21698c29187.png)

# Neo4jへのデータ登録

今回、登録するデータはこちら。

| relation  | from_node_type | from_node | to_node_type | to_node | 
| ------------ | -------------- | ---------------------- | ------------ | -------------------- | 
| 父           | テレパシー     | アーニャ・フォージャー | スパイ       | ロイド・フォージャー | 
| 母           | テレパシー     | アーニャ・フォージャー | 暗殺者       | ヨル・フォージャー   | 
| 飼い犬       | テレパシー     | アーニャ・フォージャー | 予知能力     | ボンド・フォージャー | 
| 母の弟       | テレパシー     | アーニャ・フォージャー | 特殊警察     | ユーリ・ブライアン   | 
| もじゃもじゃ | テレパシー     | アーニャ・フォージャー | 情報屋       | フランキー           | 
| 怖い人       | テレパシー     | アーニャ・フォージャー | スパイ       | フィオナ・フロスト   | 
| 妻           | スパイ         | ロイド・フォージャー   | 暗殺者       | ヨル・フォージャー   | 
| 夫           | 暗殺者         | ヨル・フォージャー     | スパイ       | ロイド・フォージャー | 
| pet          | スパイ         | ロイド・フォージャー   | 予知能力     | ボンド・フォージャー | 
| pet          | 暗殺者         | ヨル・フォージャー     | 予知能力     | ボンド・フォージャー | 
| シスコン     | 特殊警察       | ユーリ・ブライアン     | 暗殺者       | ヨル・フォージャー   | 
| 恋慕         | スパイ         | フィオナ・フロスト     | スパイ       | ロイド・フォージャー | 
| 敵           | 特殊警察       | ユーリ・ブライアン     | スパイ       | ロイド・フォージャー | 
| 恋敵         | スパイ         | フィオナ・フロスト     | 暗殺者       | ヨル・フォージャー   | 
| 仲間         | 情報屋         | フランキー             | スパイ       | ロイド・フォージャー | 

必要あれば、上記の表をexcelにコピペして作成してみてください。
では、上記のデータをまとめたcsvファイルに`test.csv`とし、実際にDBに登録していきましょう。

まずは、ライブラリのインストール。
```python:neo4j_test.ipynb
from neo4j import GraphDatabase, basic_auth
import pandas as pd
```

その後、Neo4jserverに接続・操作を行うdriverの設定。
```python:neo4j_test.ipynb
# neo4j serverに接続するdriverの設定
driver = GraphDatabase.driver('neo4j://localhost:7687', auth=('neo4j', '****'))
```

ここまでできれば、接続準備は完了。
実際に、読み込むデータのロード。
```python:neo4j_test.ipynb
demo_path = 'test.csv'
df = pd.read_csv(demo_path, encoding='sjis')
```
正しく読み込めていればこんな感じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2346414/4aa3c379-932b-5f09-a494-51b7ed017a5a.png)

今回はNeo4jにデータを登録する手順として、
1. まずnode情報の登録する
2. 登録されたnodeにrelationを作成する
の順番に処理します。
実際に処理する関数をここでは先に定義しています。

- nodeを登録するNeo4jのクエリコマンド
`CREATE (f:{label}:character [name: $name]) RETURN f`

- relationを登録するNeo4jのクエリコマンド
`MATCH (f1:character {name: $from_node})`
`MATCH (f2:character {name: $to_node})`
`f'CREATE (f1)-[:{relation}]->(f2)`

ここでは、上記のコマンドでデータを登録しているんだな、くらいに思ってもらえればOKです。

```python:neo4j_test.ipynb
def add_node(tx, pair_node_info):
    for name, label  in pair_node_info:
        query = f'CREATE (f:{label}:character [name: $name]) RETURN f'
        query = query.replace("[", "{").replace("]", "}")
        tx.run(query, name=name)

def add_relation(tx, row):
    from_node = row["from_node"]
    to_node = row["to_node"]
    relation = row["relation"]

    tx.run('MATCH (f1:character {name: $from_node})'
            'MATCH (f2:character {name: $to_node})'
            f'CREATE (f1)-[:{relation}]->(f2)',
            from_node=from_node, relation=relation, to_node=to_node)
```

では、早速node情報を登録していきましょう。
そのコマンドはこちら。

```python:neo4j_test.ipynb
node_list = []
label_list = []
node_list += df["from_node"].to_list()
label_list += df["from_node_type"].to_list()

node_list += df["to_node"].to_list()
label_list += df["to_node_type"].to_list()

pair_node_info = []
for idx in range(len(node_list)):
    pair_node_info.append((node_list[idx], label_list[idx]))

pair_node_info = list(set(pair_node_info))
with driver.session() as session:
    session.write_transaction(add_node, pair_node_info)
```

次に、relation情報を登録していきましょう。
そのコマンドはこちら。
```python:neo4j_test.ipynb
def apply_relation(row):
    with driver.session() as session:
        session.write_transaction(add_relation, row)

df.apply(apply_relation, axis=1)
```
これらを実行して登録は完了。
結構あっさりしていたかなと思います。
では、コンソール画面に戻って実際にみてみましょう。

画面のコマンド入力部分に`MATCH (n) RETURN n LIMIT 25`と入力すると、
下記のような画面が表示されます。（見やすいように形だけ、整形しています。）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2346414/e399cf07-2aa6-42a0-f581-f79de471379b.png)

簡単な人物相関図ができました:clap::clap::clap:

とまぁ、こんな感じで簡単にデータの登録ができました。
ちょっと遊んでみる分にはいろいろ面白いですね。

最後に、登録した情報を全て削除したい場合は、下記を実行。
(コメントアウトしております。)
```python:neo4j_test.ipynb
# def clear_db(tx):
#     tx.run('MATCH (n) DETACH DELETE n')

# with driver.session() as session:
#     session.write_transaction(clear_db)
```


# 終わりに
いかがでしたでしょうか？
グラフ型DBは使い所が難しい気もしますが、DBとして関係性を情報として検索できるのは結構面白いですよね。皆様も機会があれば触ってみてください。
最後に、今回使用したソースコードを貼っておきます。

```python:neo4j_test.py

from neo4j import GraphDatabase, basic_auth
import pandas as pd

def add_node(tx, pair_node_info):
    for name, label  in pair_node_info:

        query = f'CREATE (f:{label}:character [name: $name]) RETURN f'
        query = query.replace("[", "{").replace("]", "}")
        tx.run(query, name=name)

def add_relation(tx, row):
    from_node = row["from_node"]
    to_node = row["to_node"]
    relation = row["relation"]

    tx.run('MATCH (f1:character {name: $from_node})'
            'MATCH (f2:character {name: $to_node})'
            f'CREATE (f1)-[:{relation}]->(f2)',
            from_node=from_node, relation=relation, to_node=to_node)

def apply_relation(row):
    with driver.session() as session:
        session.write_transaction(add_relation, row)

def main():
    driver = GraphDatabase.driver('neo4j://localhost:7687', auth=('neo4j', 'ambl-neo4j'))

    demo_path = 'test.csv'
    df = pd.read_csv(demo_path, encoding='sjis')

    node_list = []
    label_list = []
    node_list += df["from_node"].to_list()
    label_list += df["from_node_type"].to_list()
    node_list += df["to_node"].to_list()
    label_list += df["to_node_type"].to_list()

    pair_node_info = []
    for idx in range(len(node_list)):
        pair_node_info.append((node_list[idx], label_list[idx]))

    pair_node_info = list(set(pair_node_info))
    with driver.session() as session:
        session.write_transaction(add_node, pair_node_info)
    
    df.apply(apply_relation, axis=1)

if __name__ == ('__main__'):
    main()

```