Python の 地図描画ライブラリを使おうと思い、javascript で吐き出せて マウスでぐりぐりできる folium使うことにしました。
folium使う前提で、地図にプロットできそうな統計データを探していたのですが、国勢調査のデータの中に 夫の<wbr>年齢(各歳),<wbr>妻の<wbr>年齢(各歳)別夫婦数(総数及び<wbr>日本人<wbr>) -<wbr> 全国※,<wbr>全国市部・郡部,<wbr>都道府県,<wbr>21大都市いう興味深いデータを見つけました。
統計表一覧 政府統計の総合窓口 GL08020103
このデータを加工して folium の地図上にプロットしてみようかと思います。


参考


前提

以下の環境で作業は実施しています。

  • OS

    % sw_vers 
    ProductName:    Mac OS X
    ProductVersion: 10.12.6
    BuildVersion:   16G29
    

  • pythonのverion

    % python -V
    Python 2.7.10   
    

  • 使用したfolium のversion

    % pip list | grep folium
    folium (0.3.0)
    


folium インストール

pip でインストールします。
pandas も必要なので、pandas も。

pip install pandas
pip install folium        


実施作業の流れ

以下の通り作業は実施していきました。
時系列順に記載します。

  1. GeoJSON ファイルの準備

  2. 夫の<wbr>年齢(各歳),<wbr>妻の<wbr>年齢(各歳)別夫婦数(総数及び<wbr>日本人<wbr>) -<wbr> 全国※,<wbr>全国市部・郡部,<wbr>都道府県,<wbr>21大都市 データの加工

  3. コロプレス図を描画

  4. コロプレス図にポップアップを追加する

作成したスクリプトはkemsakurai/folium_exampleUPしました。
コロプレス図に<wbr>ポップアップを<wbr>追加する<wbr> スクリプトについては、Githubを参照ください。


GeoJSON ファイルの準備

コロプレス図を描くには、GeoJSON または、TopoJSON 形式のファイルが必要になります。
夫の<wbr>年齢(各歳),<wbr>妻の<wbr>年齢(各歳)別夫婦数(総数及び<wbr>日本人<wbr>) -<wbr> 全国※,<wbr>全国市部・郡部,<wbr>都道府県,<wbr>21大都市都道府県を 色分けできればうまく描画できそうですので、都道府県のGeoJSON データを探してみました。

探したところ以下、見つかりました。 land/japan.geojson at master · dataofjapan/land

以下、curl コマンドでファイルをダウンロードし、地図上に描画してみます。(サイズが大きいって思いましたが、geojsonのサイズはこういうものなのかもしれません)

  • curlコマンド

    curl https://raw.githubusercontent.com/dataofjapan/land/master/japan.geojson > japan.geojson
    

  • plot_map.py
    動作確認用に以下 python スクリプトを作成しました。

    # -*- coding: utf-8 -
    import folium
    
    def main():
        # 地図の基準として兵庫県明石市を設定
        japan_location = [35, 135]
        m = folium.Map(location=japan_location, zoom_start=5)
        geojson = r'japan.geojson'
        # geojson読み込み
        m.choropleth(geo_data=geojson)
        # 地図をhtml形式で出力
        m.save(outfile="map.html")
    
    if __name__ == "__main__":
        main()
    

  • 実装の説明
    geo_json メソッドは、folium (0.3.0) だと対象のメソッドがなくなったらしく、以下エラーとなりました。

    AttributeError: 'Map' object has no attribute 'geo_json'
    
    どうも、python-visualization/folium: Python Data. Leaflet.js Maps.README.md記載のあるchoroplethメソッドが置き換わったようです。

  • 出力されるHTML
    上記スクリプトを実行すると、以下のような地図が出力されます。
    実際は世界地図が表示されますが、日本のみ切り取ってます。
    japan1

GeoJSON読み込み確認は以上です。


夫の<wbr>年齢(各歳),<wbr>妻の<wbr>年齢(各歳)別夫婦数(総数及び<wbr>日本人<wbr>) -<wbr> 全国※,<wbr>全国市部・郡部,<wbr>都道府県,<wbr>21大都市 データの加工

