データ分析

COVID-19の感染者数をPlotly Expressを使って動く
ヒートマップで表示する

はじめに

先日ブログの題材に取り上げたCOVID-19の感染者数をPlotly Expressを使ってサックと可視化してみました。

やりたいこと

感染者数のヒートマップを地図上にプロットして、日付け毎に動かしたい。
以下のようなグラフの濃淡を日毎に変えたいわけです。濃淡は感染者数を表ます。

f:id:banquet-kuma:20200406211544p:plain

地図上に表示したヒートマップ

Plotly Express

Pythonの可視化ライブラリPlotlyのwrapperで、簡単にjupyter notebook上でインタラクティブに可視化できます(アニメーションもOK!)。
時系列データをグラフ上で簡単に動かせるの特長です。感覚としては、matplotlibやseabornと同じくらい簡単に書けて、しかもインタラクティブなグラフを作れます。

pipでインストールしておきます。

 

pip install plotly-express

plotly.com

 

Plotly ExpressをJupyterLab上で使う

JupyterLabの拡張機能にplotly-extensionなるものがあり、これをインストールしておく必要があります。npmが必要になるので事前にインストールしておきます。

 

jupyter labextension install @jupyterlab/plotly-extension

www.npmjs.com

github.com


アニメーションの作り方

上記のPlotly Expressのサイトにおあつらえむきなグラフがあるので丸パクします(笑)以下の4行だけでアニメーションが作れます。

import plotly.express as px

df = px.data.gapminder()
fig = px.choropleth(df, locations="iso_alpha", color="lifeExp",\
   hover_name="country", animation_frame="year", range_color=[20,80])
fig.show()


すると以下の様なアニメーションが表示されます。

https://streamable.com/o9agzd


上記のコードのうち、

以下の項目をCOVID-19の感染者数のデータに差し替えます。

● df : 各国の日毎の感染者数のデータフレーム
● locations:ISO 3166-1 alpha-3に基づく国名コード
● color:濃淡を表す変数(今回の場合、感染者数)
● hover_name:地図上にカーソルを当てた時に表示する変数
● animation_frame:アニメーションを動かす軸(今回の場合、日付け)
● range_color:濃淡バーの表示範囲

感染者数を表すデータフレームの中に国名コードの列を作ってやる必要があります。
感染者数のデータは前回のブログ同様以下から入手しました。

http://data.humdata.org

コードを書く

#  必要なライブラリのインポート
import plotly.express as px
import pandas as pd
import numpy as np

# データの読み込み
Confirmed_df = pd.read_csv("time_series-ncov-Confirmed0122-0321.csv",header=1)

# 列名を変える
Confirmed_df.rename(columns={'#adm1+name': 'State', '#country+name': 'Country',"#affected+infected+value+num":"Infected person(per day)"},inplace=True)

#  "#date"列をdatetime64[ns]型 に変更
Confirmed_df["#date"] = pd.to_datetime(Confirmed_df["#date"], format='%Y-%m-%d')

print(Confirmed_df.head())

 


State Country #geo+lat #geo+lon #date Infected person(per day)
0 nan Afghanistan 33 65 2020/3/21 24
1 nan Afghanistan 33 65 2020/3/20 24
2 nan Afghanistan 33 65 2020/3/19 22
3 nan Afghanistan 33 65 2020/3/18 22
4 nan Afghanistan 33 65 2020/3/17 22


"Infected person(per day)"の列が1日の感染者数を表ます。

このデータフレームに国名コードを紐付けるために国名コードを取得する必要があります。以下から落とすことができます。

https://gist.github.com/tadast/8827699#file-countries_codes_and_coordinates-csv

# 国名コードを読み込む
ISO_alpha_3_df = pd.read_csv("countries_codes_and_coordinates.csv",header=0)

# コードが"AFG"という風に" "で囲まれているので、削除する。空白も削除しておく。
f_replace = lambda x:x.replace('"','').replace(' ','')
ISO_alpha_3_df = ISO_alpha_3_df.applymap(f_replace)

print(ISO_alpha_3_df.head())

 

