neo4j

【Python】neo4jによるグラフデータベースの使い方

【Python】neo4jによるグラフデータベースの使い方

Pythonでグラフデータベースであるneo4jを操作する方法について解説します。

グラフデータベース neo4j

neo4jとは、NeoTechnology社が提供しているグラフデータベースのことを言います。

データベースというとまず最初に思い浮かべるのはリレーショナルデータベース(RDB)かと思います。近年では、キー・バリュー型、列指向型、ドキュメント型などの様々なタイプのデータベースが登場しています。グラフデータベースもそういったデータベースの一種です。

グラフデータベースとは、以下の例のように「ノード」と「リレーションシップ」という要素で構成され、要素と関係性をグラフで表現します。(※このグラフは以降の説明の操作で作成します。)

neo4j sample

RDBは、列と行でレコードを表現しますが、グラフデータベースであるneo4jでは、ノード間の関連をリレーションシップという形で紐づけて表現することになります。ネットワーク構造を持つようなデータを直感的に表現できるという利点があります。

また、neo4jではデータベース操作用言語として、Cypher Queryという言語をサポートします。Cypher QueryはSQLに似た形式となっておりデータのアクセスや表現を行うことができます。簡単な例については以降で紹介します。

本記事では、まずneo4jをインストールし、Cypher Queryでグラフデータベースを操作してみます。その後、Pythonプログラムからアクセスして操作する方法について説明します。

Note

neo4jの公式ページはこちらを参考にしてください。

neo4jの利用方法とグラフデータベース作成

neo4jの利用方法

neo4jを利用するにはneo4jの公式ページにアクセスします。そこで、「Start Free for Developers」をクリックしてください。(※公式ページの構成は変わっている可能性があります)

neo4j 公式ページ

neo4jはクラウドサービスとして使用できます。本記事での目的は学習目的で使ってみることなので、AuraDB Freeというところの「Start Free」をクリックします。こちらはクレジットカード情報など不要です。用途によってProfessionalやEnterpriseの使用を検討してみてください。

neo4j ライセンス

Emailアドレス、パスワードの入力をして「Register」をクリックします。

neo4j 登録

Emailの確認が必要でメールが送付されます。メールに記載の「Verify my e-mail address」というボタンを押して確認を完了させてください。メールが届かない場合は、以下のResendを押してみてください。

neo4j 登録

Emailの確認後に上記画面のDashboardをクリックするとサービスとプライバシーポリシーの確認が出るので、確認して「I agree」をクリックしてください。

neo4j 登録

以上で、neo4jを使用する準備が整います。

neo4jデータベースを作成する

Dashboardを最初に開くと「Let's create your first database」ということで最初のデータベースを作成するページが開かれるかと思います。Database detailsのDatabase Nameの部分に任意のデータベース名を入力して、「Create Database」をクリックしてください。

neo4j データベース作成

上記を実行するとデータベースを作成することができます。

データベースの資格情報として以下のようにUsernameとPasswordが表示されるます。後の接続で必要になりますので控えてから「Continue」をクリックしてください。

neo4j データベース作成

以下のようなデータベースが作成されればデータベースの作成は完了です。Runningとなっているので起動状態となっています。

neo4j データベース作成

neo4jのグラフデータベース操作用言語 Cypher Query

WebベースのDB管理ツール

上記までで、グラフデータベースを作成することができました。データベースのQueryをクリックすると、WebベースのDB管理ツール画面が表示でき、neo4jの操作用言語であるCypher Queryを実行することができるようになります。

neo4j Cypher Query

以下のようなConnect画面が出ますので、データベース作成時に控えたUsername、Passwordを入力して「Connect」をクリックしてください。

neo4j Cypher Query

接続できると以下のようにコマンドを入力できるようなコンソール画面になります。neo4jでは、ここにCypher Queryを入力することでデータベースを操作することができます。

neo4j Cypher Query

Cypher Queryによるデータの操作例

Cypher Queryを使ってneo4jを操作する例を簡単な例を使ってみてみましょう。以降のコマンドは、上記のWebの管理ツールのコンソール上にコマンドを打って実行してみます。

ノードの追加

まずは、簡単にノードを追加してみます。以下のようなCypher Queryを入力して「▶」をクリックまたは「Ctrl+enter」を押して実行します。

CREATE (p:Person {name: "taro"})
RETURN p