夫の<wbr>年齢(各歳),<wbr>妻の<wbr>年齢(各歳)別夫婦数(総数及び<wbr>日本人<wbr>) -<wbr> 全国※,<wbr>全国市部・郡部,<wbr>都道府県,<wbr>21大都市 は、CSV形式、DB形式でのダウンロードができます。
DB形式の使い方がわからず、CSVをダウンロードしたのですが、通常のCSVの形式ではないため、多少加工が必要です。
元のデータに対して以下のような加工を施しました。

  1. 不要なヘッダ部の削除

  2. 全国、市のデータを削除し、都道府県データのみにする

  3. -0置換

  4. 各都道府県ごとに、夫婦の<wbr>年齢差合計計算 (夫の年齢合計 - 妻の年齢合計)

  5. 夫婦の<wbr>年齢差合計各都道府県の総夫婦数で割る

  6. 各都道府県の地域コードをjapan.geojson突き合わせ可能な形式に変換

上記手順で時間にして1時間程度かかりました。
編集した結果をcsvにして、pandas読み込みと以下の通り表示されます。
一言で言うと各県でそれほど夫婦の年齢差に開きはなく、だいたい夫のほうが2歳年上です。

  • CSVの読み込み結果
    >>> import pandas as pd
    >>> pd.read_csv('fuhu_seikei.csv')
        Area Code Area Name       Ave      Sum
    0           1       北海道  2.252136  2850398
    1           2       青森県  2.572007   781319
    2           3       岩手県  2.451050   747188
    3           4       宮城県  2.312749  1255284
    4           5       秋田県  2.502581   636549
    5           6       山形県  2.369089   671118
    6           7       福島県  2.309119  1051790
    7           8       茨城県  2.377381  1700355
    8           9       栃木県  2.296028  1110933
    9          10       群馬県  2.208948  1059895
    10         11       埼玉県  2.362114  4159938
    11         12       千葉県  2.409113  3622084
    12         13       東京都  2.403701  6906918
    13         14      神奈川県  2.389190  5160165
    14         15       新潟県  2.284645  1302798
    15         16       富山県  2.622729   700990
    16         17       石川県  2.578889   716160
    17         18       福井県  2.634202   513435
    18         19       山梨県  2.488761   503541
    19         20       長野県  2.442753  1283469
    20         21       岐阜県  2.662840  1365110
    21         22       静岡県  2.530297  2307603
    22         23       愛知県  2.561806  4576349
    23         24       三重県  2.617312  1180811
    24         25       滋賀県  2.546597   886692
    25         26       京都府  2.478029  1473632
    26         27       大阪府  2.363501  4681998
    27         28       兵庫県  2.467564  3271282
    28         29       奈良県  2.538160   867962
    29         30      和歌山県  2.598260   610695
    30         31       鳥取県  2.373060   320878
    31         32       島根県  2.322820   391934
    32         33       岡山県  2.397910  1092344
    33         34       広島県  2.389589  1624244
    34         35       山口県  2.512688   847326
    35         36       徳島県  2.399621   432966
    36         37       香川県  2.366874   558613
    37         38       愛媛県  2.367458   778922
    38         39       高知県  2.432469   399066
    39         40       福岡県  2.235163  2537348
    40         41       佐賀県  2.322415   452922
    41         42       長崎県  2.265973   727314
    42         43       熊本県  2.279736   956087
    43         44       大分県  2.339118   649035
    44         45       宮崎県  2.225000   580981
    45         46      鹿児島県  2.358946   900669
    46         47       沖縄県  2.122128   612737
    >>> 
    

コロプレス図を描画

作成したcsvをinputにして、コロプレス図の描画します。
都道府県別の<wbr>夫婦の<wbr>年齢差の<wbr>平均値元に色が変わるようにしています。
* plot_map.py

# -*- coding: utf-8 -
import folium
import pandas as pd

def main():

    # 地図の基準として兵庫県明石市を設定
    japan_location = [35, 135]
    m = folium.Map(location=japan_location, zoom_start=5)
    geojson = r'japan.geojson'

    # CSVデータの読み込み
    df = pd.read_csv('fuhu_seikei.csv')
    m.choropleth(geo_data=geojson, data=df,
               columns=['Area Code', 'Ave'],
               key_on='feature.properties.id',
               threshold_scale=[2.2, 2.3, 2.4, 2.5, 2.6, 2.7],
               fill_color='YlGnBu', reset=True)
    # 地図をhtml形式で出力
    m.save(outfile="map.html")

if __name__ == "__main__":
    main()

  • 出力されるHTML
    上記スクリプトを実行すると、以下のような地図が出力されます。
    理由は不明ですが、中部地方が年齢差が大きいようです。
    japan2

