はじめに
データを可視化して直観的に分析できるダッシュボードを作ってWebアプリとして公開したいです。
でもhtmlやcssやjsは面倒なのでいじりたくないです。
そんなとき、Streamlitなら面倒なことを一切抜きにして、Pythonスクリプト1つでデータ分析ダッシュボードを作成することができます。
この記事には、
- アプリ作成の過程(インストール、データ準備、ダッシュボード作成)
- AWS EC2インスタンスにデプロイしてみた手順
を残していきます。
こんな時期なので、題材としてはヤフー・データソリューションで公開されている東京23区内の人の往来が確認できるデータを眺めて、新型コロナ対策の影響を確認してみます。
アプリ作成
Streamlitのインストール
StreamlitはOSSです。
GitHub:https://github.com/streamlit/streamlit
インストールは以下でOK。
pip install streamlit
以下のコマンドを実行すると、ローカルでデモアプリを立ち上げてブラウザで確認することができます(ブラウザが勝手に立ち上がります)。
streamlit hello
使い方については、公式のチュートリアルページがわかりやすいです。
こちらの解説記事もどうぞ。
【追記】
こちらも参考になります。
データ準備
こんな時期なので、新型コロナ対策が東京都内の人流にどんな影響を与えているのか見てみたくなりますよね。
それっぽいデータを探してみたところ、ヤフーデータソリューションで
「東京23区滞在人口推計値の日別遷移(全体・来訪者・住人)」データが
オープンデータとして公開されていました(2020年4月10日現在)。
今回はこれをいい感じに可視化してみることにします。
【出典:ヤフー・データソリューション(https://ds.yahoo.co.jp/report/, 2020/04/09)】
データは毎日更新されています。ここでは、2020年4月9日までのデータを用いています。
元データはエクセル形式で、日毎の東京23区の住人数・来訪者数・全体数が含まれます。
これが月毎に別々のシートに保存されているので、予めcsvに変換しておきます。
これをそれぞれ読み込み、一つにまとめます。
import numpy as np
import pandas as pd
data_02 = pd.read_csv('東京23区推移0409_2月.csv')
data_03 = pd.read_csv('東京23区推移0409_3月.csv')
data_04 = pd.read_csv('東京23区推移0409_4月.csv')
data_all = pd.concat([data_02, data_03.iloc[:, 2:], data_04.iloc[:, 2:]], axis=1)
data_all.head()
エリア | 対象分類 | 2月1日 | 2月2日 | 2月3日 | 2月4日 | 2月5日 | 2月6日 | 2月7日 | 2月8日 | ... | 3月30日 | 3月31日 | 4月1日 | 4月2日 | 4月3日 | 4月4日 | 4月5日 | 4月6日 | 4月7日 | 4月8日 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 東京23区全体 | 全体 | 10485000 | 10164000 | 11676000 | 11687000 | 11659000 | 11690000 | 11691000 | 10471000 | ... | 11393000 | 11388000 | 11288000 | 11263000 | 11256000 | 10021000 | 9737000 | 11212000 | 11104000 | 10859000 |
1 | NaN | 住人 | 8921000 | 8921000 | 8921000 | 8921000 | 8921000 | 8921000 | 8921000 | 8921000 | ... | 8949000 | 8949000 | 8924000 | 8924000 | 8924000 | 8924000 | 8924000 | 8924000 | 8924000 | 8924000 |
2 | NaN | 来訪者 | 1564000 | 1243000 | 2755000 | 2766000 | 2738000 | 2769000 | 2770000 | 1550000 | ... | 2444000 | 2439000 | 2364000 | 2339000 | 2332000 | 1097000 | 813000 | 2288000 | 2180000 | 1935000 |
3 | 千代田区 | 全体 | 454900 | 356900 | 1028900 | 1039900 | 1031900 | 1043900 | 1041900 | 453900 | ... | 857000 | 855000 | 819500 | 802500 | 791500 | 266500 | 195500 | 775500 | 731500 | 624500 |
4 | NaN | 住人 | 54900 | 54900 | 54900 | 54900 | 54900 | 54900 | 54900 | 54900 | ... | 56000 | 56000 | 55500 | 55500 | 55500 | 55500 | 55500 | 55500 | 55500 | 55500 |
エリア、対象分類をMultiIndexとして整理し、出力します。
data_all.fillna(method='ffill', inplace=True)
data_all.set_index(['エリア', '対象分類'], inplace=True)
data_all.to_csv('tokyo_0409.csv', index=True, header=True)
最終的なデータはこんな感じ。
data_all.head(7)
2月1日 | 2月2日 | 2月3日 | 2月4日 | 2月5日 | 2月6日 | 2月7日 | 2月8日 | 2月9日 | 2月10日 | ... | 3月30日 | 3月31日 | 4月1日 | 4月2日 | 4月3日 | 4月4日 | 4月5日 | 4月6日 | 4月7日 | 4月8日 | ||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
エリア | 対象分類 | |||||||||||||||||||||
東京23区全体 | 全体 | 10485000 | 10164000 | 11676000 | 11687000 | 11659000 | 11690000 | 11691000 | 10471000 | 10149000 | 11523000 | ... | 11393000 | 11388000 | 11288000 | 11263000 | 11256000 | 10021000 | 9737000 | 11212000 | 11104000 | 10859000 |
住人 | 8921000 | 8921000 | 8921000 | 8921000 | 8921000 | 8921000 | 8921000 | 8921000 | 8921000 | 8921000 | ... | 8949000 | 8949000 | 8924000 | 8924000 | 8924000 | 8924000 | 8924000 | 8924000 | 8924000 | 8924000 | |
来訪者 | 1564000 | 1243000 | 2755000 | 2766000 | 2738000 | 2769000 | 2770000 | 1550000 | 1228000 | 2602000 | ... | 2444000 | 2439000 | 2364000 | 2339000 | 2332000 | 1097000 | 813000 | 2288000 | 2180000 | 1935000 | |
千代田区 | 全体 | 454900 | 356900 | 1028900 | 1039900 | 1031900 | 1043900 | 1041900 | 453900 | 356900 | 958900 | ... | 857000 | 855000 | 819500 | 802500 | 791500 | 266500 | 195500 | 775500 | 731500 | 624500 |
住人 | 54900 | 54900 | 54900 | 54900 | 54900 | 54900 | 54900 | 54900 | 54900 | 54900 | ... | 56000 | 56000 | 55500 | 55500 | 55500 | 55500 | 55500 | 55500 | 55500 | 55500 | |
来訪者 | 400000 | 302000 | 974000 | 985000 | 977000 | 989000 | 987000 | 399000 | 302000 | 904000 | ... | 801000 | 799000 | 764000 | 747000 | 736000 | 211000 | 140000 | 720000 | 676000 | 569000 | |
中央区 | 全体 | 441000 | 367000 | 849000 | 857000 | 852000 | 861000 | 863000 | 440000 | 370000 | 793000 | ... | 733000 | 728000 | 701000 | 691000 | 684000 | 307000 | 256000 | 675000 | 641000 | 563000 |
ダッシュボード作成
ここから、実際にダッシュボードを作っていきます。
Streamlitでは、ダッシュボードを1つのPythonスクリプトのみで作ることができます。
今回はstreamlit_app.py
としてスクリプトを書いていきます。
データの中身をラインチャートで表示します。
セレクトボックスで対象地域を指定し、その地域の住人・来訪者・全体数の時系列推移を見ることができる仕様にします。
import numpy as np
import pandas as pd
import streamlit as st
import plotly.graph_objects as go
st.title('東京23区滞在人口推計値の日別遷移')
st.write('【出典:ヤフー・データソリューション】')
data_all = pd.read_csv('data/tokyo_0409.csv')
erea_list = data_all['エリア'].unique()
data_all.set_index(['エリア', '対象分類'], inplace=True)
# 値を縦持ちに変更
data_all = data_all.T
# 日付をdatetime型に変換
data_all.index = map(lambda x: '2020年'+x, data_all.index)
data_all.index = pd.to_datetime(data_all.index, format='%Y年%m月%d日')
data_all.index.name = '月日'
# 表示エリアをセレクトボックスで選択
selected_erea = st.sidebar.selectbox(
'表示するエリアを選択:',
erea_list
)
# グラフ表示
st.write(f'## 表示中:{selected_erea}')
data_plotly = data_all[(selected_erea)]
data_plot = [
go.Scatter(x=data_plotly.index,
y=data_plotly['住人'],
mode='lines',
name='住人'),
go.Scatter(x=data_plotly.index,
y=data_plotly['来訪者'],
mode='lines',
name='来訪者'),
go.Scatter(x=data_plotly.index,
y=data_plotly['全体'],
mode='lines',
name='全体')]
layout = go.Layout(
xaxis={"title": "日付"},
yaxis={"title": "人数"}
)
st.plotly_chart(go.Figure(data=data_plot, layout=layout))
streamlit
モジュールのst.write()
メソッドなどを使って、
画面に表示する文字列やテーブル、グラフを定義していきます。
また、対話的な処理として、st.selectbox()
で対象地域の選択肢を表示し、
ユーザが選択した値を返り値としてselected_erea
に持つことで、
対応する地域の情報をグラフ描画しています。
st.sidebar
で画面左側のサイドバーに要素を配置できて、ちょっと見た目がいい感じに。
グラフ表示には、st.line_chart()
など簡便なメソッドが用意されていますが、
日付等をうまく扱えなさそうだったので、
今回はPlotlyで対話的なグラフを作成してそれを描画するst.plotly_chart()
を使っています。
その他にStreamlitの特徴的な機能としては、
st.map()
で地図上にデータをプロットできたり、
st.progress()
で時間がかかる処理に対してプログレスバーを表示できたりします。
このあたりも使って作り込んでみたかったですが、今回は割愛。
htmlをまったく意識せずに、短いPythonスクリプトだけで画面を作っていけるのは便利ですね。
AWSにデプロイ
想定されている使い方としては、手元のデータをサクッと確認したり結果をチームで共有したり、
っていうくらいかと思いますが、せっかくなのでAWS EC2インスタンスにデプロイしてテスト公開してみます。
手順:
1: AWS EC2無料枠のt2.microインスタンスを立てる
2: インスタンス内でアプリ実行
streamlit run streamlit_app.py
3: (必要に応じて)ドメイン取得
今回は無料ドメインサービスのfreenomで適当なドメイン名onedata.ga
を取得しました。
(「.tk」、「.ml」、「.ga」、「.cf」、「.gq」ドメインが無料で取得できます)
4: streamlitのconfigファイル編集
~/.streamlit
フォルダ内にある設定ファイル(config.toml
)に、
アクセス用のアドレスとして取得したドメイン名を記入しておきます。
[browser]
gatherUsageStats = false
serverAddress = "onedata.ga"
[server]
port = 8501
5: 80番から8501番へポート転送
streamlitはデフォルトでは8501番ポートで通信を受け付けます。
ドメイン名でポート指定せずアクセスするためにデフォルトの80番ポートからアクセスできるようにしたいのですが、
そのためにはアプリ実行時にroot権限が必要になります。
そこでここでは、iptablesをいじって80番ポートへのアクセスを8501番ポートへ転送することで対応します。
sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8501
6: 80番ポートの開放
EC2ダッシュボードのセキュリティグループ>インバウンドルールを編集し、80番ポートを開放しておきます。
作ったアプリがこちら↓
http://onedata.ga
(20230326追記:EC2料金節約のため停止中です)
実装↓
https://github.com/tkmz-n/streamlit_app
まとめ
Streamlitを使ってオープンデータを可視化する簡単なデモアプリを作成し、
AWSにデプロイして公開するまでをやってみました。
データを見ると、東京23区内の人の往来は3月末あたりから徐々に減りつつあることが確認できます。
新型コロナの一刻も早い収束を目指して、引き続きおうちに籠もりましょう。