実行すると、以下のようにtaroというノードが作成されます。

neo4j python sample

Cypher Queryの構成としては、Personという種類のノードで、属性としてname="taro"を持っているものを作成(CREATE)しているということになります。RETURNの部分は表示するものを指定していると思ってもらえればいいかと思います。

リレーションシップの追加

次にノードとノードをつなぐリレーションシップを作成してみます。以下のようなCypher Queryを入力して実行します。

MATCH (p:Person {name:"taro"})
CREATE (p)-[:FRIEND]->(hanako:Person {name:"hanako"})
RETURN p,hanako

上記のコマンドは、まずtaroをMATCHでpに取得してきて、hanakoとFRIENDというリレーションシップでつなぐという意味になっています。実行すると、以下のようにhanakoというノードが作成されるとともにFRIENDという関係の矢印が作成されます。

neo4j python sample

リレーションシップについては、Cypher Queryで「()-[]->()」という形式を使うことでノード間を接続します。[]の部分がリレーションシップの定義です。今回の例では[:FRIEND]というのはFRIENDという種類の関係性を作成していることを意味しています。

繰り返しを使用する

Cypher Queryでは繰り返しで関係性を作ることも可能です。以下のようなCypher Queryを実行します。

MATCH (p:Person {name:"taro"})
FOREACH (name in ["jiro", "haruka", "sakura"] | 
  CREATE (p)-[:FRIEND]->(:Person {name:name}))

上記を実行すると「Added ~」というような形で表示がされると思います。その後、以下で全てのノードと関係性を表示してみてください。

MATCH (n) OPTIONAL MATCH (n)-[r]-()
RETURN n,r

以下の図のようにtaroから各ノードへの関係性が作成されたのが分かるかと思います。

neo4j python sample

プログラミングにある程度慣れている方なら直感的にわかるかと思いますが、inに指定しているリストの中身が順にnameに設定され、CREATEで設定しているname属性の値として使用されています。Pythonのfor _ in ~という形と同じですので、Pythonプログラムを作ったことがある方であれば分かりやすいのではないかと思います。

全てのノードを検索する

既に上記で出てきていますが、データベース上の全てのノードを検索するには、以下のようなCypher Queryを実行します。よく使う方法として覚えておくとよいでしょう。

MATCH (n) OPTIONAL MATCH (n)-[r]-()
RETURN n,r

全てのノードを削除する

データベース上の全てのノードを削除するには、以下のようなCypher Queryを実行します。データを一度リセットしたい場合に使用できます。上記のサンプルを実行していてよく分からなくなった場合は、一度全て削除してみて再度操作してみてください。

MATCH (n) OPTIONAL MATCH (n)-[r]-()
DELETE n,r

Note

Cypher Query Languageについては、neo4jのDeveloper Guidesにあるこちらのページを参考にしてください。

Pythonからneo4jグラフデータベースを操作する

上記まででneo4jのインストール方法とCypher Queryによるデータベースの簡単な操作方法について紹介してきました。以降では、Pythonプログラムから直接neo4jのデータベースを操作する方法について紹介します。

Pythonで使用するためにneo4jをインストールする

Pythonでneo4jを使用するには、以下のようにpip installでインストールを実行します。

pip install neo4j

正常に完了すればneo4jをPythonで使用する環境が整います。

Pythonでneo4jを使用するサンプルプログラム

neo4jにPythonでアクセスするためのサンプルプログラムを以下に示します。

接続情報はneo4j.iniというファイルに記載して、configparserで読み込む形式にしています。以下の記載は接続情報などはXXXというように伏せているため皆さんの環境の接続情報に書き換えて使ってみてください。

【設定ファイル】neo4j.ini

[NEO4J]
uri = neo4j+s://xxxxxxxxxxxxxxxx:7687
user = xxxxxx
password = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

【サンプルプログラム】neo4j_sample.py

import configparser

from neo4j import GraphDatabase


def clear_db(tx):
    tx.run('MATCH (n) OPTIONAL MATCH (n)-[r]-() DELETE n,r')


def search_all(tx):
    result = tx.run('MATCH (n) OPTIONAL MATCH (n)-[r]-() RETURN n,r')
    return [r for r in result]


def add_person_node(tx, name):
    tx.run('CREATE (p:Person {name: $name}) RETURN p', {'name': name})