ポップアップを追加する

コードが長いため、地図、と説明のみ記載します。

  • 出力されるHTML
    japan3

説明

アイコンを配置したら、あまりいい感じになりませんでしたが、実装について説明します。

MulitiPoligonのGeoJsonのアイコン配置について

少なくともfolium側でいい感じに配置してくれる機能は現状なさそうだったので、県庁所在地の位置に対してpopup表示するようにしました。
県庁所在地の緯度経度は、【みんなの知識 ちょっと便利帳】都道府県庁所在地 緯度経度データ - 各都市からの方位地図 - 10進数/60進数での座標・世界測地系(WGS84)拝借し辞書を作成、それを使う形にしました。

ポップアップマーカについて

マーカーが幾つか用意されています。python-visualization/folium: Python Data. Leaflet.js Maps.README.md から設定できるもので設定をしてみました。コメントアウト、初期状態でのコメントアウト部を解除してもらえば、RegularPolygonMarkerCircleMarkerマーカをプロットできます。
マーカのパラメータはちょっと気合いを入れて調べないとわからなそうで、途中で断念していますので調整の余地ありかと思います。

key_on の設定について

ここは直感的にわからなかったのでを記載しておきます。
Geojson 上に featureキー値として存在しませんが、features存在しており、且つ、配列です。
key_on指定がfeature.properties.id なので、features > 1要素 featureしての解釈は folium 側でうまいことやってくれています。
今回 id としたいのは地域コードで、properties下にぶらさがっており、on_key feature.properties.id指定しています。

  • folium_example/japan.geojson at master · kemsakurai/folium_example
    {
        "type": "FeatureCollection",
        "features": [
            {
                "type": "Feature",
                "properties": {
                    "nam": "Kyoto Fu",
                    "nam_ja": "京都府",
                    "id": 26
                },
                "geometry": {
                    "type": "MultiPolygon",
                    "coordinates": [
                        [
                            [
                                [
                                    135.036697387695,
                                    35.537334442138686
                                ],
                                [
                                    135.035202026367,
                                    35.53973388671878
                                ],
                                [
                                    135.03010559082,
                                    35.53986740112302
                                ],
    .........
    

説明は以上です。


作業中発生したエラーについて

作業中に発生したエラーについてまとめます。同じエラーが発生した場合はご活用ください。

  • threshold_scale の要素数を7以上にした
    ValueError発生します。

      File "/Library/Python/2.7/site-packages/folium/folium.py", line 247, in choropleth
        raise ValueError
    
    エラー発生箇所を確認したところ、確かに要素数の判定をしていたので、要素数6にしました。
        if threshold_scale and len(threshold_scale) > 6:
            raise ValueError
    

  • pandasキー値指定のスペルミス

    Traceback (most recent call last):
      File "plot_map.py", line 23, in <module>
        main()
      File "plot_map.py", line 18, in main
        fill_color='BuPu')
      File "/Library/Python/2.7/site-packages/folium/folium.py", line 263, in choropleth
        color_data = data.set_index(columns[0])[columns[1]].to_dict()
      File "/Library/Python/2.7/site-packages/pandas/core/frame.py", line 2830, in set_index
        level = frame[col]._values
      File "/Library/Python/2.7/site-packages/pandas/core/frame.py", line 1964, in __getitem__
        return self._getitem_column(key)
      File "/Library/Python/2.7/site-packages/pandas/core/frame.py", line 1971, in _getitem_column
        return self._get_item_cache(key)
      File "/Library/Python/2.7/site-packages/pandas/core/generic.py", line 1645, in _get_item_cache
        values = self._data.get(item)
      File "/Library/Python/2.7/site-packages/pandas/core/internals.py", line 3590, in get
        loc = self.items.get_loc(item)
      File "/Library/Python/2.7/site-packages/pandas/core/indexes/base.py", line 2444, in get_loc
        return self._engine.get_loc(self._maybe_cast_indexer(key))
      File "pandas/_libs/index.pyx", line 132, in pandas._libs.index.IndexEngine.get_loc (pandas/_libs/index.c:5280)
      File "pandas/_libs/index.pyx", line 154, in pandas._libs.index.IndexEngine.get_loc (pandas/_libs/index.c:5126)
      File "pandas/_libs/hashtable_class_helper.pxi", line 1210, in pandas._libs.hashtable.PyObjectHashTable.get_item (pandas/_libs/hashtable.c:20523)
      File "pandas/_libs/hashtable_class_helper.pxi", line 1218, in pandas._libs.hashtable.PyObjectHashTable.get_item (pandas/_libs/hashtable.c:20477)
    KeyError: 'Area code'   
    
    pandas のキー値指定のスペルミスで上記エラーが発生しました。
    スペルミスを直せば正常に動作します。

  • on_key指定のキー値のスペルミス

    Traceback (most recent call last):
      File "plot_map.py", line 25, in <module>
        main()
      File "plot_map.py", line 22, in main
        m.save(outfile="map.html")
      File "/Library/Python/2.7/site-packages/branca/element.py", line 157, in save
        html = root.render(**kwargs)
      File "/Library/Python/2.7/site-packages/branca/element.py", line 301, in render
        child.render(**kwargs)
      File "/Library/Python/2.7/site-packages/folium/map.py", line 299, in render
        super(LegacyMap, self).render(**kwargs)
      File "/Library/Python/2.7/site-packages/branca/element.py", line 617, in render
        element.render(**kwargs)
      File "/Library/Python/2.7/site-packages/branca/element.py", line 613, in render
        figure.script.add_child(Element(script(self, kwargs)),
      File "/Library/Python/2.7/site-packages/jinja2/runtime.py", line 432, in __call__
        return self._func(*arguments)
      File "<template>", line 32, in macro
      File "/Library/Python/2.7/site-packages/jinja2/runtime.py", line 193, in call
        return __obj(*args, **kwargs)
      File "/Library/Python/2.7/site-packages/folium/features.py", line 409, in style_data
        feature.setdefault('properties', {}).setdefault('style', {}).update(self.style_function(feature))  # noqa
      File "/Library/Python/2.7/site-packages/folium/folium.py", line 316, in style_function
        "fillColor": color_scale_fun(x)
      File "/Library/Python/2.7/site-packages/folium/folium.py", line 304, in color_scale_fun
        get_by_key(x, key_on) in color_data and
      File "/Library/Python/2.7/site-packages/folium/folium.py", line 299, in get_by_key
        '.'.join(key.split('.')[1:])))
      File "/Library/Python/2.7/site-packages/folium/folium.py", line 298, in get_by_key
        get_by_key(obj.get(key.split('.')[0], None),
    AttributeError: 'NoneType' object has no attribute 'get'
    
    on_key のスペルミスで発生するエラーです。
    スペルミスが多すぎます。

  • TypeError: choropleth() got an unexpected keyword argument ‘geo_path’
    この記事を書いている最中に、version (0.4.0) がリリースされました。
    version (0.3.0) だと、

    m.choropleth(geo_path=geojson)
    
    書けますが、 version (0.4.0) だと、
    m.choropleth(geo_data=geojson)
    
    書くようになりました。

  • Markerの追加でwarning
    以下のwarningが発生しました。

    plot_map.py:92: FutureWarning: Method `add_children` is deprecated. Please use `add_child` instead.
      m.add_children(marker)
    
    Map に マーカをaddする箇所で、folium の過去 version だと、add_children書いていましたが、
    非推奨になって、同じ役割をする add_child作成されたため発生しています。
    単純に add_childメソッドを変更すれば解消されます。


まとめ

政府統計 のデータと、folium を使って コロプレス図を描画しました。
以下、まとめます。

  • ぱっと見でおもしろいと思ったデータが加工しておもしろくなるとは限らない
    平均値をとったあたりで、おもしろさがなくなっていったのかもしれません。
    平均化したら、都道府県ごとでそれほど大きい違いはありませんでした。
    主観的なバイアスをかけたりすると誇張された感じにはなるかもですが、マナー違反なきはします。
    ただ、政府統計データ自体は数も多く、「何故、この観点で調べたのか経緯は全くわからないが個人的にはおもしろい」と感じるデータが多く、まさぐってみたくなりました。

  • folium自体は何でもできるわけではないが使いやすい
    popup を出力したり、onclick アクションを追加したり、凝ったことをしようとすると、詰むか詰まないかのところで試行することになりましたが、
    コロプレス図自体は、Webの情報やpython-visualization/folium: Python Data. Leaflet.js Maps.README.md参考にすれば、時間をかけることなく望んだものが出力できました。
    凝ったことをする場合は、Leaflet - a JavaScript library for interactive maps 側のことも意識しないといけないと思いますが、
    基本的なところは、folium でだいたいできると感じました。

以上です。

コメント

カテゴリー