Country Alpha-2 code Alpha-3 code Numeric code Latitude (average) Longitude (average)
0 Afghanistan AF AFG 4 33 65
1 Albania AL ALB 8 41 20
2 Algeria DZ DZA 12 28 3
3 AmericanSamoa AS ASM 16 -14.3333 -170
4 Andorra AD AND 20 42.5 1.6

# 国名と国名コードを紐つけた辞書を作る
dict_country = dict(zip(ISO_alpha_3_df["Country"], ISO_alpha_3_df["Alpha-3 code"]))

print(dict_country)

 

{'Afghanistan': 'AFG',
'Albania': 'ALB',
'Algeria': 'DZA',
'AmericanSamoa': 'ASM',
'Andorra': 'AND',
'Angola': 'AGO',
'Anguilla': 'AIA',
'Antarctica': 'ATA',
'AntiguaandBarbuda': 'ATG',
'Argentina': 'ARG',
'Armenia': 'ARM',
'Aruba': 'ABW',
・・・}

この時点で厄介な問題に直面しました。
国名コードのソースであるISO_alpha_3_dfでの国名("Country"列)と感染者数のソースであるConfirmed_dfの国名("Country"列)に表記揺れがあるため、
dict_countryから国名コードを取得できない国が発生します。どの国が漏れるか調べます。

# 国名コードが取れない国一覧
print(set(Confirmed_df.Country)-set(dict_country.keys()))

 

{'Antigua and Barbuda',
'Bahamas, The',
'Bosnia and Herzegovina',
'Burkina Faso',
'Cabo Verde',
'Cape Verde',
'Central African Republic',
'Congo (Brazzaville)',
'Congo (Kinshasa)',
'Costa Rica',
"Cote d'Ivoire",
'Cruise Ship',
'Czechia',
'Dominican Republic',
'East Timor',
'El Salvador',
'Equatorial Guinea',
'Eswatini',
'Gambia, The',
'Holy See',
'Iran',
'Korea, South',
'Kosovo',
'Moldova',
'New Zealand',
'North Macedonia',
'Papua New Guinea',
'Saint Lucia',
'Saint Vincent and the Grenadines',
'San Marino',
'Saudi Arabia',
'South Africa',
'Sri Lanka',
'Taiwan*',
'Tanzania',
'Trinidad and Tobago',
'US',
'United Arab Emirates',
'United Kingdom'}

感染者数のソース(Confirmed_df)にある166ヵ国中、36ヵ国が漏れることが分かりました。注目すべき国は手動で辞書に国名コードを登録しました。
それ以外の国は諦めました。

# 表記揺れで辞書に登録されない国は手動で登録する
dict_country["US"]="USA"
dict_country["United Kingdom"]="GBR"
dict_country["Iran"]="IRN"
dict_country["Taiwan*"]="TWN"
dict_country["Korea, South"]="KOR"


感染者数のソース(Confirmed_df)に国名コード列を作ります。

# Confirmed_dfにiso_alpha列を作る
# Confirmed_dfの"Country"列の国名がdict_countryのキーにあれば、
# dict_countryの値をConfirmed_dfの"iso_alpha"列に加える

def func(x,dict_country=dict_country):
    if x in list(dict_country.keys()):
        return dict_country[x]
    else:
        return None
        
Confirmed_df["iso_alpha"] = Confirmed_df["Country"].apply(func)

print(Confirmed_df.head())

 

State Country #geo+lat #geo+lon #date Infected person(per day) iso_alpha
0 nan Afghanistan 33 65 2020-03-21 00:00:00 24 AFG
1 nan Afghanistan 33 65 2020-03-20 00:00:00 24 AFG
2 nan Afghanistan 33 65 2020-03-19 00:00:00 22 AFG
3 nan Afghanistan 33 65 2020-03-18 00:00:00 22 AFG
4 nan Afghanistan 33 65 2020-03-17 00:00:00 22 AFG


これで日時/感染者数/国名コードが1つのデータフレームに紐付きました。
欠損値を確認します。

Confirmed_df.isnull().sum()

 

