よちよちpython

独習 python/Qpython/Pydroid3/termux/Linux

【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-142021-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以下

現役世代を潰してまで過剰対応する本末転倒な騒ぎの根拠は示されるべきだ。



データの結合


まぁ落ち着きたまえ。茶[emoji:984]でも飲みながら、つぎは試しに

  1. トータル死亡者数を日毎に変換
  2. 日付に結合

させてみる。
そののち他の各種データはひとまとめにする。

# トータル死者数を日毎に変換
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

[emoji:B27]

陽性率を出す 陽性者数/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")

f:id:chayarokurokuro:20210106022306j:plain

ピょコーンと飛び出してるのは何でだぜ?



次のも出してみる。

  • 陽性率の平均
  • 陽性率の中央値
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()


陽性者数(日毎)
f:id:chayarokurokuro:20210106024204j:plain

陽性者は感染者ではない。
ウイルスは細胞に入り込み自己増幅し、感染する。感染に必要なウイルスの数が少なければ感染に至らないという。
PCR検査は鼻の中に綿棒を突っ込む等して「ウイルスが着いてるか着いていないか」だけを確認している。ニュースでは「感染確認」と書いてある場合のみ信用すればいい。

風邪の諸症状を引き起こすウイルスのうち、15%がコロナウイルスという。人は誰でも年間に2、3回は風邪のウイルスに感染しているそうですが、気付きませんね。免疫が働いて退治してくれているので滅多に重症化しない。



検査数(日毎)
f:id:chayarokurokuro:20210106023957j:plain
検査数は、4月頃の第1波で1万、7月の第2波で2~4万、現在は8万。
検査数を増やしているので陽性者も当然増える。

患者数
f:id:chayarokurokuro:20210106023824j:plain
診察を厳しくすれば誰でも入院、甘ければ放置。

快復、復帰者数(トータル)
f:id:chayarokurokuro:20210106023556j:plain
50代以降の重症患者や死亡者は殆どいない。


死亡者数(日毎)
f:id:chayarokurokuro:20210106023442j:plain


直接の死因を医者が「新型コロナ」だとしなければ、このカウントに載らない。交通事故死でもコロナ陽性者であれば「コロナ死」にしてしまうような国があれば、どこまでも水増しできる。
国別の単純比較に意味があるかどうかは吟味されなければならない。



検査数を8万まで増やしている為に陽性者数が伸びている。増やせば増やすほど陽性者数は伸びる。ワクチン打っても感染させなくなる訳ではないので、この調子なら今後もずっと伸びる。

おわりに


政党や内閣支持率の調査でアンケート内容が同じであっても、言い回しの文言で回答結果が真逆に出たりするという。「数値は客観的だから間違いはない」と思いがちだがそうではなく、どのような手法や状況で集められたデータなのかを細かく見ないと分析の意味は無いのだろう。壊れた測定器やセンサーのデータはゴミよ。コロナ騒動の不毛さもこれに近いと思う。コロナ分析を真面目にやっているデータサイエンティストをあまり見掛けませんもの。
Pythonのプログラミングがどうの、AIがどうの、というより先に、とりあえず統計学を実習込みでやらないとデータ分析みたいなものは立ち入れない領域かと思う。
まず数値を疑わないと。

今回は(今回も)Pandasの使い方の練習ということで。

以上です。