2020年2月9日日曜日

PythonとPlotlyでインタラクティブなコロプレス図を作成する

Pythonでシェープファイルから都道府県境界図を作成するではGeoPandasで都道府県境界図のシェープファイルを作成し、さらにコロプレス図をプロットしてみた。今回はPlotlyを利用してインタラクティブなコロプレス図を作成してみる。Plotlyを使うとPlotlyでStack OverflowのDeveloper Surveyを可視化するのように、マウスオーバーでテキストを表示したり、拡大・縮小などを行える図を作成できる。


環境


Windows10(1903)のWSL(Ubuntu 18.04)とJupyter Notebookを使用。



必要なライブラリのインストール


GeoPandasとPlotlyをインストールする。両方ともpipでインストールできる。


バージョンはそれぞれ以下の通り。



GeoJSON形式の境界図の作成


Plotlyでコロプレス図を作成するにはGeoJSON形式のデータが必要になるので、シェープファイルではなくGeoJSON形式の都道府県境界図を用意する。GeoPandasではGeoJSON形式でも保存できる。Pythonでシェープファイルから都道府県境界図を作成するで保存した都道府県境界図のシェープファイルjpnmap.shpを読み込んで、GeoJSON形式で保存し直す。

import geopandas as gpd

jpn = gpd.read_file('jpnmap.shp', encoding = 'shift-jis')

# コロプレス図で使用する人口密度の計算
# AREA(面積)の単位は平方メートルなので平方キロメートルに直して計算
jpn['DENSITY'] = jpn['JINKO'] / (jpn['AREA'] / (10**6))

# コロプレス図を作成するときにgeometryとの対応をとるためのID
jpn['ID'] = jpn.index.values

# GeoJSON形式で保存
jpn.to_file('jpnmap.geojson', driver='GeoJSON')


GeoJSON形式ファイルの軽量化


GeoJSON形式の境界図ができたのでコロプレス図の作成に取りかかりたいところだが、ここで問題がある。作成したGeoJSON形式のファイルは200MBほどあり、これを読み込んでJupyter Notebookでコロプレス図を表示しようとしたらブラウザが落ちてしまった。データサイズが大きすぎてメモリ消費が増えすぎたことが原因のようなので、GeoJSON形式境界図の軽量化を行う。

軽量化にはTopoJSONを使用する。TopoJSONはGeoJSONの拡張形式で、GeoJSONからTopoJSONへの変換などいくつかのコマンドラインツールが用意されている。それらを使ってジオメトリのデータを間引く簡略化を行い、ファイルを軽量化する。

まずはTopoJSONのインストール。TopoJSONのインストールにはnpmが必要なので先にインストールする。


続いてTopoJSONをインストール。


以下のコマンドでGeoJSON形式の境界図データをTopoJSON形式に変換して簡略化する。geo2topoでTopoJSON形式に変換、toposimplifyで簡略化。toposimplifyのオプションPについてはtoposimplifyにドキュメントがあるが、0から1の範囲の値を指定する。この値が小さいほど簡略化の程度が大きくなる。オプションfを指定すると、簡略化の閾値よりも小さな分離されたリング(ポリゴンの境界線)を取り除く。jpnはオブジェクト名で、任意の名前でOK。


簡略化したらGeoJSON形式へ再変換する。


結果、200MBを超えていたファイルサイズが約10MBになった。


Plotlyでインタラクティブなコロプレス図を作成


PlotlyにExpressというAPIがあり、そのAPIのchoropleth_mapboxというメソッドを使うとGeoJSON形式のデータを読み込んでコロプレス図を作成できる。以下は簡略化したGeoJSON形式の境界図jpnmap_simplified.geojsonを使ってコロプレス図を作成するPythonコード。
import json
import geopandas as gpd
import plotly.express as px

# コロプレス図の中心
CENTER = {'lat': 36, 'lon': 140}

jpn = gpd.read_file('jpnmap_simplified.geojson')

# GeoJSON形式で保存したときにGeoDataFrameのindexが保存されず0からのindexがふられるのでindexを再設定する
# 今回はたまたまもとのindexが0からはじまる数字なので再設定しなくても動作する
jpn.set_index('ID', drop=False, inplace=True)
 
print(jpn.info())
display(jpn.head())

# color: 表示対象の項目
# locations: GeoJSONのIDに対応する項目
# geojson: GeoJSONをdictに変換したもの。このデータではID(0-46)がふられている
fig = px.choropleth_mapbox(jpn, geojson=json.loads(jpn['geometry'].to_json()), locations='ID', color='DENSITY',
      title = '人口密度',
      hover_name = 'PREF_NAME',
      color_continuous_scale="OrRd", 
      mapbox_style='white-bg',
      zoom=4, 
      center=CENTER,
      opacity=0.5,
      labels={'DENSITY':'人口密度'}
      )
fig.update_layout(margin={'r':10,'t':40,'l':10,'b':10,'pad':5})
fig.show()

次のようなコロプレス図が作成された。マウスオーバーするとchoropleth_mapboxのhover_name、locations、labelsで指定した項目が表示される。


0 件のコメント:

コメントを投稿