Edited at

ElasticsearchとPython使えば生活圏推定を簡単にできる話

More than 3 years have passed since last update.


概要

なんか書いてみようということで、Elasticsearchに行動履歴の位置情報を登録すれば、いい感じに利用できる上に、いい感じに可視化もできるという話をします。


前提知識

logo-elastic.png

今回Elasticsearchを利用するので、簡単に紹介。

ElasticsearchはApache Solrとよく比較される全文検索エンジンの一つです。スキーマフリーですべての入出力がREST&JSONになっています。またJavaで実装されています。

インストールはyumでもbrewでも簡単に出来ます。利用したい環境に合わせて調べてみてください。

ちなみにElasticsearchのGUIプラグインのelasticsearch-headが便利なので合わせて入れておくと良いです。


Elasticsearchの設定

Elasticsearchを起動できたら、利用するindex(データベースで言うテーブルのようなもの)の設定をしていきます。

その為にまずindexのマッピング方法をjsonで作成します。

今回は以下の様なデータセットのログがあることを想定します。


sample_log

{

"id":1,
"uuid":"7ef82126c32c55f7272d5ca5dd5e40c6",
"time":"2015-12-03T04:21:01.641Z",
"lat":35.658546,
"lng":139.729281,
"accuracy":47.126048
}

この様なデータセットが上手くマッピングされるように、フィールド毎のタイプなどの設定をします。今回は以下の様なマッピングの設定にしました。geoというデータセットのタイプのマッピングの設定をしてあげているというイメージです。


geo_mapping.json

{

"geo" : {
"properties" : {
"id" : {
"type" : "integer"
},
"uuid" : {
"type" : "string",
"index" : "not_analyzed"
},
"time" : {
"type" : "date",
"format" : "date_time"
},
"location" : {
"type" : "geo_point"
},
"accuracy" : {
"type" : "double"
}
}
}
}

少し解説すると、uuidフィールドがnot_analyzedと設定しているのは、ユニークな値で形態素解析されたくない値なのでこうしています。

また今回重要となるのがlocationフィールドのtypeです。geo_pointはElasticsearchが提供しているtypeで、経度緯度をセットで登録することで利用できます。このフィールドのtypeにしておくことで便利な検索ができるようになったりします。詳しくは後で。

マッピングの設定が作成できたら、それを利用してindexを作成します。index名は今回test_geoにしてます。Elasticsearchを起動している状態で以下の様なcurlを投げてあげれば作成完了です。


indexの作成

curl -XPOST 'localhost:9200/test_geo' -d @geo_mapping.json



データの登録

データはログファイルとして所持していることを想定し、ログファイルから作成したindexにデータを登録していきます。

今回公式のpythonのclientがあるのでそれを利用してみます。

これはpipで簡単にインストールできます。


インストール

pip install elasticsearch


これを利用して登録していくプログラムは以下の用な感じです。


regist_es.py

import json

import sys
from elasticsearch import Elasticsearch

es = Elasticsearch()
index = "test_geo"
doc_type = "geo"

f = open('var/logs.json', 'r')

_line = f.readline()
while _line:
data = json.loads(_line)
_line = f.readline()
f.close()

for value in data:
lat = value['lat']
lon = value['lng']
del value['lat']
del value['lng']
value.update({
"location" : {
"lat" : lat,
"lon" : lon
}
})
es.index(index=index, doc_type=doc_type, body=value, id=value['id'])


注意する点はgeo_pointに適した形にするために、latlonをまとめてlocationにしている点です。

このプログラムを実行すればlogs.jsonにあった行動履歴の情報がElasticsearchに登録されます。とても簡単ですね。


Kibanaで可視化

データの登録が出来たので、あとは煮るなり焼くなりです。

KibanaはElasticsearchに登録されているデータを可視化してくれる、公式の出している可視化ツールです。

現在はKibana4が出ているので、せっかくなので最新版を取ってきてみると良いと思います。

取得したら./bin/kibanaを実行するだけで5601番ポートでHTTPサーバが起動します。詳しい設定の方法など

実際に起動したら、適当なブラウザでアクセスするとダッシュボードの設定ができます。

色々といじって遊んでみると、下みたいなヒートマップなどが簡単に作成できたりします。

heatmap.png

histgram.png


生活圏推定

せっかくデータを登録したので利用もしてみます。

今回はこの論文(モバイル端末向けの位置情報を利用した情報推薦システム)の生活圏推定なんかをやってみます。

位置情報をgeo_typeで登録したので以下の様な、特定の位置から何km以内のデータを取得するというようなクエリが投げれます。

query = {

"from":0,
"query": {
"filtered" : {
"query" : {
"simple_query_string" : {
"query" : uuid,
"fields" : ["uuid"],
}
},
"filter" : {
"geo_distance" : {
"distance" : 10 + 'km',
"geo.location" : {
"lat" : lat,
"lon" : lon
}
}
}
}
}
}

実際にこれを利用した生活圏推定とかした結果は以下みたいになりました。


Sample結果

Stage1

35.653945 , 139.716692
半径(km): 5.90

Stage2
35.647367 , 139.709346
半径(km): 1.61

重心使う場合
35.691165 , 139.709840
半径(km): 8.22

昼: (104)
35.696822 , 139.708228
半径(km): 9.61
夜: (97)
35.685100 , 139.711568
半径(km): 6.77

最寄り駅(昼): 東新宿
最寄り駅(夜): 新宿御苑前



感想

Elasticsearchはとても簡単に設定できる上に、利用するのも簡単です。

Elasticsearch凄い。Kibana凄い。しかも新プロダクト(Beats)もあるらしい。

データの量にもよりますが、Elasticsearchにとりあえず登録してみると色々と便利です。

ログなんかはfluentdとかで自動に登録することもできるので、組み合わせれば色々出来そうです。

記事書くのむずかしい…