SHIROBAKO大好き人間のブログ

SHIROBAKOが好きなエンジニアによる技術ブログ

Neo4jで遊んでみた

概要

Neo4jというDBをご存知でしょうか? neo4j.com

自分は名前だけは知っていたのですが、触ったことがなかったので試しに遊んでみることにしました

Neo4jとは

Neo4jはグラフ型のDBです

ここでいうグラフとは、グラフ理論のことでノード(点)とエッジ(辺)で表されるデータのことをいいます

Neo4jはグラフを扱うのに特化したDBで、RDBとはかなり異なるDBになっています

その分、グラフに関する操作はやりやすい設計になっており、グラフで表されるデータの複雑な操作や分析がやりやすくなることが期待できます

今回扱うテーマ

何かテーマを用意しないと面白くないので、今回は「東京の駅で無くなったら一番困る駅は?」というのをテーマにしてみます

東京って駅がたくさんありますよね

その中でも無くなったら一番困る駅ってどこでしょうか?

おそらく、新宿や東京や品川あたりが候補に出てくると思います

今回はそれをグラフ理論の考え方に基づいて調べていきましょう



このテーマをグラフ理論の言葉で言い換えるとこうなります

「東京の駅で媒介中心性が一番高いのはどの駅?」

媒介中心性(Betweenness centrality)とは、あるノードがどれくらい他のノード間の最短経路になっているかを表す指標です

ざっくり言ってしまうと、「そのノードが無くなったときに、どれくらい遠回りをする羽目になるか」を表します

今回の東京の駅の例にはぴったりの指標ですね

媒介中心性の詳しい説明や定義はWikipediaがよくまとまっていたので、そちらを参照してください

en.wikipedia.org

やった作業の流れ

ここからは実際にNeo4jを使って媒介中心性を求めていきます

1.データを用意する

まずは駅のデータが無ければ始まりません

今回は駅データ.jpのデータを使わせていただきました

ekidata.jp

無料会員登録をすれば、駅の情報とどの駅がつながっているかのデータをダウンロードすることができます

Neo4jで読み込ませるため、ダウンロードしてきたデータを加工してこんなcsvにしておきます

各行は電車でつながっている駅を表します

$ head join_list.csv
station_name_x,station_name_y
東京,新橋
新橋,品川
大崎,五反田
五反田,目黒
目黒,恵比寿
恵比寿,渋谷
渋谷,原宿
原宿,代々木
代々木,新宿

2. Neo4jを起動

データが用意できたので、Neo4jを起動します

今回はお手軽にdockerで起動します

docker run \
    --name testneo4j \
    -p7474:7474 -p7687:7687 \
    -d \
    -v $HOME/neo4j/data:/data \
    -v $HOME/neo4j/logs:/logs \
    -v $HOME/neo4j/import:/var/lib/neo4j/import \
    -v $HOME/neo4j/plugins:/plugins \
    --env NEO4J_AUTH=neo4j/test \
    --env NEO4JLABS_PLUGINS='["graph-data-science"]' \
    neo4j:latest

dockerの起動オプションはこちらを参考にしました

How-To: Run Neo4j in Docker - Developer Guides

また、dockerではなくバイナリが良いという方はこちらからダウンロードできます

Neo4j Desktop Download - Launch and Manage Neo4j Databases



Neo4jを起動して、ブラウザでlocalhost:7474にアクセスするとこんな画面が出てきます

f:id:phoro3:20210620082838p:plain

ここからクエリを入力することで、データをロードしたりデータに対する操作を行うことができます

3. Neo4jにデータをロード

手順1で作成したcsvをNeo4jにロードします

Neo4jではSQLではなく、Cypher queryというクエリでDBを操作します

データをロードするクエリはこのような形になっています

LOAD CSV WITH HEADERS FROM 'file:///join_list.csv' AS row
MERGE (s1:Station {name: row.station_name_x})
MERGE (s2:Station {name: row.station_name_y})
MERGE (s1)-[r:CONNECTED]->(s2)
RETURN s1,r,s2

MERGE (s1:Station {name: row.station_name_x})で駅の情報をノードとして登録し、 MERGE (s1)-[r:CONNECTED]->(s2)で駅と駅の間をエッジで繋いでいます

このクエリをNeo4j上で実行すると、DBにデータがロードされるとともに、グラフの形を視覚的に確認できます

f:id:phoro3:20210620091703p:plain

実行した結果をすぐに確認できるのは便利ですね

4. 媒介中心性を計算

ロードしたデータを使って媒介中心性を計算します

Neo4jにはGraph Data Science Library (GDS)というライブラリが付属しており、中心性のような基本的な計算は簡単にできるようになっています

今回はこれを使っていきます

媒介中心性を計算するクエリはこのようになります

CALL gds.graph.create('stationGraph', 'Station', {CONNECTED: {orientation: 'UNDIRECTED'}})

CALL gds.betweenness.stream('stationGraph')
YIELD nodeId, score
RETURN gds.util.asNode(nodeId).name AS name, score
ORDER BY score DESC

gds.graph.createで計算用にグラフをメモリにロードし、gds.betweenness.streamで媒介中心性を計算するイメージです

このクエリを実行した結果は以下のようになりました

╒════════════════╤══════════════════╕
│"name"          │"score"           │
╞════════════════╪══════════════════╡
│"新宿"            │97896.80592681374 │
├────────────────┼──────────────────┤
│"東京"            │61886.951565744006│
├────────────────┼──────────────────┤
│"吉祥寺"           │53840.41960233269 │
├────────────────┼──────────────────┤
│"三鷹"            │49887.98300653595 │
├────────────────┼──────────────────┤
│"国分寺"           │47959.88300653591 │
├────────────────┼──────────────────┤
(以下省略)

新宿、東京のスコアが高いのは予想通りですが、吉祥寺や三鷹が上位に入ってくるのは少し意外ですね

感想

媒介中心性の計算までやることで、Neo4jの大まかな使い方は把握できました

今回の規模だとPythonでNetoworkXのようなグラフに特化したライブラリを使う方が便利です

ただ、例えば業務で複数の人が同じデータを参照する場合はNeo4jに格納した方が、効率的に作業が進められそうですね

補足

  • dockerなどを使わず、とりあえずNeo4j触りたいだけのときはブラウザからsandbox版を試すことができます
    • Neo4jの雰囲気だけ知りたいという方はぜひ試してみてください
  • 今回はGDSがあったので、全てNeo4j内で操作が完結しましたが、実際はアプリケーションからDBの値を読んで、色々操作したいという場合もあると思います