📊

[streamlit]PythonでCovid-19オープンデータ可視化アプリを作ろう

2022/07/16に公開

はじめに

執筆時現在COVID-19感染が再び拡大しているという状況でして、新規陽性患者数など関連の様々な指標がニュースを賑わせています。ということで、今回はstreamlitを用いてCOVID-19の感染状況を確認する簡単なダッシュボードアプリを作ってみようと思います。streamlitのhow toというよりは折角のオープンデータを活用しようぜ!というモチベーションの方が強いです。

イメージ


最新のオープンデータをロードしてplotlyでグラフを描画


都道府県地図に直近の新規陽性者数を描画

ながれ

  • 政府提供のオープンデータの紹介
  • Streamlitによるダッシュボードアプリの実装
  • プログラム詳細の解説

おことわり

  • 政府が提供しているダッシュボードは立派です。今回ここまで取り組むモチベには至りませんでした。練習程度に捉えてください。
    https://covid19.mhlw.go.jp/extensions/public/index.html

  • Dockerで使用できるように作りましたが、アプリ化面倒臭いと言う場合はnotebookのファイルも用意していますので、GoogleColabなどでご利用ください。


オープンデータについて

ビッグデータ時代と言われて久しいですが、データという資産を公共のものとして有効活用していこうという動きは年々高まっています。データサイエンティストにとっても、探索段階からモデリングまで、自社のみで得られない要素を補完するものとして活用する機会はあるでしょう。

例えばe-statは、様々な政府統計を提供しています
https://www.e-stat.go.jp/

今回は、Covid-19に関するオープンデータを用います。ファイルの場所が固定されている上に毎日最新版に更新されるので、データのURLをPythonで読み込むようにしておけば、わざわざcsvでダウンロードする必要はありません。

https://www.mhlw.go.jp/stf/covid-19/open-data.html


streamlitによる実装

streamlitでアプリを作り、Docker環境に実装します。必要なファイルとフォルダ構成を記すと冗長になかと思い、Githubに格納しましたのでダウンロードして利用してください。コードのポイントだけ後の項で解説します。notebookのファイルも app_covid_19.ipynb同梱しています。

  • プログラムファイル等の場所

https://github.com/LittleDarwin2021/streamlit_covid19.git

dockerでの実装および後片付けは下記を参照ください。

https://zenn.dev/littledarwin/articles/581c15b3a061cb

ターミナルを立ち上げ、このディレクトリまで移動したら
docker compose up

起動出来たらブラウザでURLhttp://localhost:8501/を入力するとダッシュボードアプリが開きます。

操作

都道府県(または全国計)、表示期間、移動平均をとる日数をスライドバーで選択します。

選択した条件で新規陽性患者数、重傷者数、死亡者数(累計)を表示させます。plotlyを用いているので、マウスでインタラクティブにグラフ表示を操作することが可能です。

続いて日本地図を用いた可視化が2つ。一つは都道府県別の新規陽性患者数ですが人口の影響を大きく受けてしまうので人口当たりの新規陽性患者数をもう一つの図としています。


プログラムの解説

Dockerfile

プログラム本編ではありませんがDockerfileに少し戸惑いましたので備忘録。今回のアプリで日本地図描写する際にOpenCVが動くのですが、ただインストールするだけだと環境構築時に

ImportError: libGL.so.1: cannot open shared object file

というエラーが出ます。Dockerfileに

RUN apt-get update && apt-get upgrade -y
RUN apt-get install -y libgl1-mesa-dev

を追記することで解決しました。

requirements.txt

これも本編ではありませんがインストールが必要なライブラリです。

streamlit
pandas
numpy
matplotlib
plotly
japanmap
openpyxl
opencv-python

app.py

Pythonファイルでstreamlitを用いてアプリ内容を記述しています。

  • 政府が提供するオープンデータファイルのURLをそのまま変数に入れています。
#オープンデータのURL 使用していないものもある
newly_confirmed_cases_daily = "https://covid19.mhlw.go.jp/public/opendata/newly_confirmed_cases_daily.csv"
requiring_inpatient_care_etc_daily = "https://covid19.mhlw.go.jp/public/opendata/requiring_inpatient_care_etc_daily.csv"
deaths_cumulative_daily = "https://covid19.mhlw.go.jp/public/opendata/deaths_cumulative_daily.csv"
severe_cases_daily = "https://covid19.mhlw.go.jp/public/opendata/severe_cases_daily.csv"
population = "https://www.e-stat.go.jp/stat-search/file-download?statInfId=000032110815&fileKind=0"
  • 新規陽性者数、重傷者数、死亡者数(累積)をデータフレームに格納します。インデックスは日付形式に変更しています。
#オープンデータを読み込みデータフレームを作成
df01 = pd.read_csv(newly_confirmed_cases_daily, index_col="Date")
df01.index = pd.DatetimeIndex(df01.index).date
df02 = pd.read_csv(severe_cases_daily, index_col="Date")
df02.index = pd.DatetimeIndex(df02.index).date
df03 = pd.read_csv(deaths_cumulative_daily, index_col="Date")
df03.index = pd.DatetimeIndex(df03.index).date
  • 都道府県(複数選択)、表示期間、移動平均日数を選択肢やスライドバーで指定する記述です。ここで入力された値が都度グラフ情報として反映されます。
