【Pandas】コロナのグラフ作成
はじめに
厚生労働省が公表している日本における新型コロナの各種データを、Pythonを用いてPandasの練習を兼ねてグラフ作成する。
・グラフ作成に利用する元データのURLは、下記のコード内の先頭に書いています。
・データの日付は、2021年1月3日が最新。開始日はデータの種類で異なるが、2020年3月14日から揃っているようです。
【実行環境】
Android
termux
Python3.9
Jupyter Notebook
<外部ライブラリ>
・Numpy
・Pandas
・matplotlib
目次
実装(Python)
コードを実行する際はネット上のurlから直接データをダウンロードしますので、インターネット接続が必要です。
# CSV形式データのurl 厚生労働省の「オープンデータ」から urls = [ "https://www.mhlw.go.jp/content/pcr_positive_daily.csv", "https://www.mhlw.go.jp/content/pcr_tested_daily.csv", "https://www.mhlw.go.jp/content/cases_total.csv", "https://www.mhlw.go.jp/content/recovery_total.csv", "https://www.mhlw.go.jp/content/death_total.csv", "https://www.mhlw.go.jp/content/pcr_case_daily.csv", "https://www.mhlw.go.jp/content/current_situation.csv" ] """ 上記のurlのデータ内容は以下の通り。 1. 陽性者数 (positive daily) 2. PCR検査実施人数 (tested daily) 3. 入院治療等を要する者の数 (cases total) 4. 退院又は療養解除となった者の数 (recovery total) 5. 死亡者数 (death total) 6. PCR検査の実施件数 (case daily) 7. 発生状況 (current situation) """ # ライブラリのインポート import pandas as pd import numpy as np import matplotlib.pyplot as plt # 各種データをそれぞれでDataFrameに df_posi = pd.read_csv(urls[0]) df_tested = pd.read_csv(urls[1]) df_cases_total = pd.read_csv(urls[2]) df_recovery = pd.read_csv(urls[3]) df_death_total = pd.read_csv(urls[4]) df_case_daily = pd.read_csv(urls[5]) df_situation = pd.read_csv(urls[6])
どんなデータか見てみる。
# 最終5行
df_posi.tail()
日付 | PCR 検査陽性者数(単日) | |
---|---|---|
349 | 2020/12/30 | 3845 |
350 | 2020/12/31 | 4322 |
351 | 2021/1/1 | 3106 |
352 | 2021/1/2 | 3045 |
353 | 2021/1/3 | 3127 |
# 最終行のみ全列表示 print(df_tested.iloc[-1:,:]) print(df_cases_total.iloc[-1:,:]) print(df_recovery.iloc[-1:,:]) print(df_death_total.iloc[-1:,:]) print(df_case_daily.iloc[-1:,:]) print(df_situation.iloc[-1:,:])
日付 PCR 検査実施件数(単日)
330 2021/1/3 20291
日付 入院治療を要する者
334 2021/1/3 38729
日付 退院、療養解除となった者
340 2021/1/3 198874
日付 死亡者数
324 2021/1/3 3598
日付 国立感染症研究所 検疫所 地方衛生研究所・保健所 民間検査会社 大学等 医療機関
319 2021/1/2 0 0 3272 NaN NaN NaN
Unnamed: 0 PCR検査\n実施人数 ※3 陽性者数 入院治療等を要する者の数 \
3 合計 4,953,004\n(+22,969) 243,847\n(+3,150) ※2 38,871\n(+712)
うち重症者の数 退院又は療養解除と\nなった者の数 死亡者数 確認中 ※4
3 731\n(+17)※6 200,676\n(+2,190) 3,599\n(+51) 1,293\n(-50)
下の2つの「case_daily」「situation」は文字が色々入っている。その他のは「通し番号列」と「日付列」と「データ列」。最終日付が2021/1/3
で揃っているようです。
試しにそのまま横に結合させてみる。
df_test = pd.concat([
df_posi,
df_tested,
df_cases_total,
df_recovery,
df_death_total,
df_case_daily,
df_situation],
axis=1
)
df_test
日付 | PCR 検査陽性者数(単日) | 日付 | PCR 検査実施件数(単日) | 日付 | 入院治療を要する者 | 日付 | 退院、療養解除となった者 | 日付 | 死亡者数 | ... | 大学等 | 医療機関 | Unnamed: 0 | PCR検査\n実施人数 ※3 | 陽性者数 | 入院治療等を要する者の数 | うち重症者の数 | 退院又は療養解除と\nなった者の数 | 死亡者数 | 確認中 ※4 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2020/1/16 | 1 | 2020/2/5 | 4.0 | 2020/2/4 | 15.0 | 2020/1/29 | 1.0 | 2020/2/14 | 1.0 | ... | 79.0 | NaN | 国内事例 ※1,※5\n(チャーター便帰国\n者を除く) | 4,540,928\n(+20,291) | 241,902\n(+3,127) ※2 | 38,729\n(+698) | 731\n(+17)※6 | 198,874\n(+2,181) | 3,598\n(+51) | 1,293\n(-50) |
1 | 2020/1/17 | 0 | 2020/2/6 | 19.0 | 2020/2/5 | 16.0 | 2020/1/30 | 1.0 | 2020/2/15 | 1.0 | ... | 0.0 | NaN | 空港検疫 | 411,247\n(+2,678) ※7 | 1,930\n(+23) | 142\n(+14) | 0 | 1,787\n(+9) | 1 | 0 |
2 | 2020/1/18 | 0 | 2020/2/7 | 9.0 | 2020/2/6 | 12.0 | 2020/1/31 | 1.0 | 2020/2/16 | 1.0 | ... | 0.0 | NaN | チャーター便\n帰国者事例 | 829 | 15 | 0 | 0 | 15 | 0 | 0 |
3 | 2020/1/19 | 0 | 2020/2/8 | 4.0 | 2020/2/7 | 12.0 | 2020/2/1 | 1.0 | 2020/2/17 | 1.0 | ... | 108.0 | NaN | 合計 | 4,953,004\n(+22,969) | 243,847\n(+3,150) ※2 | 38,871\n(+712) | 731\n(+17)※6 | 200,676\n(+2,190) | 3,599\n(+51) | 1,293\n(-50) |
4 | 2020/1/20 | 0 | 2020/2/9 | 10.0 | 2020/2/8 | 7.0 | 2020/2/2 | 1.0 | 2020/2/18 | 1.0 | ... | 19.0 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
349 | 2020/12/30 | 3845 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
350 | 2020/12/31 | 4322 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
351 | 2021/1/1 | 3106 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
352 | 2021/1/2 | 3045 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
353 | 2021/1/3 | 3127 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
354 rows × 25 columns
不要な列(途中の日付など)が入ったり、行数も違うしずれている。なにか工夫がいる。
一旦、これは置いておこう。
先に日毎の死亡者数を出したいのでトータル死者数(death_total)を加工する作業に移る。
死者数のトータル
新型コロナが直接の死因とされて亡くなった人は現在までにどのくらいいるのか。
↓にあるように、日付は2020-02-14
~ 2021-01-03
約11ヶ月間のトータル。
# トータル死亡者数
df_death_total
日付 | 死亡者数 | |
---|---|---|
0 | 2020/2/14 | 1 |
1 | 2020/2/15 | 1 |
2 | 2020/2/16 | 1 |
3 | 2020/2/17 | 1 |
4 | 2020/2/18 | 1 |
... | ... | ... |
320 | 2020/12/30 | 3413 |
321 | 2020/12/31 | 3459 |
322 | 2021/1/1 | 3513 |
323 | 2021/1/2 | 3547 |
324 | 2021/1/3 | 3598 |
325 rows × 2 columns
累計数が最下行の3598 人。(2020/02/14 - 2021/01/3)。
最近は1日に50人ほど亡くなっているので、このペースで行けばあと1ヶ月で5000人/年を超えるかな。
日本では、
- 毎年130万人以上が亡くなる
- 1日平均で約3600人
新型コロナの年間死亡者数と、1日で亡くなる人数はほぼ等しく、その死亡者数は日本全体の年間死亡者数の300分の1未満程度に過ぎなかった。グラフにすればコロナは殆ど存在が隠れる。
その他、新型コロナの死亡者数は、
- インフルエンザの死亡者の2分の1から3分の1
- 肺炎の死亡者の20分の1以下
現役世代を潰してまで過剰対応する本末転倒な騒ぎの根拠は示されるべきだ。
データの結合
まぁ落ち着きたまえ。茶でも飲みながら、つぎは試しに
- トータル死亡者数を日毎に変換
- 日付に結合
させてみる。
そののち他の各種データはひとまとめにする。
# トータル死者数を日毎に変換 np_death_daily = np.append(1, np.diff(df_death_total.iloc[:,1])) df_death_daily = pd.concat([df_death_total.iloc[:,0], pd.Series(np_death_daily, name="死亡者数(日毎)")], axis=1) df_death_daily
日付 | 死亡者数(日毎) | |
---|---|---|
0 | 2020/2/14 | 1 |
1 | 2020/2/15 | 0 |
2 | 2020/2/16 | 0 |
3 | 2020/2/17 | 0 |
4 | 2020/2/18 | 0 |
... | ... | ... |
320 | 2020/12/30 | 65 |
321 | 2020/12/31 | 46 |
322 | 2021/1/1 | 54 |
323 | 2021/1/2 | 34 |
324 | 2021/1/3 | 51 |
325 rows × 2 columns
日毎の死亡者数データを作成し結合させた。次は他のデータもつなげる。
形状を確かめる。
# 形状 df_shape = ([ df_posi.shape, df_tested.shape, df_cases_total.shape, df_recovery.shape, df_death_total.shape, df_death_daily.shape ]) min_len = np.array(df_shape)[:,0].min() print("形状\n",df_shape) print("\n最小行数\n",min_len)
形状
[(354, 2), (331, 2), (335, 2), (341, 2), (325, 2), (325, 2)]
最小行数
325
- 最後の2つ「death_total」「death_daily」は行数が最小なので、日付はこれを基準に合わせる。
- データは2列目(index番号は1)に入っている。
- 最終日付は2021/1/3で揃っているので後ろから読む。
- join='inner'で内部結合
- axis=1で横向きに結合
詳しい説明はこちら
(https://note.nkmk.me/python-pandas-concat/)
df = pd.concat([ df_death_total.iloc[-min_len:,0],# 日付列のみ df_posi.iloc[-min_len:,1], df_tested.iloc[-min_len:,1], df_cases_total.iloc[-min_len:,1], df_recovery.iloc[-min_len:,1], df_death_daily.iloc[-min_len:,1] ], join="inner", axis=1) df
日付 | PCR 検査陽性者数(単日) | PCR 検査実施件数(単日) | 入院治療を要する者 | 退院、療養解除となった者 | 死亡者数(日毎) | |
---|---|---|---|---|---|---|
29 | 2020/3/14 | 7 | 699 | 258 | 30 | 2 |
30 | 2020/3/15 | 12 | 553 | 271 | 30 | 1 |
31 | 2020/3/16 | 6 | 147 | 323 | 31 | 2 |
32 | 2020/3/17 | 7 | 110 | 366 | 31 | 4 |
33 | 2020/3/18 | 7 | 1314 | 377 | 32 | 1 |
... | ... | ... | ... | ... | ... | ... |
320 | 2020/12/30 | 2019 | 63512 | 26816 | 151949 | 65 |
321 | 2020/12/31 | 2419 | 60261 | 27147 | 153971 | 46 |
322 | 2021/1/1 | 2507 | 32677 | 26863 | 156695 | 54 |
323 | 2021/1/2 | 2425 | 11963 | 27077 | 159176 | 34 |
324 | 2021/1/3 | 2497 | 74383 | 28154 | 161686 | 51 |
296 rows × 6 columns
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 296 entries, 29 to 324
Data columns (total 6 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 日付 296 non-null object
1 PCR 検査陽性者数(単日) 296 non-null int64
2 PCR 検査実施件数(単日) 296 non-null int64
3 入院治療を要する者 296 non-null int64
4 退院、療養解除となった者 296 non-null int64
5 死亡者数(日毎) 296 non-null int64
dtypes: int64(5), object(1)
memory usage: 14.0+ KB
df.describe()
PCR 検査陽性者数(単日) | PCR 検査実施件数(単日) | 入院治療を要する者 | 退院、療養解除となった者 | 死亡者数(日毎) | |
---|---|---|---|---|---|
count | 296.000000 | 296.000000 | 296.000000 | 296.000000 | 296.000000 |
mean | 539.662162 | 14849.334459 | 7587.087838 | 44124.293919 | 12.091216 |
std | 591.566837 | 15405.808679 | 6774.351500 | 43640.374809 | 14.382491 |
min | 6.000000 | 42.000000 | 258.000000 | 30.000000 | 0.000000 |
25% | 59.750000 | 3788.000000 | 1936.500000 | 8805.750000 | 3.000000 |
50% | 398.000000 | 9568.500000 | 5876.000000 | 21199.500000 | 7.000000 |
75% | 708.250000 | 21865.250000 | 11078.250000 | 78298.500000 | 15.000000 |
max | 2674.000000 | 103676.000000 | 28154.000000 | 161686.000000 | 91.000000 |
陽性率を出す 陽性者数/PCR検査件数(日毎)
陽性者数(単日)を、1日あたりのPCR検査件数で単純に割って率を計算する。
- 陽性率 = 陽性者数(単日) / 検査件数(単日)
「日付」以外の列を、「PCR検査実施件数(単日)」で日毎に割っていく。
r_posi_tested = df.iloc[:,1]/df.iloc[:,2] r_posi_tested
29 0.010014
30 0.021700
31 0.040816
32 0.063636
33 0.005327
...
320 0.031789
321 0.040142
322 0.076721
323 0.202708
324 0.033569
Length: 296, dtype: float64
これをグラフにしてみる。
- 横軸 : 日付
- 縦軸 : 陽性率
import matplotlib.pyplot as plt # 横軸 day = df.iloc[:,0] # 縦軸 r_posi_tested = df.iloc[:,1]/df.iloc[:,2] # グラフ作図 # プロット plt.plot(day, r_posi_tested) # タイトル表示 plt.title("positive tested ratio") # グリッド表示 plt.grid() # 横軸の間隔をあける plt.xticks(day[::31], rotation=45) # グラフ表示 plt.show() # グラフ画像保存 plt.savefig("positive_tested_ratio.jpg")
ピょコーンと飛び出してるのは何でだぜ?
次のも出してみる。
- 陽性率の平均
- 陽性率の中央値
print("陽性率平均値\n",r_posi_tested.mean()) print("陽性率中央値\n",r_posi_tested.median())
陽性率平均値
0.07361082809333053
陽性率中央値
0.030277772354372533
中央値で3%。
陽性者数
は検査件数
×0.03
で大体だせる。陽性者数で騒ぎたければ検査件数を増やせばよろしい。
逆に陽性率を下げたければ、感染の少なさそうな地方の田舎で大量に検査すればよい。
相関関係
相関係数は-1 ~ 1
の値。Numpyで出してみる。
np_array = np.array(df) coef = np.corrcoef([np_array[:,1].astype(float), np_array[:,2].astype(float), np_array[:,3].astype(float), np_array[:,4].astype(float), np_array[:,5].astype(float)]) coef
array([[1. , 0.60309613, 0.86919031, 0.7659963 , 0.68373287],
[0.60309613, 1. , 0.70134416, 0.74273002, 0.54073107],
[0.86919031, 0.70134416, 1. , 0.73017693, 0.83177733],
[0.7659963 , 0.74273002, 0.73017693, 1. , 0.59251197],
[0.68373287, 0.54073107, 0.83177733, 0.59251197, 1. ]])
見にくい。なぜNumpyでやったのか。Pandasで表にする。
index = ["positive","tested","case","recovery","death_daily"] r_df = pd.DataFrame(coef, index=index, columns=index) r_df
positive | tested | case | recovery | death_daily | |
---|---|---|---|---|---|
positive | 1.000000 | 0.603096 | 0.869190 | 0.765996 | 0.683733 |
tested | 0.603096 | 1.000000 | 0.701344 | 0.742730 | 0.540731 |
case | 0.869190 | 0.701344 | 1.000000 | 0.730177 | 0.831777 |
recovery | 0.765996 | 0.742730 | 0.730177 | 1.000000 | 0.592512 |
death_daily | 0.683733 | 0.540731 | 0.831777 | 0.592512 | 1.000000 |
相関係数は-1 ~ 1
の値をとり、1
に近いほど強い正の相関関係がある。
たとえば、左端の行名「tested」を横に見ていくと、PCR検査人数を増やせば入院等「case」や快復「recovery」は0.7
以上で高めなので増えるということだが、日毎死亡者数「death_daily」は0.5
で関係が薄い。これは奇妙です。コロナと関係がないということなのか?
若年者などコロナと無関係な年齢層の検査や患者が増えても死亡者数の増加に関わりがないという感じかな。
おまけに言うと、緊急事態宣言などで飲食店の夜の時短をやるのは感染抑止には無意味な対策だ。ウイルスは昼間休んでいる訳ではない。重症患者や死亡者の多いお年寄りは夜に繁華街を飲み歩いたりしない。欧米でも効果が出ていない。
グラフ
各種データを日毎の推移で見てみる。
# 日付が画像からはみ出すので、置換 df.iloc[:,0] = df.iloc[:,0].str.replace("2020/","") df.iloc[:,0] = df.iloc[:,0].str.replace("2021/","")
# 横軸(日付の抽出) x = df.iloc[:,0] for i in range(1,6): # 縦軸 y = df.iloc[:,i] # グラフ作成 plt.plot(x,y) plt.xticks(x[::31]) # 31日ごとに目盛 plt.title(index[i-1]) #indexは相関関係の所作った変数 plt.savefig("{}.jpg".format(index[i-1])) ##画像保存 plt.show()
陽性者数(日毎)
陽性者は感染者ではない。
ウイルスは細胞に入り込み自己増幅し、感染する。感染に必要なウイルスの数が少なければ感染に至らないという。
PCR検査は鼻の中に綿棒を突っ込む等して「ウイルスが着いてるか着いていないか」だけを確認している。ニュースでは「感染確認」と書いてある場合のみ信用すればいい。
風邪の諸症状を引き起こすウイルスのうち、15%がコロナウイルスという。人は誰でも年間に2、3回は風邪のウイルスに感染しているそうですが、気付きませんね。免疫が働いて退治してくれているので滅多に重症化しない。
検査数(日毎)
検査数は、4月頃の第1波で1万、7月の第2波で2~4万、現在は8万。
検査数を増やしているので陽性者も当然増える。
患者数
診察を厳しくすれば誰でも入院、甘ければ放置。
快復、復帰者数(トータル)
50代以降の重症患者や死亡者は殆どいない。
死亡者数(日毎)
直接の死因を医者が「新型コロナ」だとしなければ、このカウントに載らない。交通事故死でもコロナ陽性者であれば「コロナ死」にしてしまうような国があれば、どこまでも水増しできる。
国別の単純比較に意味があるかどうかは吟味されなければならない。
検査数を8万まで増やしている為に陽性者数が伸びている。増やせば増やすほど陽性者数は伸びる。ワクチン打っても感染させなくなる訳ではないので、この調子なら今後もずっと伸びる。
おわりに
政党や内閣支持率の調査でアンケート内容が同じであっても、言い回しの文言で回答結果が真逆に出たりするという。「数値は客観的だから間違いはない」と思いがちだがそうではなく、どのような手法や状況で集められたデータなのかを細かく見ないと分析の意味は無いのだろう。壊れた測定器やセンサーのデータはゴミよ。コロナ騒動の不毛さもこれに近いと思う。コロナ分析を真面目にやっているデータサイエンティストをあまり見掛けませんもの。
Pythonのプログラミングがどうの、AIがどうの、というより先に、とりあえず統計学を実習込みでやらないとデータ分析みたいなものは立ち入れない領域かと思う。
まず数値を疑わないと。
今回は(今回も)Pandasの使い方の練習ということで。
以上です。