グラフデータの視覚化について(前編:標準的なグラフ表現) #neo4j
この記事は1年以上前に投稿されました。情報が古い可能性がありますので、ご注意ください。
グラフデータベースの広がりと共にグラフデータの視覚化に関する様々なニーズが急浮上しつつあります。
いくつか、例をあげてみます。
-
標準的なグラフを描く
より多く情報を伝えるためにテクスチュアル表現に加えてグラフを表示したい場合があります。
-
ネットワークデータの分析を行う
とても複雑なネットワークデータ構成を適切に視覚化することによって様々な洞察を得ることができます。
Analyzing data networks -
MLなどで分析を行った結果をグラフで表現する
標準的な表現では意味が伝わらないですが、MLなどの結果値がグラフの場合があります。
Graph Layouts with Neo4j Graph Algorithms and Machine Learning
* 検索などのインターフェースをグラフで表現する
検索などで標準的なグラフ表現をユーザインターフェースとして取り入れていることもあります。例示は、Neo4j DeskTopのBloomです。
Neo4j Bloom
- グラフを用いてダイアグラム的に表現する
形も情報としてとても重要な位置を占めるものがあります。分かり易い例で言えば、組織図やネットワークのようなものです。このような表現は、単なるネットワークグラフで表現したら、情報の伝達力が致命的に落ちてしまいます。ダイアグラムをグラフデータとして格納した場合、クエリが使えるから情報処理能力は飛躍的に上がるでしょうが、グラフデータを動的にダイアグラムに変換することは容易ではありません。
[ダイアグラム]
ダイアグラフとは、「グラフを用いたデータの視覚化」と言えます。互いに繋がっているデータの表現ということではグラフと変わりませんが、ノードをリレーション配置する位置や形までも、とても重要な情報の一部になります。標準的なネットワークグラフでは、視覚化が困難です。
今回は、様々なグラフ表現のなかでも、対極にある標準的なグラフ表現とダイアグラム的な表現に絞って、主に方法論を中心に実装例を紹介するような形で構成しています。
便宜上、2部構成にしています。
- 前編(今回)
標準的なグラフ表現 - 後編(次回)
ダイアグラム的な表現
SVGで描画してみる
SVG(Scalable Vector Graphics)とは
ベクター画像(Vector graphics)と呼ばれる画像形式は、画像を点の集合で表現するのではなく、計算式によって色や線を表現しています。写真のようなたくさんの色を使う描写には不向きですが、アイコンや図形の描画には何ら問題もなく、拡大したりしも画像がぼやけたりしないメリットがあります。使い分けする領域はあるものの、現在のインターネットのコンテンツ表現では欠かせない存在になっています。
- ビットマップ画像
GIF/JPG/PNG…画像を点の集合で表示 - ベクタ画像
計算式によって色や線を表現
SVGでグラフを描いてみる
グラフを描くというテーマで調査をしていくと、自然に辿り着く先がSVGです。
まず、2つの円(ノード)を描いてみます。
Webブラウザー上に表示しますので、特に環境を容易する必要もありません。
次のようなコードを実行するだけでいいです。
<!DOCTYPE html> <html> <body></p> <h1>My First Graph</h1> <svg width="500" height="100"> <circle cx="50" cy="50" r="40" stroke="green" stroke-width="4" fill="yellow"/> <circle cx="300" cy="50" r="40" stroke="green" stroke-width="4" fill="blue"/> </svg> <p></body> </html>
このコードは、次のように2つの円(ノード)を描きます。
幅500pxで高さ100pxのキャンバス上でcxとcyは、それぞれの座標です。そして、rは円の大きさです。
次は、ノードに対して線(リレーション)を引いてみます。
<!DOCTYPE html> <html> <body></p> <h1>My First Graph</h1> <svg width="500" height="100"> <circle cx="50" cy="50" r="40" stroke="green" stroke-width="4" fill="yellow"/> <circle cx="300" cy="50" r="40" stroke="green" stroke-width="4" fill="blue"/> <line x1="90" y1="50" x2="260" y2="50" style="stroke:rgb(255,0,0);stroke-width:2"/> </svg> <p></body> </html>
Webブラウザーで実行すると、無向グラフが表示されます。
線(リレーション)は、X軸を2つ指定(x1,x2)し、同じ高さで直線を引いています。
このようにSVGでは、描画する空間のサイズを定義し、座標を入力し、形や色などを設定することでグラフを描きます。
さらに詳しく知りたい人は、下記のリンクを参照してください。
[w3school]
SVG In HTML
しかし、理屈が分かったとしても、一々座標を指定するこのようなやり方では、複雑且つ大規模のグラフを動的に画くことは無理でしょう。
そこで、D3のような描画専用のライブラリが登場します。
D3でグラフを画く
D3(Data Driven Documents)とは
D3は、描画専用のライブラリです。
本格的なグラフを描いてみようとすると、遅かれ早かれ、D3に辿りつきます。
- Webブラウザーで動的コンテンツを描画するJavaScriptライブラリです。
- W3(World Wide Web Consortium)準拠の可視化ツールとしてSVG、JavaScript、HTML5、CSSなどを取り込んでいます。
- 最終的に出力された結果物に対して視覚的な調整ができます。
[参照サイト]
https://d3js.org/
[バージョンに注意]
D3は、現在v5ですがv3からv4の間には殆ど互換性がありません。書籍やソースを見る時には、v4以上であるかを確かめてからにしてください。
D3で無向グラフを描いてみる
まず、とてもシンプルな例をみてみます。
下記のリンクをクリックしてみてください。
Force Directed Graph with Labels
次のようなグラフが表示されます。とても小さくて申し訳ありませんが、データとグラフ表現の関係が分かり易い例です。
下記は実際のグラフデータです。ノードの属性値と、リレーションを描くためのソース(スタート)とターゲット(エンド)で構成されています。このようなグラフデータの型式は、全般的に同じです。
{ "nodes": [ {"sortBy": "Location", "id": "site"}, {"sortBy": "Location", "id": "C"}, {"sortBy": "Location", "id": "Building"}, {"sortBy": "Location", "id": "D"}, {"sortBy": "Location", "id": "Area"}, {"sortBy": "Location", "id": "Device"} ], "links": [ {"source": "site", "target": "C", "value": 1}, {"source": "site", "target": "Building", "value": 1}, {"source": "site", "target": "D", "value": 1}, {"source": "Building", "target": "Area", "value": 1}, {"source": "Area", "target": "Device", "value": 1} ] }
そして、ソースコードを見て頂きたいです。読めないよ、というが方がいらっしゃるかもしれませんが、言いたいことはノード配置の座標を一々指定したりしていないことです。ノードのサイズや色、ノード間の間隔、ラベルなどのルールを指定してあげれば、グラフは適切に描画してくれます。
<svg width="960" height="600"></svg> <script src="https://d3js.org/d3.v4.min.js"></script></p> <script> var svg = d3.select("svg"), width = +svg.attr("width"), height = +svg.attr("height"); var color = d3.scaleOrdinal(d3.schemeCategory20); var simulation = d3.forceSimulation() .force("link", d3.forceLink().id(function(d) { return d.id; })) .force("charge", d3.forceManyBody()) .force("center", d3.forceCenter(width / 2, height / 2)); d3.json("miserables.json", function(error, graph) { if (error) throw error; var link = svg.append("g") .attr("class", "links") .selectAll("line") .data(graph.links) .enter().append("line") .attr("stroke-width", function(d) { return Math.sqrt(d.value); }); var node = svg.append("g") .attr("class", "nodes") .selectAll("g") .data(graph.nodes) .enter().append("g") var circles = node.append("circle") .attr("r", 5) .attr("fill", function(d) { return color(d.group); }) .call(d3.drag() .on("start", dragstarted) .on("drag", dragged) .on("end", dragended)); var lables = node.append("text") .text(function(d) { return d.id; }) .attr('x', 6) .attr('y', 3); node.append("title") .text(function(d) { return d.id; }); simulation .nodes(graph.nodes) .on("tick", ticked); simulation.force("link") .links(graph.links); function ticked() { link .attr("x1", function(d) { return d.source.x; }) .attr("y1", function(d) { return d.source.y; }) .attr("x2", function(d) { return d.target.x; }) .attr("y2", function(d) { return d.target.y; }); node .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; }) } }); function dragstarted(d) { if (!d3.event.active) simulation.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; } function dragged(d) { d.fx = d3.event.x; d.fy = d3.event.y; } function dragended(d) { if (!d3.event.active) simulation.alphaTarget(0); d.fx = null; d.fy = null; } </script> <p>
あまりにも小さいからな実感がないという方は、こちらを実行してみてください。
Force Directed Graph with Labels
データが増えただけでコードは全く同じです(そもそも、こちらが参照元です)。
有向グラフを描いてみる
いわゆるアローヘッドグラフを描いてみたい方は、こちらを参照してください。
Force directed graph for D3.js v4 with labelled edges and arrows
グラフのサイズはコンパクトですが、アローヘッドグラフを描くための要素はすべて揃っています。
データを見てください。無向グラフと、フォーマットの構成は全く同じです。ソースコードは、アローヘッドの構成が加わるなど無向グラフよりもやや複雑になっています。
Neo4jと連携してアプリを作成してみる
概要
ここではNeo4jからデータを取得してグラフを描くPythoneの演習アプリを簡略に紹介します。もちろん、他にJava、JavaScript、.NET、Rubyなどのソースコードもあります。
[参照]
Example for Neo4j and Library Usage
Using Neo4j from Python
neo4j-examples/movies-python-bolt
事前準備
Pythonのアプリを起動する前に、Neo4jの映画データベースを構築した状態でNeo4jサーバーを起動しておく必要があります。
Neo4jサーバーは、次のサイトからダウンロードしてインストールできます。今回は、Neo4j Community v3.4.10、Windows版を利用しています。
Neo4j Download Center
Neo4jを実行します。
%NEO4J_HOME%\bin\neo4j console
Neo4j標準の映画グラフを作成します。
:play movie graph
次は、Pythonをインストールします。今回は、Python3.6.7をインストールしています。
python -V Python 3.6.7
pip -V pip 10.0.1 from c:\users\c-lee\appdata\local\programs\python\python36\lib\site-packages\pip (python 3.6)
Neo4j Driverをンストールします。今回は、v1.7.1です。
pip install neo4j
Virtualenvをインストールし、Pythonの仮想環境を作ります。
To need virtualenvs
ここでは、C:Works\neo4j-movie\のようにディレクトリを作成しています。
C:Works>mkdir neo4j-movie C:Works>cd neo4j-movie C:Works\neo4j-movie>pip install virtualenv Requirement already satisfied: virtualenv in c:\users\c-xxx\appdata\roaming\python\python36\site-packages (16.1.0)
neo4j-moviesプロジェクトを作成します。
C:\Works\neo4j-movie>c:\users\c-xxx\appdata\roaming\python\python36\site-packages\virtualenv.py neo4j-movies Using base prefix 'C:\Users\c-xxx\AppData\Local\Programs\Python\Python36' New python executable in C:\Works\neo4j-movies\Scripts\python.exe Installing setuptools, pip, wheel... done.
次のようにテンプレートが作られます。
neo4j-movies/bin neo4j-movies/include neo4j-movies/Lib neo4j-movies/Scripts neo4j-movies/tcl
次のサイトから映画アプリのソースをダウンロードしてneo4j-movie/bin/配下に配置します。
neo4j-examples/movies-python-bolt
次のような配置になります。
bin/static/index.html bin/index.html bin/movies.py bin/README.adoc bin/requirements.txt
movies.pyを開いてpasswordを適切に変更します。
driver = GraphDatabase.driver('bolt://localhost',auth=basic_auth("neo4j", password))
では、実行してみます。
neo4j-movies\bin>movies.py * Serving Flask app "movies" (lazy loading) * Environment: production WARNING: Do not use the development server in a production environment. Use a production WSGI server instead. * Debug mode: off * Running on http://127.0.0.1:8080/ (Press CTRL+C to quit)
ブラウザーから表示してみます。
http://127.0.0.1:8080/
次のような画面が表示されるはずです。
このアプリでは、Boltドライバ―を利用し、CypherクエリでNeo4jからデータを取得しています。返還値はJSON型式のノードとリンク(リレーション)のデータです。グラフは、D3ライブラリに食べさせてSVGをレンダリングしています。
{ "nodes": [ {"title": "Unforgiven", "label": "movie"}, {"title": "Gene Hackman", "label": "actor"}, {"title": "Richard Harris", "label": "actor"}, {"title": "Clint Eastwood", "label": "actor"}, {"title": "The Green Mile", "label": "movie"}, {"title": "Michael Clarke Duncan", "label": "actor"}, {"title": "Tom Hanks", "label": "actor"}, {"title": "Patricia Clarkson", "label": "actor"}, {"title": "Gary Sinise", "label": "actor"}, {"title": "Bonnie Hunt", "label": "actor"}, {"title": "David Morse", "label": "actor"}, {"title": "Sam Rockwell", "label": "actor"}, {"title": "James Cromwell", "label": "actor"}, {"title": "Joe Versus the Volcano", "label": "movie"}, {"title": "Meg Ryan", "label": "actor"}, {"title": "Nathan Lane", "label": "actor"} ...略 ], "links": [ {"source": 1, "target": 0}, {"source": 2, "target": 0}, {"source": 3, "target": 0}, {"source": 5, "target": 4}, {"source": 6, "target": 4}, {"source": 7, "target": 4}, {"source": 8, "target": 4}, {"source": 9, "target": 4}, {"source": 10, "target": 4}, {"source": 11, "target": 4}, {"source": 12, "target": 4}, {"source": 14, "target": 13}, {"source": 6, "target": 13}, {"source": 15, "target": 13} ...略 ]
Neo4jブラウザーはどのようにしているのか
非常に気になるところですが、Neo4jブラウザ-も、JSON型式のデータからグラフを描画しています。
Neo4jブラウザーから映画データベースに対して、次のようなクエリを実行してみます。
MATCH path = (n)-[r]->(m) RETURN path LIMIT 5
この結果は、次のようなグラフになります。
このグラフに対してNeo4jブラウザーからSVG型式でエクスポートしてみると、次のような内容です。このような結果がCypherから返ってくるわけではありません。
Neo4jブラウザーから、次のようなREST APIを実行してみます。
:POST /db/data/transaction/commit {"statements":[{"statement":"MATCH path = (n)-[r]->(m) RETURN path LIMIT 5","resultDataContents":["graph"]}]}
次のようなJSON型式のデータが返って来ます。
{"results":[{"columns":["path"],"data":[{"graph":{"nodes":[{"id":"0","labels":["Movie"],"properties":{"tagline":"Welcome to the Real World","title":"The Matrix","released":1999}},{"id":"8","labels":["Person"],"properties":{"born":1978,"name":"Emil Eifrem"}}],"relationships":[{"id":"7","type":"ACTED_IN","startNode":"8","endNode":"0","properties":{"roles":["Emil"]}}]}},{"graph":{"nodes":[{"id":"0","labels":["Movie"],"properties":{"tagline":"Welcome to the Real World","title":"The Matrix","released":1999}},{"id":"7","labels":["Person"],"properties":{"born":1952,"name":"Joel Silver"}}],"relationships":[{"id":"6","type":"PRODUCED","startNode":"7","endNode":"0","properties":{}}]}},{"graph":{"nodes":[{"id":"0","labels":["Movie"],"properties":{"tagline":"Welcome to the Real World","title":"The Matrix","released":1999}},{"id":"6","labels":["Person"],"properties":{"born":1965,"name":"Lana Wachowski"}}],"relationships":[{"id":"5","type":"DIRECTED","startNode":"6","endNode":"0","properties":{}}]}},{"graph":{"nodes":[{"id":"0","labels":["Movie"],"properties":{"tagline":"Welcome to the Real World","title":"The Matrix","released":1999}},{"id":"5","labels":["Person"],"properties":{"born":1967,"name":"Lilly Wachowski"}}],"relationships":[{"id":"4","type":"DIRECTED","startNode":"5","endNode":"0","properties":{}}]}},{"graph":{"nodes":[{"id":"0","labels":["Movie"],"properties":{"tagline":"Welcome to the Real World","title":"The Matrix","released":1999}},{"id":"4","labels":["Person"],"properties":{"born":1960,"name":"Hugo Weaving"}}],"relationships":[{"id":"3","type":"ACTED_IN","startNode":"4","endNode":"0","properties":{"roles":["Agent Smith"]}}]}}]}],"errors":[]}
もっと詳しく知りたい人は、下記を参照してください。
[参照サイト]
* Graph Visualization for Neo4j
* Neo4j Browser soruce code
まとめ
今回は、様々なグラフ表示のニーズがあるなかで、標準的なグラフ表現の方法について簡略に紹介しました。Cyhperクエリからの結果値がJSONであることは多少がっかりだったかも知れません。しかしながら、標準的なネットワークグラフ作成は、比較的に簡単であるとも言えます。次回は、グラフのダイアグラム的な表現について紹介させて頂きます。