akatak’s blog

プログラム初心者の50代ビジネスマンがセカンドキャリアを目指して働きながらPythonを中心に独学しています。自らの覚え書きや成果物、感じたことなどを脈絡もなく書き連ねるブログです。

可視化フレームワークDashで世界統計地図を描いてみた

Dashとはカナダに拠点をおくPlotly社が開発しているPythonから利用できるWebフレームワークで、様々なデータの可視化・グラフ化に利用できます。

以前紹介したPlotlyというPythonライブラリもこのDashで使えますし、Dashでは独自にグラフ化のためのコンポーネントを用意しているほか、htmlのタグ等もコンポーネント化していますので、簡単なWebページが作成できるというものです。Plotlyと同様にインタラクティブにグラフの操作が可能です。また、pandasのDataFrameを使えるのも良いですよね。

今回は、世界銀行が公表しているデータをworld_data_bankというライブラリを使って取得。このデータをDash上にグラフ化し、Herokuにデプロイしました。画面はこんな感じ。

f:id:akatak:20200711164239p:plain

サイトへのリンクはこちら↓
https://world-bank-map.herokuapp.com

左上のドロップダウンにて「一人当たりGDP(ドル)」「平均寿命」「人口」。隣のドロップダウンで「西暦年」を選択すると、グラフが表示されます。右のドロップダウンはDashで利用可能なカラーパレットです。いろいろなカラースケールを確かめたく、選択できるようにしてみました。また、グラフ表示だけでなく、タブでテーブルを選択するとグラフ表示で利用したデータをテーブル形式で見られるようにもしました。

世界銀行の公表データの取得

世界銀行(World Bank)は数多くのデータを公表していて、APIを利用して取得することができます。Pythonの場合、世界銀行APIを直接利用しなくても、このAPIを利用したモジュールがいくつかあります。

有名どころではpandas_datareaderを利用することができます。 pandas-datareader.readthedocs.io

今回は、公表データの一覧表等を比較的入手しやすいworld_bank_dataというモジュールを利用しました。 pypi.org

インストールは以下の通り。

pip install world_bank_data --upgrade

どんな感じのデータか見てみましょう。world_bank_datapandasをインポートしておきます。

import world_bank_data as wbd
import pandas as pd

データを取得するには何のデータか特定する必要があります。World BankではIndicatorと呼んでいますが、こちらのサイトから検索できます。

Data Catalog | Data Catalog あるいは、world_bank_dataget_indicators()メソッドで全てのIndicatorを取得(17473 行!)して探すか、あるいはsearch_indicators('キーワード')で検索するかして、入手したいデータのindicatorを特定します。

今回は、「一人当たりGDP(ドル)」「平均寿命」「人口」を取得したいと思います。それぞれindicatorはNY.GDP.PCAP.KD, SP.DYN.LE00.IN, SP.POP.TOTLとなります。

get_series('インディケーター')メソッドで取得すると

f:id:akatak:20200711153547p:plain

MultiIndexのDataFrameが返されます。MultiIndexのうち、CountryとYearを軸にこれら3つのDataFrameをpandasのmergeを使って統合します。ついでに、indicatorをわかりやすい日本語に変えておきます。

_df = pd.merge(df_gdp_pcap,df_lifeexp, on=['Country', 'Year'])
_df = pd.merge(_df, df_pops, on=['Country', 'Year'])
_df.columns = ['一人当たりGDP(ドル)', '平均寿命(歳)', '人口(人)']

f:id:akatak:20200711154121p:plain

Dashのコロプレス図(choropleth map)を描くには、3文字の国コードが必要になります。幸いworld_bank_dataでは簡単に国・地域の一覧が入手できます。

df_all_countries = wbd.get_countries()

結果を見てみると、一番左のidが3文字の国コードですね。

f:id:akatak:20200711154642p:plain

これを先ほどの_dfと統合します。_dfのCountry欄とdf_all_countriesのname欄の紐つけています。

df = pd.merge(_df.reset_index(), df_all_countries.reset_index(), left_on='Country', right_on='name')

f:id:akatak:20200711155505p:plain

これだと、Country 欄にはまだ、国以外に地域(東アジア、ヨーロッパ等)が残っていますので、国だけのリストにします。ついでに、不要な欄を削除し、Year欄を文字列から整数列に変えておきます。

df = df[df['region'] != 'Aggregates'].reset_index(drop=True)
df = df.drop(['iso2Code','name', 'adminregion', 'incomeLevel', 'lendingType', 'capitalCity', 'longitude','latitude'], axis=1).reset_index(drop=True)
df['Year'] = df['Year'].astype(int)

Dashアプリを作成する

Dashについて、細かく説明するのは大変なので、省略します。すみません(笑)。詳しくは、本家の英語のTutorialを参照いただくか、日本語でチュートリアルを解説されているサイトをご参照いただければと思います。

dash.plotly.com

qiita.com

ここではチュートリアルに書かれていないポイントを中心にいくつか記載します。

DashでBootstrapを利用する

今回タブを使っています。Dashにおいて標準で提供されているタブはdash_core_componentのTabなんですが、横に間延びしてしまって、格好が悪いんですよね。なので、dashでBootstrapを利用するためのモジュールであるdash_bootstrap_componentsをインストールして利用しています。

Dash Bootstrap Components

このdash_bootstrap_componentsのTabは、すっきりしていて良いのですが、標準では使わなくてよいcallback関数を使わないといけなくなるのが若干のマイナスポイントですかね。

コロプレス図はplotly.graph_objectsを利用する

Plotlyが最近導入したplotly.expressは、グラフを簡単に描くためのモジュールで、Dashでも使えます。ただし、コロプレス図においてcallback関数を使う場合には、ページがリフレッシュされないという問題がPlotly Community Forumにも報告されています。なので、Dashにてコロプレス図を書く場合にはplotly.graph_objects.Choroplethを利用します。

棒グラフ(Bar Chart)にカラーパレットを適用する

go.Barの属性にmarkerを設定します。

 go.Bar(
        x=df_selected['Country'],
        y=df_selected[item],
       marker={
               'color': df['人口'],                  # データ系列をセット
               'colorscale': 'viridis'            # カラーパレットを設定
       }
)

テーブルのフォーマットを変更する

以下の通り、設定します。

dash_table.DataTable(
       # 省略

       # 特定のカラムのセルを左寄せにする
        style_cell_conditional = [
               {
                     'if': {'column_id': c},
                     'textAlign': 'left'
               } for c in ['Country', 'id', 'region']
         ],

  # 1行おきに背景色を変える
        style_data_conditional = [
               {
                    'if': {'row_index': 'odd'},
                    'backgroundColor': 'rgb(248, 248, 248)'
               },
         ],

       # タイトル行の背景色を変える・太字にする
        style_header={
               'backgroundColor': 'rgb(230, 230, 230)',
               'fontWeight': 'bold'
        }
)

なお、特定行の小数点を2桁にしたいと思って色々と試したけど、うまくいきませんでした。ご存知の方がいたら教えていただけると助かります。

スクリプト全体はこちら↓をご参照ください。

github.com