prefecture_slection = st.multiselect("都道府県を選択してください", df01.columns, default="ALL")

min_period = df01.index.min()
start_period = st.slider("開始日付",df01.index.min(), df01.index.max(),value=df01.index.min())
end_period = st.slider("終了日付",df01.index.min(), df01.index.max(), df01.index.max())

#移動平均の設定
moving_average = st.slider("移動平均の日数",1, 30,value=7)
df01 = df01.rolling(moving_average, min_periods=1).mean().round(1)
df02 = df02.rolling(moving_average, min_periods=1).mean().round(1)

  • plotlyでグラフを描画します。
  • 都道府県は複数選択されている可能性がありますので、その数だけfor分で描画を重ねています
fig= make_subplots(specs=[[{"secondary_y": True}]])
pre = prefecture_slection

for idx, prefecture in enumerate(pre):
    fig.add_trace(go.Bar(x=df01.index, y=df01[prefecture],name=(prefecture + "_新規陽性者数")))
    fig.add_trace(go.Bar(x=df02.index, y=df02[prefecture], name=(prefecture + "_重症者数")))
    fig.update_layout(barmode='overlay', xaxis=dict(range=(start_period, end_period)))
    fig.add_trace(go.Scatter(x=df03.index, y=df03[prefecture], name=(prefecture + "_累計死者数")))
                  
st.plotly_chart(fig, use_container_width=True) 
  • 日本地図描写のjapanmapでは日本語の都道府県名を用いるので、アルファベット表記になっているCovid-19データの前処理を行います。なおdf_today = df01.iloc[-1]は、表の一番下のデータつまり今日の感染者数を取り出す目的で実行しています。
df_today = df01.iloc[-1]
df_today =df_today.rename({"Hokkaido":"北海道", 
                           "Aomori":"青森","Akita":"秋田", "Iwate":"岩手", "Miyagi":"宮城","Yamagata":"山形", "Fukushima":"福島", 
                           "Ibaraki":"茨城", "Tochigi":"栃木", "Gunma":"群馬", "Saitama":"埼玉", "Chiba":"千葉", "Tokyo":"東京", "Kanagawa":"神奈川",
                           "Niigata":"新潟", "Toyama":"富山", "Ishikawa":"石川","Fukui":"福井", "Yamanashi":"山梨", "Nagano":"長野", 
                           "Gifu":"岐阜","Shizuoka":"静岡", "Aichi":"愛知", "Mie":"三重",
                           "Shiga":"滋賀", "Kyoto":"京都", "Osaka":"大阪","Hyogo":"兵庫", "Nara":"奈良", "Wakayama":"和歌山", 
                           "Tottori":"鳥取","Shimane":"島根", "Okayama":"岡山", "Hiroshima":"広島", "Yamaguchi":"山口",
                           "Kagawa":"香川", "Tokushima":"徳島","Ehime":"愛媛", "Kochi":"高知", 
                           "Fukuoka":"福岡", "Saga":"佐賀", "Nagasaki":"長崎", "Kumamoto":"熊本", "Oita":"大分", "Miyazaki":"宮崎", "Kagoshima":"鹿児島", "Okinawa":"沖縄"})
  • 地図に描画するための都道府県別の最新の新規陽性患者数を表すデータフレームを作ります。ここで人口あたりの感染者数を算出するため新たなデータセット(人口データ)を用いていますが、それはCovid-19オープンデータとは異なりexcelデータであったり、1行目からデータが始まっていなかったりするのでpd.read_excel(population, header = 5, skipfooter=1)という記述をしています。

  • なお、df_today = df_today[1:]としているのは一つ目のデータが"ALL"すなわち全国計なので、それを省くためです。

df_today = df_today[1:]

df_population = pd.read_excel(population, header = 5, skipfooter=1)
df_population = df_population.rename(columns = {"人":"人口"})
df_population = df_population[["都道府県名","人口"]].iloc[1:, :]
df_population["陽性患者数"] = df_today.values
df_population["人口あたりの陽性患者数"] = df_population["陽性患者数"] / df_population["人口"]
  • japanmapというライブラリを用いて人口あたりの新規陽性者数を日本地図に描いています。
  • カラーバーの濃淡は"人口あたりの陽性患者数"の最小-最大値を取得しています。
  • streamlitではst.pyplot(fig)という記述で描画します。
#人口あたり新規陽性者総数
cmap = plt.get_cmap('Blues')
norm = plt.Normalize(vmin=df_population["人口あたりの陽性患者数"].min(), vmax=df_population["人口あたりの陽性患者数"].max())
fcol = lambda x: '#' + bytes(cmap(norm(x), bytes=True)[:3]).hex()
fig = plt.figure(figsize=(4,4))
plt.colorbar(plt.cm.ScalarMappable(norm, cmap))
plt.imshow(picture(df_population["人口あたりの陽性患者数"].apply(fcol)))
st.pyplot(fig)

おわりに

つらつらと書いてみたものの、実際にブラウザで動かしながら確認いただいた方が分かりやすいかと思います。Pythonだけでアプリが作れるのは本当に便利ですね。オープンデータも活用しながら様々な課題を見える化していただければ幸いです。

過去の記事
https://zenn.dev/littledarwin/articles/8b5372e2f2c249

https://zenn.dev/littledarwin/articles/581c15b3a061cb

Discussion