State 9420
Country 0
#geo+lat 0
#geo+lon 0
#date 0
Infected person(per day) 0
iso_alpha 2040
dtype: int64

"state"(州)と"iso_alpha"(国コード)列に多くの欠損があります。
"iso_alpha" 列の欠損は前述したとおり、感染者数のソースと国コードのソースに表記揺れが生じるために発生しています。
"iso_alpha"列に欠損があるとエラーが発生するため欠損行を削除しておきます。

# 'iso_alpha'列にNaNがある行を削除する
Confirmed_df.dropna(subset=['iso_alpha'], axis=0, inplace=True)


国別にデータフレームを作成し、縦につなげます。

# 国コードが取得できた国名の一覧をリストにする
country_list = list(set(Confirmed_df["Country"]))

# 各国別のデータフレームを格納するリストを作る
dataframe_list = []

# インデックス('pandas.core.indexes.datetimes.DatetimeIndex'をstr型に変更する)
# これは、Plotly Expressのanimation_frame指定する型はstr or int 型である必要があるため
f_strftime = lambda x:x.strftime('%Y-%m-%d')

# 国別の日毎の感染者数のデータフレームを作ってdataframe_list に格納する
for i in range(len(country_list)):
    df2 = Confirmed_df[Confirmed_df["Country"] == country_list[i]].sort_values(by="#date")
    df2 = pd.DataFrame(df2.groupby(['#date'])['Infected person(per day)'].sum())
    df2['Infected person(per day)'] = df2['Infected person(per day)'].diff()
    df2.dropna(subset=['Infected person(per day)'],inplace = True)
    df2["Date"] = df2.index.map(f_strftime)
    df2["Country"] = country_list[i]
    df2["iso_alpha"] = dict_country[country_list[i]]
    df2.reset_index(inplace=True, drop=True)
    dataframe_list.append(df2)

#各国のデータフレームを縦に繋ぐ
df_all = pd.concat(dataframe_list, axis=0)


感染者数そのもので濃淡を付けると、中国以外の国の変化が分かりにくいので、
感染者数の対数をとって変化を見やすくします。エラーがでないようにするために、感染者数に微小な数(1.0e-10)を足しておきます。

df_all["log(Infected person(per day))"]=np.log10(df_all["Infected person(per day)"]+1.0e-10)

print(df_all.head().to_markdown())

 

Infected person(per day) Date Country iso_alpha log(Infected person(per day))
0 0 2020-01-23 Togo TGO -10
1 0 2020-01-24 Togo TGO -10
2 0 2020-01-25 Togo TGO -10
3 0 2020-01-26 Togo TGO -10
4 0 2020-01-27 Togo TGO -10


ようやく可視化できるデータフレームが完成しまた。感染者が0の日は、"log(Infected person(per day)"の列が-10になっていますが、
濃淡バーを0以上に設定するので影響ないと思われます。最後にPlotly Expressで可視化します。

# グラフの作成
fig = px.choropleth(df_all, locations="iso_alpha", color="log(Infected person(per day))", \
         hover_name="Country", animation_frame="Date", range_color=[0,4],width=1000, height=800)
fig.show()


以下のような動くヒートマップが表示されます。

https://streamable.com/jdkpbr

最後に

Plotly Expressは可視化自体は簡単なのですが、グラフの種類によっては前処理が面倒なものもあります。
簡単なグラフはmatplotlibやseabornと同じ操作感で書けて、カーソルをあてることで値を確認できるので、便利です。

以下はkaggleのデータでEDAした時の様子です。折れ線グラフにカーソルをあてると値を確認できます。

streamable.com

TableauやPower BIと棲み分けて、効率的な可視化を行っていきたいです。

 

  • この記事を書いた人
  • 最新記事

マルチンゲール

材料工学専攻 ▶大手メーカーで生産技術▶データ解析の技術者派遣▶大手メーカーでデータサイエンティスト | データ解析やキャリアについて発信します|特許登録8件、経産省AI Quest2期修了

-データ分析

© 2022 製造業のDXに挑むデータサイエンティストのブログ Powered by AFFINGER5