「経路情報をネットワーク分析で遊んでみる」ための前準備

「経路情報をネットワーク分析で遊んでみる」ための前準備


「路線情報をネットワーク分析で遊んでみる」ための前準備

キャリアDiv. マーケティング企画統括部 データアナリティクス部 マーケティングテクノロジーグループのSEIDAです。
自分では、データエンジニアだと思って日々仕事しています。

この記事は、インテリジェンス Advent Calendar 2016の12/15の記事です。

ご縁がありリックテレコム様より今年初めに出版されたNeo4jの書籍に参加させていただきました。
グラフ型データベース入門 Neo4jを使う

ネットワーク分析にチャレンジしたい

さて、グラフデータベースを触っているとネットワーク分析をしてみたいと思うことがあります。
とっつきやすい題材として、「路線情報をコミュニティ分割したら都道府県とは違うカテゴライズができるのでは?」という仮説をもって、ネットワーク分析をする前準備をします。

路線情報を入手する.データ加工する

まずは、路線情報を入手する必要があります。
駅データ.jp様の路線データ、駅データ、接続駅データを用意します。

データ上、複数路線が入る駅が別々の駅に分かれているので、Neo4jを使って1つの駅にまとめます。
neo4jのインストールの仕方とかは、書籍を読んでいただければと思います。

// 処理速度を上げるために事前にINDEXを作成しておく
CREATE INDEX ON :STATION(station_g_cd);
CREATE INDEX ON :STATION(station_cd);
CREATE INDEX ON :STATION(line_cd);
 
// 駅のノードを作成する。
// 一意なコード「station_g_cd」をもつノードが無ければ作成。あれば路線ごとの駅コード「station_cd」にまとめていく
USING PERIODIC COMMIT 1000
LOAD CSV WITH HEADERS FROM
'file:/path/station20161107free.csv' as line
MERGE (s:STATION {station_g_cd : line.station_g_cd})
ON CREATE SET
s.station_cd = [line.station_cd],
s.station_name = line.station_name,
s.line_cd = [line.line_cd],
s.pref_cd = line.pref_cd,
s.post = line.post,
s.address = line.add,
s.lon = toFloat(line.lon),
s.lat = toFloat(line.lat)
ON MATCH SET
s.station_cd = s.station_cd + line.station_cd,
s.line_cd = s.line_cd + line.line_cd
;
 
MATCH(s:STATION) set s.company_cd = [], s.line_name = [];
 
// 路線コードと路線名を追加していく
LOAD CSV WITH HEADERS FROM 'file:/path/line20161107free.csv' as line
MATCH(s:STATION) WHERE line.line_cd in s.line_cd
SET s.company_cd = s.company_cd + line.company_cd ,s.line_name = s.line_name + line.line_name;
 
// 路線ごとのリレーションを作成する(遅いです。。。)
USING PERIODIC COMMIT 1000
LOAD CSV WITH HEADERS FROM 'file:/path/join20161107.csv' as line
MATCH(s:STATION) where line.station_cd1 in s.station_cd
MATCH(t:STATION) where line.station_cd2 in t.station_cd
MERGE (s)-[:LINE {line_cd: line.line_cd}]-(t);
 
// 路線名を追加する
USING PERIODIC COMMIT 1000
LOAD CSV WITH HEADERS FROM 'file:/path/line20161107free.csv' as line
MATCH (s)-[l:LINE {line_cd : line.line_cd}]-(t)
SET l.line_name = line.line_name;

Neo4jのWebブラウザで見るとこんな感じです。

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-12-14-19-01-28

見づらいですね。。。
ちゃんとしたネットワークのビジュアライズツールで可視化します。

Cytoscapeで可視化してみる

ネットワーク分析、ビジュアライズができる「Cytoscape」で可視化します。

まず、Cytoscape用にデータをNeo4jからCSVでエクスポートします。
neo4j-shellの拡張コマンドを追加します。
neo4j-shell-tools

