こんな人にオススメ
新型コロナウイルスの感染者数や死者数を実際の都道府県の地図上に示したい!
ということで、今回はplotly
のmapbox
を使用して新型コロナウイルス感染症の累計の感染者数と死者数を都道府県別に図示する。色んな表現方法があるが、今回は散布図で作成。
以前、新コロの感染者数と死者数についてグラフを作成した。これらは棒グラフなどを使用して、より詳細にデータを示した。しかし、今回は地図上なのでより直感的。
地図を扱うことができるようになると、あらゆるデータを地図とともに示して、より相手にわかりやすくなるだろう。
python環境は以下。
- Python 3.9.6
- numpy 1.21.1
- pandas 1.3.1
- plotly 5.1.0
- plotly-orca 3.4.2
作成したコード全文
使用するデータ
今回使用するデータはNHKの「特設サイト 新型コロナウイルス」の「新型コロナ データ一覧」もの。データ自体は編集・加工していないが、ファイル容量を減らしたグラフ化のためにデータの一部のみを使用した場面もある。
なお、データは2021年8月21日時点のもので、日が経過するとデータが変わったり更新されたりするので注意。このサイトには以下のような文言があるので、過去のデータを鵜呑みにし続けてはいけない。
※東京都は2021年2月15日、新型コロナの感染確認者数について、都内の保健所から報告漏れがあったとして、838人を追加で発表しました。(追加発表の対象期間:2020年11月18日~2021年1月31日)
また、今回は地図上の各都道府県に感染者数・死者数の値を入れるので、どの位置にどの都道府県があるのかを知らなくてはいけない。
ということで、「みんなの知識 ちょっと便利調」から「都道府県庁所在地 緯度経度データ」を使用した。このサイトではExcelファイルが公開されているので使用。
都道府県の位置は各庁(県庁など)をプロットする位置とした。
NHKの感染者数・死者数のグラフ
NHKでは、都道府県ごとの感染者数が都道府県の形状に沿って色付けされている。これに似たグラフを作成しようとしたが、ちょっと複雑そうだったので、とりあえずは散布図。
公開されているのは感染者数だが、死者数については地図がない。パッと作成できそうだがないので、今回の散布図で雰囲気だけでも掴んでほしい。
感染者数・死者数のデータの形式
感染者数・死者数のデータは「.csv
」形式で以下のような構造となっている。
日付 | 都道府県コード | 都道府県名 | 各地の感染者数_1日ごとの発表数 | 各地の感染者数_累計 | 各地の死者数_1日ごとの発表数 | 各地の死者数_累計 |
2020/1/16 | 1 | 北海道 | 0 | 0 | 0 | 0 |
2020/1/17 | 1 | 北海道 | 0 | 0 | 0 | 0 |
2020/1/18 | 1 | 北海道 | 0 | 0 | 0 | 0 |
... | 0 | 0 | 0 | 0 | ||
2021/5/5 | 1 | 北海道 | 181 | 25204 | 2 | 878 |
2021/5/6 | 1 | 北海道 | 320 | 25524 | 5 | 883 |
2021/5/7 | 1 | 北海道 | 248 | 25772 | 4 | 887 |
2020/1/16 | 2 | 青森県 | 0 | 0 | 0 | 0 |
2020/1/17 | 2 | 青森県 | 0 | 0 | 0 | 0 |
2020/1/18 | 2 | 青森県 | 0 | 0 | 0 | 0 |
... | 0 | 0 | 0 | 0 | ||
2021/5/5 | 47 | 沖縄県 | 63 | 12778 | 0 | 137 |
2021/5/6 | 47 | 沖縄県 | 39 | 12817 | 0 | 137 |
2021/5/7 | 47 | 沖縄県 | 82 | 12899 | 1 | 138 |
日付は2020年1月16日から2021年の8月20日までで、毎日更新されている。都道府県コードは都道府県ごとに北から順番に1から振られている。あとは1日ごと、累計の感染者数・死者数のデータがある。
データは合計で583日分でそれが47都道府県分あるので合計で27,401行存在する。なお、NHKのデータについては以下のように書かれている。
各自治体や公的機関の発表数値を基にNHKがまとめたものです。現在は1日に1回程度更新し掲載しています。ダウンロードしたファイルのデータはその時点の数値であり、発表に基づいて、後から修正・更新されることがあります。
繰り返すようだが、過去のデータを鵜呑みにしてはいけない。というよりそもそも情報ってのは1つに絞ってどうこうってものじゃない。自分で取捨選択する方が情報が平均されて安全。
各都道府県の緯度・経度のデータ
各都道府県庁の緯度・経度のデータは上の画像のようになっている。セルの結合があって面倒だが、使用するのは「世界測地系(WGS84)」「10進数」の「緯度」と「経度」。
pandas
を使用することでこの列のデータだけを引っ張ってきて使用する。
下準備
import sys import numpy as np import pandas as pd import plotly.graph_objects as go import plotly.io as pio sys.path.append('../../') import plotly_layout_template as template
まずは下準備としてのimport
関連。plotly_layout_template
はplotly
の自作テンプレートでレイアウトをキレイに整えるために使用。
-
-
【随時更新 備忘録】plotlyのグラフ即席作成コード
続きを見る
データの作成
まずは必要となるデータを作成する。今回は2種類のデータを整形して使いやすいようにする。
- 各都道府県の感染者数・死者数
- 各都道府県庁の緯度・経度
感染者数・死者数
df_covid = pd.read_csv( './nhk_news_covid19_prefectures_daily_data.csv', index_col=0, )
感染者数・死者数についてはNHKのページからダウンロードしたファイルを読み込み、0列目にある日付をインデックスとした。出力は以下。
print(df_covid) # 都道府県コード 都道府県名 ... 各地の死者数_1日ごとの発表数 各地の死者数_累計 # 日付 ... # 2020/1/16 1 北海道 ... 0 0 # 2020/1/17 1 北海道 ... 0 0 # 2020/1/18 1 北海道 ... 0 0 # 2020/1/19 1 北海道 ... 0 0 # 2020/1/20 1 北海道 ... 0 0 # ... ... ... ... ... ... # 2021/8/16 47 沖縄県 ... 1 246 # 2021/8/17 47 沖縄県 ... 1 247 # 2021/8/18 47 沖縄県 ... 0 247 # 2021/8/19 47 沖縄県 ... 1 248 # 2021/8/20 47 沖縄県 ... 0 248 # [27401 rows x 6 columns]
緯度・経度
df_position = pd.read_excel( './latlng_data.xls', header=(0, 1, 2), index_col=0, skiprows=2, skipfooter=6, # データではない部分をスキップ )
緯度・経度のデータについてはExcel形式なので、読み込みにはread_csv
ではなくread_excel
を使用。基本的な使用方法はread_csv
と同等。
表のタイトル2行分と表の最後に書いてある注釈6行分をスキップしたのち、結合されている0, 1, 2行目をヘッダとして使用する。インデックスは衣装しないけど自治体コードにしておいた。
緯度と経度のデータは予め変数lat
, lon
に入れてわかりやすくしておく。ヘッダがMulticoluns
になっているので[]
をつなげてデータを取り出している。空白とかがあるのでそこは注意して選択する。
lat = df_position['世界測地系(WGS84)'][' 10進数']['緯度'] lon = df_position['世界測地系(WGS84)'][' 10進数']['経度']
特定の日付のデータを抽出する関数
def get_date_df(year: int, month: int, day: int): date = f'{year}/{month}/{day}' # ある特定の日付を選択 date_df = df_covid.loc[date] return date_df, date
今回はある特定の日付時点での累計感染者数・死者数をマップ化するので、作成したデータフレームから特定の日付のデータを抽出。get_date_df
関数の入力は年・月・日。
指定した日付を.loc
を使用してデータフレームから抽出。このデータと日付を返り値とする。
地図上に散布図をプロット(マップボックス)
def get_mapbox(df, size_data: str, color_data: str, cmin: float, cmax: float, size_scale: int): d = go.Scattermapbox( lat=lat, lon=lon, marker=dict( # マーカーサイズにするデータ size=np.log10(df[f"各地の{size_data}数_累計"]) * size_scale, # カラーにするデータ color=np.log10(df[f"各地の{color_data}数_累計"]), showscale=True, # カラーバーを作成するか否か colorscale='Jet', # カラースケール colorbar=dict( title=dict(text=f"累計{color_data}数(log)", side='right',), ), # カラーバーの表示色範囲 cmin=np.log10(cmin), cmax=np.log10(cmax), cauto=False, ), # customdataで追加の情報を一括管理 customdata=df[['都道府県名', '各地の感染者数_累計', '各地の死者数_累計']], hovertemplate='%{customdata[0]}<br>' + '累計感染数: %{customdata[1]}人<br>' + '累計死者数: %{customdata[2]}人<br>' + 'lat: %{lat}°<br>' + 'lon: %{lon}°<br>' + '<extra></extra>', ) return d
ここが今回のメインテーマになる部分。地図上に散布図を作成する場合はgo.Scattermapbox
を使用すればいい。これにlat
, lon
を入力するだけで地図上に散布図を作成可能。簡単。
今回はマーカーの色で感染者数(もしくは死者数)、マーカーのサイズで死者数(もしくは感染者数)を表すことにする。get_mapbox
関数ではそれぞれsize_data
, color_data
で指定している。
シンプルにデータだけを使用すると色が偏ったりマーカーのサイズが極端に大きくなるので、今回は以下のように対数(log)を使用して調整を入れている。
# マーカーサイズにするデータ size=np.log10(df[f"各地の{size_data}数_累計"]) * size_scale, # カラーにするデータ color=np.log10(df[f"各地の{color_data}数_累計"]),
また、customdata
を使用することで、複数個の追加データを指定することができる。1つだけならtext
で賄えるが複数個になるとcustomdata
が便利。
ここでは都道府県名と各地の感染者数、死者数の累計値をcustomdata
にし、hover
でそれぞれの値を表示するように設定した。
# customdataで追加の情報を一括管理 customdata=df[['都道府県名', '各地の感染者数_累計', '各地の死者数_累計']], hovertemplate='%{customdata[0]}<br>' + '累計感染数: %{customdata[1]}人<br>' + '累計死者数: %{customdata[2]}人<br>' + 'lat: %{lat}°<br>' + 'lon: %{lon}°<br>' + '<extra></extra>',
レイアウトの作成
def set_layout(date: str, color_data: str, size_data: str): title = f"{date}時点の日本各地の累計{color_data}数(色)"\\ f"と{size_data}数(サイズ)" layout = go.Layout( template=template.plotly_layout(), mapbox=dict( # マップの初期表示中心位置 center=dict(lat=lat.mean(), lon=lon.mean(),), zoom=5, style="open-street-map", # マップの種類 # pitch=0, # マップの傾き(正の数のみ受け付け、手前に傾く) # bearing=0, # 正の値で反時計回りに何度回転させるか ), title=title, showlegend=False, # デフォルトだと地図の外側に白い枠が広がっているから白い部分を削除 margin=dict(l=0, r=0, t=0, b=0), ) return layout
レイアウトではマップボックス特有のmapbox
という引数が存在する。これらのうち、使えそうなものを上のコードに書いておいた。他にもあるかもしれないが、パッと見はこんなもん。
zoom
は拡大率を示すが、いまいちどういう基準かがわからない。今回はいい感じになった5
を選択。pitch
は値を大きくしすぎると頭打ちとなって傾きが止まる。
コロナマップ作成(散布図やけど)
ということで、これまでの関数を総動員して新コロの感染者数・死者数の累計のマップを作成。上で書いた関数の引数をmapbox_graph
関数でも使用。
def mapbox_graph(year: int, month: int, day: int, size_data: str, color_data: str, cmin: float, cmax: float, size_scale=1): # 特定の日付のデータを抽出 date_df, date = get_date_df(year=year, month=month, day=day,) # プロットデータ作成 plot = [] # 都道府県と感染者数を同時に表示したい d = get_mapbox( df=date_df, size_data=size_data, color_data=color_data, cmin=cmin, cmax=cmax, size_scale=size_scale, ) plot.append(d) # レイアウト作成 layout = set_layout(date=date, color_data=color_data, size_data=size_data) fig = go.Figure(data=plot, layout=layout) # fig.show(config=template.plotly_config()) # グラフの保存 pio.orca.config.executable = '/Applications/orca.app/Contents/MacOS/orca' pio.write_html(fig, f"{color_data}.html") pio.write_image(fig, f"{color_data}.png")
あとは以下のように、用途に応じて引数を指定してあげると簡単に散布図版のコロナマップができてしまう。
# マーカーサイズを死者数、色合いを感染者とする mapbox_graph( year='2021', month='8', day='20', size_data='死者', color_data='感染者', cmin=1e3, cmax=5e5, size_scale=20, ) # マーカーサイズを感染者数、色合いを死者数とする mapbox_graph( year='2021', month='8', day='20', size_data='感染者', color_data='死者', cmin=1e1, cmax=5e3, size_scale=10, )
地図があるとわかりやすいな
以前作成した、新コロの感染者数と死者数のグラフはどちらも棒グラフなどで、パッと見で位置とデータが結びつきにくかった。
しかし、今回のグラフだと地図上でどれくらいなのかがわかりやすい。やっぱり都市圏だと感染者数が多いということがパッと見で分かる。
地図を使うことができたので、これを応用すると各都道府県を塗りつぶしたり3D棒グラフにしたりできる。より効果的に見せることができる。試行錯誤する。
関連記事
-
-
【plotly&Scattergl】大量のデータをplotlyで軽くグラフ化
続きを見る
-
-
【plt&棒グラフ】pythonのmatplotlibで棒グラフを作成してみる
続きを見る
-
-
【plotly&工夫】楽にグラフを描くためのplotlyの関数化
続きを見る
-
-
【plotly&バブルチャート】plotlyで各国の収入と平均寿命の時代変化をバブルチャートで描く
続きを見る