Tak's Notebook

Kaggle, Machine Learning, Engineering

Folium: Python で地図可視化

TOC

要旨

  • folium を使うことで、Python で柔軟な地図可視化が出来る。しかもグリグリ動かせる
  • folium は日本語チュートリアルが少ないので、簡単なものを網羅的にまとめる
  • バージョンの更新で結構使い方が異なっているので、公式ドキュメント以外を参考すると上手くいかないことがある


概要

Folium は javascript ライブラリの leaflet を、Python で使えるようにしたモジュール。これを使うことで Python で柔軟な地図可視化の操作ができるようになります。

ただ、日本語版チュートリアルが少なく、またバージョンが 0.1 とか 0.2 の頃と現在とではマップにピンを置く関数名や使い方が大きく異なっています。余談だと、2017年時点では version0.5 だったのが、2018年に入ってから version0.6 となり、2018年11月に見たら version0.7 になっていました。

こういった事情で、公式ドキュメントを見る必要があるが、そうするとますます日本語ドキュメントの数が少なくなったり。そこで、簡単なものだけでも網羅的に使い方をまとめたいと思った次第です。


実装

Install

インストールは pip でも何でも。

# install if not installed
$ pip install folium

# upgrade if not latest edition
$ pip install folium -U

Getting started

ここからのコードの実行は基本的には jupyter 上で行われていると想定してください。実行後、即表示されるのでオススメです。

そうでなければ、一度htmlで保存する必要があります。

import numpy as np
import pandas as pd

import folium
from folium import plugins
print( "folium version is {}".format(folium.__version__) )

folium version is 0.7.0 と出てきます。

使用するデータ

今回使用するデータは、Kaggle のリクルートコンペの hpg_store_info.csv です。緯度経度が入っていて、適当な大きさのデータだったためです。

Recruit Restaurant Visitor Forecasting | Kaggle

中身は、以下のような感じです。

hpg_store_id hpg_genre_name hpg_area_name latitude longitude
0 hpg_6622b62385aec8bf Japanese style Tōkyō-to Setagaya-ku Taishidō 35.643675 139.668221
1 hpg_e9e068dd49c5fa00 Japanese style Tōkyō-to Setagaya-ku Taishidō 35.643675 139.668221
2 hpg_2976f7acb4b3a3bc Japanese style Tōkyō-to Setagaya-ku Taishidō 35.643675 139.668221

適当な1つの緯度経度地点が欲しいので、以下のように定義しておきます。

LAT = data.latitude.iloc[0]
LNG = data.longitude.iloc[0]

地図を表示する

まずは、地図を表示させます。

そのためには、以下のようにするだけです。

m = folium.Map(location=[LAT, LNG])
m

map_quickstart
map_quickstart

location 引数は、表示する地図の中心点となります。

HTMLで保存

ちなみに、HTML 保存する場合も以下のように簡単にできます。

filename = 'output/map.html'
m.save(filename)


Tile

Tile とは地図のベースとなるものです。

デフォルトは Open Street Map というオープンライセンスのマップタイルが利用されます。

Tile については、説明するより見比べると理解しやすいと思います。

stamen-toner

m = folium.Map(location=[LAT, LNG], tiles="Stamen Toner", zoom_start=13)
m

f:id:takaishikawa42:20190111233332j:plain
map_tile_stamen-toner

stamen-terrain

m = folium.Map(location=[LAT, LNG], zoom_start=12, tiles="Stamen Terrain")
m

f:id:takaishikawa42:20190111233357j:plain
map_tile_stamen-terrain

tile 引数で指定できます。かっこいい感じになりますね。

ちなみに、zoom_start 引数で最初のズームレベルを指定できます。こちらは指定しないと、「いい感じ」にしてくれるようです。ズームレベルは、1~20の整数値で指定し、1が最もズームアウトした状態、20が最もズームインした状態です。

あと、普段使いませんが、Mapbox も API Key を渡して使用することができるようです。


Markers

Simple Marker

最もシンプルなMarker。
ホバーやクリックで情報を表示する設定もできます。

m = folium.Map(location=[LAT, LNG])
folium.Marker(
    location=[data.latitude.iloc[100], data.longitude.iloc[100]],
    popup="This is Simple Marker",
).add_to(m)
m

引数 popup で、クリックしたときに表示する情報を追加できます。

folium.Marker() でマーカーを作るだけでは地図に表示されず、これを add_to(m) することで、地図に情報を追加できます。このあたりの書き方が最初慣れず使いにくく感じました。

f:id:takaishikawa42:20190111233426j:plain
map_simple-marker

Marker with icon

工夫したい場合は、Marker の色を変えたり、アイコンを設定できたりできます。(Leaflet.R でいうところの AwesomeMarker です)

