fbpx

グラフデータの視覚化について(前編:標準的なグラフ表現) #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をインストールしています。

About the offical drivers

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であることは多少がっかりだったかも知れません。しかしながら、標準的なネットワークグラフ作成は、比較的に簡単であるとも言えます。次回は、グラフのダイアグラム的な表現について紹介させて頂きます。

Author

モダンアーキテクチャー基盤のソリューションアーキテクトとして活動しています。

[著書]
・Amazon Cloudテクニカルガイド―EC2/S3からVPCまで徹底解析
・Amazon Elastic MapReduceテクニカルガイド ―クラウド型Hadoopで実現する大規模分散処理
・Cypherクエリー言語の事例で学ぶグラフデータベースNeo4j
・Neo4jを使うグラフ型データベース入門(共著)
・RDB技術者のためのNoSQLガイド(共著)

leeの記事一覧

新規CTA