Dashを使ってダッシュボードを作る
ローコードでブラウザでの表示が可能なダッシュボードを作成できるDashを使って選択したデータの時系列グラフと指定した年度の都道府県別マップを作成してみました。
使用しているパッケージ
今回使用しているパッケージは以下の通りです。
- Pandas
- NumPy
- GeoPandas
- Dash
使用するデータ
SSDSE-県別推移(SSDSE-B)
このデータセットのグラフ化・地図化を行いました。このデータは様々な分野の都道府県別・時系列データを集めたデータセットになっています。独立行政法人統計センターが公開しているものです。
47都道府県のポリゴン
japonyol.netで公開されているこのデータを使わせていただきました。
実装
年度を選択すると地図の表示が変化します。
地図上で特定の都道府県を選択すると時系列データのグラフが都道府県の時系列データに変化し、
もう一度、同じ都道府県をクリックすると全国のデータに戻るようになっています。
表示するデータを変えることもできます。
全コードは下にあります。
鍵となる部分の解説
レイアウト
app.layout = html.Div([],id=XXX,style={'width':'100px'})
このような感じでhtmlのコンポーネントをPythonコードで並べることができます。コンポーネントをネストしていきたいときはPythonコードをネストしていきます。ここで、つけるIDは動的に変化させるときに利用します。
レイアウトについてはここにまとめられています。
https://dash.plotly.com/layout
コールバック
@app.callback(Output('graph_title', 'children'),
[Input('map', 'selectedData'), Input('data', 'value')])
def function(map, data):
....
return children
Input()
を使うとドロップダウンなどから値を取ってくることができます。この値を関数に渡して処理します。Input()
の1番目の引数がhtmlのIDで、ここで指定したIDのコンポーネントから値を取得します。2番目の引数でコンポーネントの中のどのプロパティを利用するかを指定します。value
がよく使われますが、図の中の要素から値を取得したい場合にはselectedData
としてあげることで、クリックした要素の値を取ってくることができます。これを利用して、選択した都道府県の時系列グラフを表示するようにしています。
また、Output()
で書き換えるコンポーネントとその中のプロパティを指定しています。
図からの要素の取得はここにまとめられています。
https://dash.plotly.com/interactive-graphing
GeoDataFrameのマップ化
地図を作るのにchoropleth()
を利用しています。
GeoPandasで作成したGeoDataFrame
を地図化する際に、このデータでは'都道府県'列をインデックスにセットする必要があります。インデックスにセットしたらchoropleth()
の初めの引数にはGeoDataFrame
、2番目の引数geojson
にはgeometory
、3番目の引数locations
にはindex
を渡します。
色分けに使用する列は引数color
に列名を渡してあげればよいです。
全コード
GitHub上にも置いておきます。
https://github.com/HKimura787/blog/blob/main/Qiita/Dash/interactive_app1.py
import pandas as pd
import geopandas as gpd
import os
from dash import Dash, html, dcc, Input, Output
import plotly.express as px
import numpy as np
def data_handring():
# the function to get absolute path
def get_absolute_path(path):
this_dir = os.path.dirname(os.path.abspath(__file__))
return os.path.join(this_dir, path)
# read geojson
gdf = gpd.read_file(get_absolute_path('data\prefectures.geojson'))
# read csv
df = pd.read_csv(get_absolute_path('data\SSDSE-B-2023.csv'), encoding='shift_jis', skiprows=1)
# merge csv and geojson
merged_df = gpd.GeoDataFrame(df.merge(gdf, left_on='都道府県', right_on='name', how = 'left'))
print(merged_df.head())
# plot map
# merged_df.query('年度==2020').plot(column='総人口', legend=True, figsize=(10, 10))
# plt.show()
return merged_df
def main():
df = data_handring()
app = Dash(__name__)
# レイアウトの作成
app.layout = html.Div([
html.H1('都道府県別統計データの推移'),
html.Div([
html.Div([
html.H2('データを選択'),
dcc.Dropdown(
id='data',
options=[{'label': i, 'value': i} for i in df.columns[3:]],
value='総人口',
style={'width': '50%'}
)
]),
html.Div([
html.H2('地図に表示する年度を選択'),
dcc.RadioItems(
id='year',
options=[{'label': i, 'value': i} for i in np.arange(df['年度'].min(), df['年度'].max()+1)],
value=df['年度'].max(),
style={'display': 'flex'})
]),
]),
html.Div([
html.Div([
html.H2(id='graph_title',),
dcc.Graph(id='graph',style={'height': '70vh'}),
]),
html.Div([
html.H2(id='map_title'),
dcc.Graph(id='map',style={'height': '70vh'})
]),
], style={'display': 'flex'})])
# 時系列グラフのタイトルを変更するためのコールバック関数
@app.callback(Output('graph_title', 'children'),
[Input('map', 'selectedData'), Input('data', 'value')])
def data_graph_title(selectedData, data):
if selectedData is None:
return f'全国の{data}の推移'
else:
location = selectedData['points'][0]['location']
return f'{location}の{data}の推移'
# 時系列グラフを更新するためのコールバック関数
@app.callback(Output('graph', 'figure'),
[Input('map', 'selectedData'), Input('data', 'value')])
def display_selected_data(selectedData, data):
if selectedData is None:
filtered_df = df.groupby('年度').sum().reset_index()
else:
location = selectedData['points'][0]['location']
filtered_df = df.query('都道府県 == @location')
fig = px.line(filtered_df, x='年度', y=data)
return fig
# 地図のタイトルを変更するためのコールバック関数
@app.callback(
Output('map_title', 'children'),
[Input('year', 'value'), Input('data', 'value')])
def map_title(year, data):
return f'{year}年度の{data}の地図'
# 地図を更新するためのコールバック関数
@app.callback(
Output('map', 'figure'),
[Input('year', 'value'), Input('data', 'value')])
def update_map(year,data):
filtered_df = df.query(f'年度 == {int(year)}').set_index('都道府県')
fig = px.choropleth(
filtered_df,
geojson=filtered_df.geometry,
locations=filtered_df.index,
color=data,
projection="mercator",
fitbounds="locations",
range_color=(df[data].min(), df[data].max()),
color_continuous_scale="Reds",
basemap_visible=False)
fig.update_layout(clickmode='event+select')
return fig
# サーバーの起動
app.run_server(debug=True, use_reloader=False)
if __name__ == '__main__':
main()