見出し画像

Dashを用いた人流データビジュアライゼーション Part.1

1. やりたいこと

G空間情報センターにて公開されている全国人流オープンデータを用いて、1kmメッシュ別の滞在人口を可視化させること。
具体的には、メッシュにカーソルを合わせてインタラクティブに人口を表示させたり、プルダウンで関心エリアの変更を可能にする機能を実装します。

画像4

出典:「全国の人流オープンデータ」(国土交通省)(https://www.geospatial.jp/ ckan/dataset/mlit-1km-fromto)

2. 使用データ概要

人流データの概要・フォーマットや滞在人口の算出方法の詳細は定義書に記載されている通りですが、

●集計項目:1kmメッシュ別の滞在人口(1ヶ月間における1日あたりの平均値)
●集計単位:全日・平日・休日/終日・昼・夜/(居住地)同市区町村・同地方・それ以外
●集計期間:2019年1月〜2020年12月の各月(24ヶ月)

となっています。

3. データ前処理

本記事で使用する以下の3ファイルで、取得する都道府県(都道府県コード)は埼玉県(11)、千葉県(12)、東京都(13)、神奈川県(14)とします。

・monthly_mdp_mesh1km:1kmメッシュ別の滞在人口
・attribute:1kmメッシュデータにおけるメッシュIDの座標を示すデータ
・prefcode_citycode_master:都道府県コード、市区町村コードのマスタデータ

画像2

4. jupyter上で滞在人口を可視化する

ディレクトリ構造はこんな感じ

Agoop/
├── data
│   └── {pref_code}_mesh1km
│   │   └── 2019/{mm}/monthly_mdp_mesh1km.csv.zip
│   └── attribute
│   │   └── attribute_mesh1km_2019.csv.zip
│   └── prefcode_citycode_master
│       └── prefcode_citycode_master_utf8_2019.csv.zip
└── src
   └── app.ipynb
   └── app.py

まずはjupyterでサクッと可視化してみましょう。

2019年1月のdayflag=2(全日)、timezone=2(終日)のデータだけをひとまず可視化することにします。また、各メッシュIDの座標や市区町村名も紐づけておきます。

# app.ipynb

import numpy as np
import pandas as pd
from dfply import *
import geopandas as gpd
import plotly.express as px
import plotly.graph_objects as go
from shapely.geometry import Polygon

attribute_mesh1km = pd.read_csv('../data/attribute/attribute_mesh1km_2019.csv.zip')
prefcode_citycode_master = pd.read_csv('../data/prefcode_citycode_master/prefcode_citycode_master_utf8_2019.csv.zip')

dfs = []
for pref_code in [11, 12, 13, 14]:
   df = pd.read_csv(f'../data/{pref_code}_mesh1km/2019/01/monthly_mdp_mesh1km.csv.zip') >> mask(X.dayflag==2, X.timezone==2)
   dfs.append(df)
df = pd.concat(dfs).reset_index(drop=True)
df = df >> inner_join(attribute_mesh1km, by=['mesh1kmid', 'prefcode', 'citycode']) >> inner_join(prefcode_citycode_master[['citycode', 'cityname']], by='citycode')
df
画像3

あとはplotly.express.choropleth_mapboxメソッドで簡単に可視化できます。※ただし、メッシュIDの四隅の座標からポリゴン作成→GeoJSON形式へ変換する必要がある。

df['geometry'] = df.apply(lambda x: Polygon([(x['lon_min'], x['lat_min']), (x['lon_min'], x['lat_max']), (x['lon_max'], x['lat_max']), (x['lon_max'], x['lat_min'])]), axis=1)
df = df.set_index('mesh1kmid')

fig = px.choropleth_mapbox(df, geojson=gpd.GeoSeries(df['geometry']).__geo_interface__, locations=df.index, color='population',
                          color_continuous_scale='Jet', center={"lat": df['lat_center'].mean(), "lon": df['lon_center'].mean()},
                          hover_data=['cityname'], mapbox_style="open-street-map",opacity=0.5,zoom=9, height=600,)
fig.show()​
画像4

ちなみにdfplyはSQLっぽくテーブル操作できるライブラリです。

5. Dashを用いたデータビジュアライゼーション

では、Dashにうつります。先に完成コードがこちら。

# app.py

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output

import ast
import json
import pandas as pd
from dfply import *
import geopandas as gpd
import plotly.express as px
from shapely.geometry import Polygon

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

attribute_mesh1km = pd.read_csv('../data/attribute/attribute_mesh1km_2019.csv.zip')
prefcode_citycode_master = pd.read_csv('../data/prefcode_citycode_master/prefcode_citycode_master_utf8_2019.csv.zip')

dfs = []
for pref_code in [11, 12, 13, 14]:
   df = pd.read_csv(f'../data/{pref_code}_mesh1km/2019/01/monthly_mdp_mesh1km.csv.zip') >> mask(X.dayflag==2, X.timezone==2)
   dfs.append(df)
df = pd.concat(dfs).reset_index(drop=True)
df = df >> inner_join(attribute_mesh1km, by=['mesh1kmid', 'prefcode', 'citycode']) >> inner_join(prefcode_citycode_master[['citycode', 'cityname']], by='citycode')
df['geometry'] = df.apply(lambda x: Polygon([(x['lon_min'], x['lat_min']), (x['lon_min'], x['lat_max']), (x['lon_max'], x['lat_max']), (x['lon_max'], x['lat_min'])]), axis=1)
df = df.set_index('mesh1kmid')


app.layout = html.Div(
   [
       html.Div(
           [
               html.H4('都道府県の選択'),
               dcc.Dropdown(
                   id='pref-dropdown',
                   options=[
                       {'label': '埼玉県', 'value': 11},
                       {'label': '千葉県', 'value': 12},
                       {'label': '東京都', 'value': 13},
                       {'label': '神奈川県', 'value': 14}
                   ],
                   value=13,
               ),
           ]
       ),
       html.Div(
           [
               html.H3(id='hoverdata-h3'),
               html.Div(dcc.Loading(id='loading', type='circle', children=dcc.Graph(id='population-map')), style={'width': '80%', 'margin': 'auto'}),
           ],
       ),
   ],
   style={'width': '100%', 'margin': 'auto', 'textAlign': 'center'},
)

@app.callback(Output('population-map', 'figure'), Input('pref-dropdown', 'value'))
def update_map(value):
   df_target = df >> mask(X.prefcode==value)

   fig = px.choropleth_mapbox(
       df_target, geojson=gpd.GeoSeries(df_target['geometry']).__geo_interface__, locations=df_target.index, color='population',
       color_continuous_scale='Jet', center={'lat': df_target['lat_center'].mean(), 'lon': df_target['lon_center'].mean()},
       hover_data=['cityname'], mapbox_style='open-street-map',opacity=0.5,zoom=9, height=800,
   )
   return fig

@app.callback(Output('hoverdata-h3', 'children'), Input('population-map', 'hoverData'))
def update_title(hoverData):
   try:
       title = ast.literal_eval(json.dumps(hoverData, ensure_ascii=False))
       meshcode = title['points'][0]['location']
       location = title['points'][0]['customdata'][0]
       return f'{location}(地域メッシュコード:{meshcode})'
   except:
       return 'NULL'

if __name__ == '__main__':
   app.run_server(debug=True)

5-1. レイアウト

各コンポーネントにIDをつける

・埼玉県/千葉県/東京都/神奈川県を切り替えるプルダウン
・選択した4都県に応じた地図の切り替え(=update_map関数の返り値)
・メッシュに対応した地名の切り替え(=update_title関数の返り値)

app.layout = html.Div(
   [
       html.Div(
           [
               html.H4('都道府県の選択'),
               dcc.Dropdown(
                   id='pref-dropdown',
                   options=[
                       {'label': '埼玉県', 'value': 11},
                       {'label': '千葉県', 'value': 12},
                       {'label': '東京都', 'value': 13},
                       {'label': '神奈川県', 'value': 14}
                   ],
                   value=13, # 初期値の設定
               ),
           ]
       ),
       html.Div(
           [
               html.H3(id='hoverdata-h3'),
               html.Div(dcc.Loading(id='loading', type='circle', children=dcc.Graph(id='population-map')), style={'width': '80%', 'margin': 'auto'}),
           ],
       ),
   ],
   style={'width': '100%', 'margin': 'auto', 'textAlign': 'center'},
)

5-2. 一つ目のコールバック関数

pref-dropdownのvalue(11〜14)を受け取って抽出した1つをマッピングし、dcc.Graph(id='population-map')に渡します。

@app.callback(Output('population-map', 'figure'), Input('pref-dropdown', 'value'))
def update_map(value):
   df_target = df >> mask(X.prefcode==value)

   fig = px.choropleth_mapbox(
       df_target, geojson=gpd.GeoSeries(df_target['geometry']).__geo_interface__, locations=df_target.index, color='population',
       color_continuous_scale='Jet', center={'lat': df_target['lat_center'].mean(), 'lon': df_target['lon_center'].mean()},
       hover_data=['cityname'], mapbox_style='open-street-map',opacity=0.5,zoom=9, height=800,
   )
   return fig

5-3. 二つ目のコールバック関数

地図上のメッシュにカーソルを合わせたときに、該当メッシュの地名と地域メッシュコードをhtml.H3(id='hoverdata-h3')に渡します。

@app.callback(Output('hoverdata-h3', 'children'), Input('population-map', 'hoverData'))
def update_title(hoverData):
   try:
       title = ast.literal_eval(json.dumps(hoverData, ensure_ascii=False))
       meshcode = title['points'][0]['location']
       location = title['points'][0]['customdata'][0]
       return f'{location}(地域メッシュコード:{meshcode})'
   except:
       return 'NULL'

参考書籍

Python インタラクティブ・データビジュアライゼーション入門 ―Plotly/Dashによるデータ可視化とWebアプリ構築

この記事が気に入ったらサポートをしてみませんか?