def add_friend_relationship(tx, name, friend_name=None):
    if not friend_name:
        tx.run('CREATE (p:Person {name: $name}) RETURN p', {'name': name})
    else:
        tx.run('MATCH (p:Person {name: $name}) '
               'CREATE (p)-[:FRIEND]->(:Person {name: $friend_name})',
               name=name, friend_name=friend_name)


def main():
    # ===== neo4jの設定取得
    config = configparser.ConfigParser()
    config.read('neo4j.ini')
    uri = config['NEO4J']['uri']
    user = config['NEO4J']['user']
    password = config['NEO4J']['password']

    # neo4jドライバーの作成
    driver = GraphDatabase.driver(uri, auth=(user, password))
    with driver.session() as session:
        # データベースのクリア
        session.write_transaction(clear_db)

        # ノードの追加
        session.write_transaction(add_person_node, 'taro')

        # FRIEND関係の追加
        session.write_transaction(add_friend_relationship, 'taro', 'hanako')
        # 繰り返しで追加
        friend_list = ['jiro', 'haruka', 'sakura']
        for f in friend_list:
            session.write_transaction(add_friend_relationship, 'taro', f)

        # データの検索
        result = session.read_transaction(search_all)

    # 結果の確認
    for res in result:
        print(res)


if __name__ == '__main__':
    main()
【実行結果】
<Record n=<Node id=63 labels=frozenset({'Person'}) properties={'name': 'taro'}> r=<Relationship id=42 nodes=(<Node id=63 labels=frozenset({'Person'}) properties={'name': 'taro'}>, <Node id=67 labels=frozenset({'Person'}) properties={'name': 'sakura'}>) type='FRIEND' properties={}>>
<Record n=<Node id=63 labels=frozenset({'Person'}) properties={'name': 'taro'}> r=<Relationship id=41 nodes=(<Node id=63 labels=frozenset({'Person'}) properties={'name': 'taro'}>, <Node id=66 labels=frozenset({'Person'}) properties={'name': 'haruka'}>) type='FRIEND' properties={}>>
<Record n=<Node id=63 labels=frozenset({'Person'}) properties={'name': 'taro'}> r=<Relationship id=40 nodes=(<Node id=63 labels=frozenset({'Person'}) properties={'name': 'taro'}>, <Node id=65 labels=frozenset({'Person'}) properties={'name': 'jiro'}>) type='FRIEND' properties={}>>
<Record n=<Node id=63 labels=frozenset({'Person'}) properties={'name': 'taro'}> r=<Relationship id=39 nodes=(<Node id=63 labels=frozenset({'Person'}) properties={'name': 'taro'}>, <Node id=64 labels=frozenset({'Person'}) properties={'name': 'hanako'}>) type='FRIEND' properties={}>>
<Record n=<Node id=64 labels=frozenset({'Person'}) properties={'name': 'hanako'}> r=<Relationship id=39 nodes=(<Node id=63 labels=frozenset({'Person'}) properties={'name': 'taro'}>, <Node id=64 labels=frozenset({'Person'}) properties={'name': 'hanako'}>) type='FRIEND' properties={}>>
<Record n=<Node id=65 labels=frozenset({'Person'}) properties={'name': 'jiro'}> r=<Relationship id=40 nodes=(<Node id=63 labels=frozenset({'Person'}) properties={'name': 'taro'}>, <Node id=65 labels=frozenset({'Person'}) properties={'name': 'jiro'}>) type='FRIEND' properties={}>>
<Record n=<Node id=66 labels=frozenset({'Person'}) properties={'name': 'haruka'}> r=<Relationship id=41 nodes=(<Node id=63 labels=frozenset({'Person'}) properties={'name': 'taro'}>, <Node id=66 labels=frozenset({'Person'}) properties={'name': 'haruka'}>) type='FRIEND' properties={}>>
<Record n=<Node id=67 labels=frozenset({'Person'}) properties={'name': 'sakura'}> r=<Relationship id=42 nodes=(<Node id=63 labels=frozenset({'Person'}) properties={'name': 'taro'}>, <Node id=67 labels=frozenset({'Person'}) properties={'name': 'sakura'}>) type='FRIEND' properties={}>>

【実行後neo4jの状態】

neo4j python sample

実行結果を管理ツールで見てみると前の節で順番にCypher Queryを実行したのと同じグラフができていることが分かります。コンソール上の出力結果では、グラフに出てくる全てのノードと関係性が列挙されます。

