引き続き、 R の可視化を Python に持ってくるシリーズ。R には以下のようなパッケージがあり、地図上へのリーフレット配置やコロプレス図の描画がカンタンにできる。それぞれの概要はリンク先を。
{leaflet}
: リーフレット配置{choroplethr}
: コロプレス図の描画
これを Python でやりたい。調べてみると folium
というパッケージが上記 両方をサポートしているようなので使ってみる。
インストール
pip で。
pip install folium
準備
以降の操作は Jupyter
から行う。まずはパッケージをロードする。
import numpy as np import pandas as pd import folium
folium
は プロット結果を html
としてエクスポートすることを想定して作成されているようだ。そのため、結果を Jupyter
上に埋め込みたい場合は 以下のような関数を定義する必要がある。
from IPython.display import HTML def inline_map(m): m._build_map() # 中間生成される json が必要なプロットがあるため、一度 html として書き出し m.create_map(path='tmp.html') iframe = '<iframe srcdoc=\"{srcdoc}\" style=\"width: 100%; height: 400px; border: none\"></iframe>' return HTML(iframe.format(srcdoc=m.HTML.replace('\"', '"')))
リーフレット
手順は以下のようになる。
folium.Map
で地図を描画する範囲を緯度経度/ズームにより指定Map.simple_marker
で緯度経度を指定してリーフレットを配置 (複数配置する場合は繰り返し)Map.create_map
で地図を含むhtml
を生成 (Jupyter
上に描画する場合は上で定義した関数inline_map
を呼ぶ )
m = folium.Map(location=[33.763, -84.392], zoom_start=17) m.simple_marker([33.763006, -84.392912], popup='World of Coca-Cola') inline_map(m)
補足 Jupyter
上ではスクロール / 拡大縮小できる。
既定では OpenStreetMap が利用されるが、Mapbox や maps.stamen を使うこともできる。また、リーフレットのマーカーとしては以下のものが利用できる。
- Simple Markers: シンプルなマーカー (上のもの)
- Circle Markers: 円形のマーカー
- Polygon Markers: 多角形のマーカー
- Lat/Lng Popups: 緯度経度を表示するマーカー
- Click-for-Marker: クリックで配置可能なマーカー
- Vincent Popups: Vincent によるプロットを埋め込めるマーカー
それぞれの描画サンプルは README で確認することができる。
コロプレス図
コロプレス図を描くには以下2つのデータソースが必要である。
GeoJSON
もしくはTopoJSON
形式のファイル- コロプレス図を色分けするための値を含む
pandas
のDataFrame
サンプルとして 国別のマクドナルドの店舗数 をプロットする。
GeoJSON
ファイルの準備
国別にプロットするため、国別の GeoJSON
ファイルがほしい。以下リポジトリの countries.geo.json
をローカルに保存して使う。
中身は 以下のように ISO 3166-1 alpha-3 の国コードを id としたデータとなっている。
{"type":"FeatureCollection", "features":[{"type":"Feature","id":"AFG", "properties":{"name":"Afghanistan"}, "geometry":{"type":"Polygon","coordinates":[[[61.210817,35.650072],[62.230651,35.270664],...
DataFrame
の準備
DataFrame
は 上で準備した GeoJSON
と紐づけるためのキー (一般には GeoJSON
の id ) と値の 2 列をもつ必要がある。 国別のマクドナルドの店舗数 データを pd.read_html
で読み込む。
url = "https://en.wikipedia.org/wiki/List_of_countries_with_McDonald%27s_restaurants" df = pd.read_html(url, header=0, index_col=0)[0] # 列名を変更 df.columns = ['Country', 'Date', 'First outlet location', 'Number', 'Source', 'Note', 'CEO'] df[['Country', 'Number']].head() # Country Number # # # 1 United States 14267 # 2 Canada 1427 # 3 Puerto Rico 108 # 4 U.S. Virgin Islands 6 # 5 Costa Rica 54
こちらのデータには国名が入っているため、GeoJSON
と紐づけるためには ISO 国コードに変換する必要がある。変換には pycountry
を使う。インストールしていない方は pip で。
import pycountry pycountry.countries.get(name='Japan').alpha3 # 'JPN' # データ中の国名が pycountry のものと違う場合の mapper countries = {'United Kingdom': 'United Kingdom', 'Russia': 'Russian Federation', 'South Korea': 'Korea, Republic of', 'Taiwan': 'Taiwan, Province of China', 'Vietnam': 'Viet Nam', } def f(x): for k, v in pd.compat.iteritems(countries): if k in x: x = v try: return pycountry.countries.get(name=x).alpha3 except KeyError: return np.nan # 国コードへの変換 df.loc[:, 'Code'] = df['Country'].apply(f) # 国コードに変換できなかったデータは除外 df = df.dropna(subset=['Code']) # 欠損値を 0 でパディング df.loc[:, 'Number'] = df['Number'].fillna('0') # 数値に変換できない文字列があるため、余計な文字を削除 df.loc[~df['Number'].str.isdigit().values, 'Number'] = df['Number'].str.replace('[+,]', '') # 数値 (float) 型に変換 df.loc[:, 'Number'] = df['Number'].astype(float) df[['Code', 'Country', 'Number']].sort('Number', ascending=False).head() # Code Country Number # # # 1 USA United States 14267 # 8 JPN Japan 3164 # 49 CHN China 2000 # 11 DEU Germany 1468 # 2 CAN Canada 1427
これで、国コード / プロットする値をもつ DataFrame
が準備できた。アメリカすごいな、、、。
コロプレス図の描画
Map.geo_json
で コロプレス図を描画するレイヤーを Map
に追加できる。ここで使っている引数の意味は以下。
geo_path
:GeoJSON
ファイルのパスdata
: 色分けのための値をもつDataFrame
columns
:data
中GeoJSON
と紐づけるキー / 値 を含む列名key_on
:GeoJSON
側で紐付けに使うキーthreshold_scale
: 色分けをする際の閾値fill_color
色分けに使う color-brewer の名前reset
: 既存のレイヤがある場合に削除する
m = folium.Map(location=[10, 35], zoom_start=1.5) geojson = r'countries.geo.json' m.geo_json(geo_path=geojson, data=df, columns=['Code', 'Number'], key_on='feature.id', threshold_scale=[1, 100, 500, 1000, 2000, 4000], fill_color='BuPu', reset=True) inline_map(m)
まとめ
folium
を使えば リーフレット / コロプレス図の描画がカンタンにできる。Jupyter
上で Javascript を使用してのデータ可視化、結構使えるのでは。