m = folium.Map(location=[LAT, LNG])
folium.Marker(
    location=[data.latitude.iloc[100], data.longitude.iloc[100]],
    popup=data.hpg_genre_name.iloc[100],
    tooltip="<i>" + data.hpg_area_name.iloc[100] + "</i>",  # HTMLタグも使える
    icon=folium.Icon(color="red", icon="info-sign"),  # 色やマーカーのアイコンを変更できる
).add_to(m)
m

f:id:takaishikawa42:20190111233448j:plain
map_marker-icon

Circle and Circle Marker

ピンのマーカーではなく、円をマーカー代わりにできます。

m = folium.Map(
    location=[data.latitude.iloc[0], data.longitude.iloc[0]],
    tiles="Stamen Toner",
    zoom_start=10,
)
folium.Circle(
    radius=500,
    location=[data.latitude.iloc[100], data.longitude.iloc[100]],
    popup="<b>" + data.hpg_genre_name.iloc[100] + "</b>",
    color="crimson",
    fill=False,
).add_to(m)
folium.CircleMarker(
    location=[data.latitude.iloc[200], data.longitude.iloc[200]],
    radius=50,
    popup=data.hpg_genre_name.iloc[200],
    color="#3186cc",
    fill=True,
    fill_color="#3186cc",
).add_to(m)
m

f:id:takaishikawa42:20190111233509j:plain
map_circle

その他:lat/lng popovers

クリックした点の緯度経度をポップアップ表示します。

m = folium.Map(location=[46.1991, -122.1889], tiles="Stamen Terrain", zoom_start=13)
m = m.add_child(folium.LatLngPopup())
m

f:id:takaishikawa42:20190111233541j:plain
map_popover

その他:click-for-marker

クリックした点にMarkerを生成します。

m = folium.Map(location=[46.8527, -121.7649], tiles="Stamen Terrain", zoom_start=13)
folium.Marker(location=[46.8354, -121.7325], popup="Camp Muir").add_to(m)
m = m.add_child(folium.ClickForMarker(popup="Waypoint"))
m

f:id:takaishikawa42:20190111233600j:plain
map_click-for-marker

MarkerCluster

Leaflet.R と異なり、Folium.plugins.MarkerCluster で使える点に注意。ドキュメント探すときは、plugins の項目内にあるので探します。

folium.plugins.FeatureGroupSubGroup でクラスターをグループ分けできる

今までは、1点のみをマーカーを貼っていたが、複数の地点をマークしたい場合は、numpy の array 形式で渡してあげます。

locations = np.array([data.latitude, data.longitude]).T

m = folium.Map([LAT, LNG], zoom_start=4)
plugins.MarkerCluster(locations).add_to(m)
m

f:id:takaishikawa42:20190111233615j:plain
map_marker-cluster

FeatureGroup

Creation, International cuisine, Japanese style の3つのジャンルがデータ中で最も多いジャンル名だったため、これらの地図を作成することにする。

def create_feature_group(genre):
    data_genre = data[data.hpg_genre_name == genre]
    arr_latlon = np.array([data_genre.latitude, data_genre.longitude]).T
    feature_group = folium.FeatureGroup(name=genre)
    plugins.MarkerCluster(arr_latlon).add_to(feature_group)
    return feature_group


m = folium.Map(location=[LAT, LNG], zoom_start=12)
genres = ["Creation", "International cuisine", "Japanese style"]
for genre in genres:
    fg = create_feature_group(genre)
    fg.add_to(m)
folium.LayerControl().add_to(m)
m

f:id:takaishikawa42:20190111233710j:plain
map_feature-group


DataFrame 内の全要素をプロットする

m = folium.Map(location=[LAT, LNG], zoom_start=10)
data_nodup = data.drop_duplicates(subset=["latitude", "longitude"])
for lat, lon in zip(data_nodup.latitude, data_nodup.longitude):
    folium.Marker(
        location=[lat, lon],
        popup="/".join([str(lat), str(lon)]),
        tooltip=str(lat) + "_" + str(lon),
    ).add_to(m)
m

f:id:takaishikawa42:20190111233729j:plain
map_df


その他

感覚的には、ポップアップをつけたMarkerを5000以上プロットしようとすると、固まるイメージ。 Vincent/Vega and Altair/VegaLite Markers を使用する際も、渡す地図情報のサイズを重すぎないようにすることが大事かも。 (具体的にどれくらいメモリを使っているのか、どのように処理が進んでいるのかは調べていない)

LeafletRでは、マップオブジェクトにパイプ( %>% )で付加情報を足していって地図を生成する Folium の場合だと、Mapオブジェクトをはじめに定義して、その後各種要素を作って add_to(m) していく

ただ、クラスターに関しては、LeafletR ではaddMarkerの引数でオプションを指定したが、 Folium ではMarkerCluster オブジェクトを別で作成して add_to(m) する必要がある

LeafletR は、kazutan さんのサイトを参照するのがいいと思います



github.com