以降でサンプルの説明をしていきます。

Pythonからneo4jへ接続する

neo4jを使用するためには、neo4jパッケージからGraphDatabaseをimportします。

from neo4j import GraphDatabase

以下の部分はneo4j.iniから接続情報を取得している部分です。neo4jというよりconfigparserモジュールの使い方というところですので、configparserの使い方が分からない方は「configparserによるconfigファイル管理」も参考にしてください。

    # ===== neo4jの設定取得
    config = configparser.ConfigParser()
    config.read('neo4j.ini')
    uri = config['NEO4J']['uri']
    user = config['NEO4J']['user']
    password = config['NEO4J']['password']

neo4jに接続するには、以下の記載の部分のようにドライバーを取得し、sessionにより接続します。

    driver = GraphDatabase.driver(uri, auth=(user, password))
    with driver.session() as session:

write_transactionでneo4jに書き込みを行う

neo4jへ書き込みを行う場合には、neo4jのセッションを作成した後にwrite_transactionを使用することで実行できます。

    with driver.session() as session:
        # データベースのクリア
        session.write_transaction(clear_db)

        # ノードの追加
        session.write_transaction(add_person_node, 'taro')

        # FRIEND関係の追加
        session.write_transaction(add_friend_relationship, 'taro', 'hanako')
        # 繰り返しで追加
        friend_list = ['jiro', 'haruka', 'sakura']
        for f in friend_list:
            session.write_transaction(add_friend_relationship, 'taro', f)

具体的には、処理に関する関数とその関数に渡す引数を渡します。関数自体を渡すことになりますので、session.write_transaction(clear_db)というようにclear_db関数に()はないことに注意しましょう。例えばclear_dbはデータベースをすべて削除する関数です。

具体的に渡す関数として実装しているのは以下の部分です。

def clear_db(tx):
    tx.run('MATCH (n) OPTIONAL MATCH (n)-[r]-() DELETE n,r')


def add_person_node(tx, name):
    tx.run('CREATE (p:Person {name: $name}) RETURN p', {'name': name})


def add_friend_relationship(tx, name, friend_name=None):
    if not friend_name:
        tx.run('CREATE (p:Person {name: $name}) RETURN p', {'name': name})
    else:
        tx.run('MATCH (p:Person {name: $name}) '
               'CREATE (p)-[:FRIEND]->(:Person {name: $friend_name})',
               name=name, friend_name=friend_name)

関数はtxという引数を受け取るように作成します。neo4jモジュールのwrite_transactionの方でtxが設定され、tx.runによりCypher Queryを実行します。clear_dbは単純にデータを全て削除するCypher Queryをtx.runに渡していることが分かるかと思います。

一方で、add_person_nodeやadd_friend_relationshipという方は、txの他に引数を受け取っています。受け取った引数は、Cypher Query上で$name、$friend_nameのように指定することで、受け取った引数を当てはめてCypher Queryを実行できます。Oracle等のDBMSをプログラムから扱ったことがある人であればバインド変数と思っていただければわかるかと思います。

read_transactionでneo4jからデータを取得する

neo4jの読み込みを行う場合には、neo4jのセッションを作成した後にread_transactionを使用することで実行できます。

    with driver.session() as session:
        # データの検索
        result = session.read_transaction(search_all)

    # 結果の確認
    for res in result:
        print(res)

read_transactionに渡している関数であるsearch_allは以下の部分で定義しています。

def search_all(tx):
    result = tx.run('MATCH (n) OPTIONAL MATCH (n)-[r]-() RETURN n,r')
    return [r for r in result]

考え方は上記のwrite_transactionと同様です。全データ検索用のCypher Queryをtx.runに渡しています。結果は「'neo4j.work.result.Result'」というもので返却されますが扱いやすいようにリスト内包表記でリストに返却して返しています。

以上が、Pythonでのneo4jでの簡単な使い方です。write_transactionやread_transactionに渡す関数を色々変えてもらえば任意のデータベース操作関数や専用のクラスを作成するといったことも可能になります。色々試してみてください。

Note

関数を渡すというのは高階関数(関数自体を引数/戻り値として扱う関数)について勉強したことがないと分かりにくいかもしれません。「デコレータ(decorator)の基本的な使い方」にてデコレータの説明をする過程で高階関数やクロージャの説明を含めて説明しているため参考にしてみてください。