ビルド済みjarのzipファイルがあります。解凍してjarをlib配下に配置します。

次をneo4j-shellで実行すると、MATCH式で返されたものがCSVファイルに出力されます。

import-cypher -o s.csv match (s:STATION) return id(s) as id, s.station_g_cd as station_g_cd, s.station_name as station_name, s.post as post, s.pref_cd as pref_cd, s.address as address ,s.lon as lon, s.lat as lat, round(s.lon * 1000000) / 100 as x, round(s.lat * toInt("-1000000")) / 100 as y
import-cypher -o c.csv match(s:STATION)-[r:LINE]->(t:STATION) return id(r) as id, r.line_cd as line_cd, r.line_name as line_name, s.station_g_cd as station_cd1, t.station_g_cd as station_cd2

ダブルクォテーションで囲まれているとCytoscapeでうまくいかないので削除しまいす。

$ tr -d '"' < c.csv > connect.csv
$ tr -d '"' < s.csv > station.csv

Cytoscapeの使い方は以下を参考にしてください。
http://qiita.com/keiono/items/29286f49b15a5b13c987

Cytoscapeにインポートします。

ネットワークの作成

「File > import > network > file」を選択し、インポートファイルにconnect.csvを選択します。

インポートする/しないのカラム選択や、型を指定できます。ポイントは、line_cdをinteractionに指定することです。

id : meaning(not imported)
linecd : DataType(string) meaning(interaction)
station
cd1 : meaning(Source)
station_cd2 : meaning(Target)

次にノードの属性を追加します。

「File > import > table > file」を選択し、インポートファイルにstation.csvを選択します。
こちらも以下のように指定します。

id : meaning(not imported)
key: stationgcd

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-12-15-19-58-47

もう少しです

Styleでノード位置を整えます。

「Properties」のクリックし、「X Location」「Y Location」を追加します。
「Column」に、それぞれカラムX、カラムYを設定します。
「Mapping Type」には、「Passthrough Mapping」を設定します。

こんな感じになります。

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-12-15-20-01-06

Rでネットワーク分析(コミュニティ分析)してみる。

CytoscapeからRに読み込む用のデータをエクスポートします。
「File > Export > Network」
GraphML形式でエクスポートしてください。

このままではRで読み込むとエラーとなるので、ファイルをエディタで開いて修正します。

  • NameSpaceを追加する
    <graphml xmlns=”http://graphml.graphdrawing.org/xmlns”>

  • Keyのforが全てnodeなので、edge・graphに直す。

  • edgedefault=”undirected”にする

Rのigraphライブラリを使ってコミュニティ分割してみます。

> library(igraph)
> g <- read.graph("graph.graphml", format="graphml")
> g<-simplify(g, remove.multiple=T, remove.loops=T)
> multilevel.community(g)
 
IGRAPH clustering multi level, groups: 73, mod: 0.96
+ groups:
  $`1`
   [1] "9992715" "9992714" "9992713" "9992712" "9992711" "9992710" "9992709" "9992708" "9992707" "9992706"
  [11] "9992705" "9992704" "9992703" "9992702" "9992701"
 
  $`2`
    [1] "9991507" "9981910" "9981909" "9981908" "9981907" "9981906" "9981903" "9981831" "9981830" "9981834"
   [11] "9981827" "9981826" "9981825" "9981824" "9981823" "9981820" "9981819" "9981818" "9981817" "9981816"
   [21] "9981815" "9981814" "9981813" "9981812" "9981811" "9981810" "9981809" "9981808" "9981807" "9981806"
   [31] "9981805" "9981804" "9981803" "9981802" "9981905" "9981733" "9981732" "9981731" "9981730" "9981729"
   [41] "9981728" "9981727" "9981726" "9981725" "9981724" "9981723" "9981722" "9981721" "9981720" "9981719"
  + ... omitted several groups/vertices

それっぽく分割されますね。

では、良い分析ライフを!!