1
5

More than 3 years have passed since last update.

pandas と matplot で期間をレンジにしたグラフを描きたい。(超初心者)

Last updated at Posted at 2021-03-19

English

COVID-19の厚生労働省の公開データ(厚生省のが2020/2/14からの集計)を使ってグラフを描こうとしたところ NHK 発表データ(NHKのが2020/1/16からの集計)と重ね合わせたいと思った。

この記事で必要になるものは、厚労省のオープンデータと、NHKの発表するオープンデータみたいなもの(ダウンロードしてから使用)です。厚労省のオープンデータのダウンロード先はこの記事の文中のコードブロックの中と、文中にあります。

NHKのオープンデータみたいなもの

全くグラフの描き方は知らないが、できた。
395日間死亡数 (1).png

参考にしたのは、この方の公開されているもの。
https://oku.edu.mie-u.ac.jp/~okumura/python/COVID-19.html
自分にはグラフのプロットの知見無いので、ちゃんとグラフを描けているコードそのままコピーして、実行してみてから、テンプレートにして変形していった。
この方のプログラムを見ると、かなり初期、オープンデータが無いころからコロナウィルスの感染状況を可視化に取り組まれていたようです。

できたが、おかしいところがあるので、その部分をよく見たいと思った。

このグラフは何かというと、コロナウィルス感染による死者の数の一日づつの数字。
奇妙なことには、厚生省の公開データと、NHKの発表データとは、累計総数も違う。同じ数だったら重ねるとぴったり一致するはずです。

じゃあ、他の人は、どうしてるのかとNHKの集計データをつかっているオープンデータのところ(なんていうのかな。オープンデータを使ってオープンデータを公開しているサイトのことを)を見ると・・・

Data Discrepancies
Our data can sometimes disagree with MHLW or prefectural governments because of different policies we are using to input the data. Here are our known discrepancies and why:

  • National death counts. We are counting more deaths than MHLW. Our death counts are aligned with NHK's reporting. We are unclear why MHLW is reporting less deaths.

とのこと。

MHLWとは、なに?・・・厚生労働省( Ministry of Health, Labour and Welfare of Japan )のこと。

まぁ、違うんだ、ノイズか自分のミスかと思ってもいたが、どうやら、そうでもないかもしれない。

なので、やはり、その部分がよく見たい。

その部分とは、4月から5月にかけてのピョコンとでたとこと、5月のあたまらへんのピョコンだ。
だから、そのぴょこっとしたのを含む、2週間くらいを含むクローズアップが見たい。それが表題の指す具体的なこと。

425日分(NHK)のデータから、必要な30日分(これをレンジ=範囲とする)だけのデータをグラフにしたい。

まず最初のグラフを描くためのプログラムコードは、こうなっていた。

import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import pandas as pd

#mhlw1 = pd.read_csv("https://www.mhlw.go.jp/content/pcr_positive_daily.csv", parse_dates=['日付'])
mhlw2 = pd.read_csv("https://www.mhlw.go.jp/content/death_total.csv", parse_dates=['日付'])
#mhlw3 = pd.read_csv("https://www.mhlw.go.jp/content/pcr_tested_daily.csv", parse_dates=['日付'])
#mhlw4 = pd.read_csv("https://www.mhlw.go.jp/content/severe_daily.csv", parse_dates=['日付'])


#nhk = pd.read_csv('https://www3.nhk.or.jp/n-data/opendata/coronavirus/nhk_news_covid19_domestic_daily_data.csv', parse_dates=['日付'])
#ダウンロードした
nhk2 = pd.read_csv(r'/content/nhk_news_covid19_domestic_daily_data.csv', parse_dates=['日付'])

print(len(nhk2))#データの日数 2021-03-16時点で425日
print(len(mhlw2))#データの日数 2021-03-16時点で396日

locator = mdates.AutoDateLocator()
formatter = mdates.ConciseDateFormatter(locator)
fig, ax = plt.subplots(figsize=(15, 3))

ax.xaxis.set_major_locator(locator)
ax.xaxis.set_major_formatter(formatter)

ax.bar(mhlw2['日付'], mhlw2['死亡者数'] - mhlw2['死亡者数'].shift(),width=1,color='red')
ax.bar(nhk2['日付'], nhk2['国内の死者数_1日ごとの発表数'] ,width =0.5, color='blue')

NHKのデータ(https://www3.nhk.or.jp/n-data/opendata/coronavirus/nhk_news_covid19_domestic_daily_data.csv)はこうなっていた。

            日付  国内の感染者数_1日ごとの発表数  国内の感染者数_累計  国内の死者数_1日ごとの発表数  国内の死者数_累計
0   2020-01-16                 1           1                0          0
1   2020-01-17                 0           1                0          0
2   2020-01-18                 0           1                0          0
3   2020-01-19                 0           1                0          0
4   2020-01-20                 0           1                0          0
..         ...               ...         ...              ...        ...
420 2021-03-11              1317      444332               45       8464
421 2021-03-12              1271      445603               58       8522
422 2021-03-13              1320      446923               51       8573
423 2021-03-14               988      447911               21       8594
424 2021-03-15               695      448606               38       8632

注意点としては、URLから直接 csv を読み込むと途中でエラーになるので、

!wget 'https://www3.nhk.or.jp/n-data/opendata/coronavirus/nhk_news_covid19_domestic_daily_data.csv'

wgetでダウンロードしてから使用した。

pd.read_csv(r'/content/nhk_news_covid19_domestic_daily_data.csv', parse_dates=['日付'])

というのは、ファイルまでの path になっている。path の場合、なにか特別なモジュール読み込まないとディレクトリ記号とか認識しないのかもなと思っていたが、rを付けたら不具合がなかった。

rについて
https://stackoverflow.com/questions/19034822/unknown-python-expression-filename-r-path-to-file

rを付けてパスを文字列で表記すると、r以降がraw stringsとして扱われるので、パスに含まれる\/がエスケープ記号では無いように認識されるということらしい。

厚生労働省(Ministry of Health, Labour and Welfare of Japan)のデータは、項目(死者数とか陽性者数とか検査数とか)ごとに別のファイルになっているので、別々に読み込む。日付は共通で付いている(はずですが見てません)。死亡数(death_total.csv)は日ごとに累計(足し算)のみ。

データはここから。

ひと、くらし、みらいのために厚生労働省オープンデータ

死亡者数
https://www.mhlw.go.jp/stf/covid-19/open-data.html
※1各報告日時点の集計値を記載しているため、各自治体のホームページ等で公表されている数値と異なる場合がある。
※2チャーター便を除く国内事例については、令和2年4月21日公表分から、データソースを従来の厚生労働省が把握した個票を積み上げたものから、各自治体がウェブサイトで公表している数等を積み上げたものに変更した。
※3国内事例には、空港検疫にて陽性が確認された事例を国内事例としても公表している自治体の当該事例数は含まれていない。
※4各自治体の報道発表と自治体のホームページ更新の時点が異なる場合があるため、個別の事例数を積み上げて算出した累計の死亡者数とは異なる場合がある。
※56月17日の事務連絡を踏まえ、新型コロナウイルスとの関連が不明な死亡者に係る取り扱いの見直しを行いホームページ上に公表した自治体については、見直しを行い公表した時点を報告日として計上している。

#mhlw1 = pd.read_csv("https://www.mhlw.go.jp/content/pcr_positive_daily.csv", parse_dates=['日付'])
mhlw2 = pd.read_csv("https://www.mhlw.go.jp/content/death_total.csv", parse_dates=['日付'])
#mhlw3 = pd.read_csv("https://www.mhlw.go.jp/content/pcr_tested_daily.csv", parse_dates=['日付'])
#mhlw4 = pd.read_csv("https://www.mhlw.go.jp/content/severe_daily.csv", parse_dates=['日付'])

厚生省データの死亡数(death_total.csv)は日ごとに累計

            日付  死亡者数
0   2020-02-14     1
1   2020-02-15     1
2   2020-02-16     1
3   2020-02-17     1
4   2020-02-18     1
..         ...   ...
391 2021-03-11  8449
392 2021-03-12  8507
393 2021-03-13  8558
394 2021-03-14  8588
395 2021-03-15  8620

pandas DataFrame でひとつ前のデータにアクセスするのはshift()というのでできるらしいので、以下のようにしたら、累計(足し算)の数から、1日で発生した数になるはず。

mhlw2['死亡者数'] - mhlw2['死亡者数'].shift()

pandas でどうやるのかわからないけど、累計から1日の発生数にしたデータを見たい、という場合は、

for i,l in enumerate(mhlw2['死亡者数']):
    d = 0
    if i > 0:
       d = l - mhlw2['死亡者数'][i-1]
    print(d)

このように確認してみた。

該当する日の累計から、前日分の累計を引き算している。例えば、10という値が入っていても、その日に10人亡くなったということではなくて、その日までに(その日を含む)10人亡くなったというのが累計なので、前日の値(これも累計)でちくいち引き算すると、前日までで1人亡くなっている場合は、その日は1日で9ということになります。
確かに、これでいいはず?
注意点としては、shift()の場合はpandas DataFrameに読み込まれた行と列になったデータの列の値を下に一個ずらす(shift)ということなので、下に1つずれたとき一番先頭の行の、列の一番上になにか値が入る(NaN)。
fillna(0)を付け足すとゼロで埋めてくれるらしい。でも、そこにはレンジの範囲の一個前から値を持ってきたいので、レンジに指定した日時の一日前のデータ(ここではfloat(mhlw00['死亡者数']))で埋める。

mhlw['死亡者数'] - mhlw['死亡者数'].shift().fillna(float(mhlw00['死亡者数']))

なんか長くなるけど、今日までの累計-昨日までの累計というイメージで見ると単純だ。ただ単に、それがしたい。

また、shift()すると、ずらした数値がすべて int 値から float 値にされるので float に型変換しておいて、その後の引き算が計算されるようにしておく。 float へ型変換していない場合は NaN で置き換えになってしまうので計算されない。たぶん、int 値のままだと値が入らない。
この挙動の違いはこうすると確認できる。見た方が分かりやすいというか、見ないとどうなっているのか気がつかない。

print('-'*55)
print(mhlw2[mask0]['死亡者数'] - mhlw2[mask0]['死亡者数'].shift())
print('-'*55)
print(mhlw2[mask0]['死亡者数'] - mhlw2[mask0]['死亡者数'].shift().fillna(float(mhlw2[mask00]['死亡者数'])))

shift()を使わずにmhlw2['死亡者数'].diff()でも同じになるかもしれない。

また、前もって確認しといた方がいいこととして、データの最後がどうなっているか?は、確認のために見ておいた方がいい。最後ってだいじ。
pandas DataFrameでリストなんかで使う「-1の添え字=最後」が使えそうなのはilocみたいだから、pd.iloc[-1]という形で見てみる。

print('NHK:',nhk2.iloc[-1])
print('-'*53)
print('MHLW:',mhlw2.iloc[-1])

期間を限定してグラフを描く

あとは、期間を限定してグラフにしたい。つまり、全期間のデータをズバッとグラフにしてくれる matplotlib に、ここの部分だけをグラフにしてくれませんかとお願いする手順を考えたら、こうしたらできた。ただ、できているが他にもやり方はあるだろう。全く知らない。この方法ではto_datetimeを使って日付けを時間として計算できるようにして、レンジ=範囲を用意している。

import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import pandas as pd

#mhlw1 = pd.read_csv("https://www.mhlw.go.jp/content/pcr_positive_daily.csv", parse_dates=['日付'])
mhlw2 = pd.read_csv("https://www.mhlw.go.jp/content/death_total.csv", parse_dates=['日付'])
#mhlw3 = pd.read_csv("https://www.mhlw.go.jp/content/pcr_tested_daily.csv", parse_dates=['日付'])
#mhlw4 = pd.read_csv("https://www.mhlw.go.jp/content/severe_daily.csv", parse_dates=['日付'])


#nhk2 = pd.read_csv('https://www3.nhk.or.jp/n-data/opendata/coronavirus/nhk_news_covid19_domestic_daily_data.csv', parse_dates=['日付'])
#ダウンロードした
nhk2 = pd.read_csv(r'/content/nhk_news_covid19_domestic_daily_data.csv', parse_dates=['日付'])

#print(len(nhk2)) #データの日数
#print(len(mhlw2)) #データの日数
#print('-'*53)
#print('NHK:',nhk2.iloc[-1]) #データの最後
#print('-'*53)
#print('MHLW:',mhlw2.iloc[-1]) #データの最後


locator = mdates.AutoDateLocator()
formatter = mdates.ConciseDateFormatter(locator)
fig, ax = plt.subplots(figsize=(10, 3))

ax.xaxis.set_major_locator(locator)
ax.xaxis.set_major_formatter(formatter)

mhlw2['日付'] = pd.to_datetime(mhlw2['日付'])
nhk2['日付'] = pd.to_datetime(nhk2['日付'])


start = '2020-04-15'
end   = '2020-05-15'

mask0 = (mhlw2['日付'] >= pd.Timestamp(start)) & \
       (mhlw2['日付'] <= pd.Timestamp(end))
mask1 = (nhk2['日付'] >= pd.Timestamp(start)) & \
       (nhk2['日付'] <= pd.Timestamp(end))

import pandas.tseries.offsets as offsets
mask00 = (mhlw2['日付'] >= pd.Timestamp(start)+offsets.Day(-1)) &\
         (mhlw2['日付'] < pd.Timestamp(start))

mhlw00 = mhlw2[mask00]#レンジで指定した期間の一日前
#print(mhlw2[mask00]['死亡者数'])
#print('-'*55)
#print(mhlw2[mask0]['死亡者数'] - mhlw2[mask0]['死亡者数'].shift())
#print('-'*55) ##比較
#print(mhlw2[mask0]['死亡者数'] - mhlw2[mask0]['死亡者数'].shift().fillna(float(mhlw2[mask00]['死亡者数'])))
mhlw = mhlw2[mask0]
nhk = nhk2[mask1]

#ax.plot(mhlw3['日付'], mhlw3['PCR 検査実施件数(単日)'],color='pink')
#ax.bar(mhlw1['日付'], mhlw1['PCR 検査陽性者数(単日)'],color='blue')
#ax.bar(mhlw4['日付'], mhlw4['重症者数'],color='green')
#ax.bar(mhlw2['日付'], mhlw2['死亡者数'] - mhlw2['死亡者数'].shift(),color='red')
ax.bar(mhlw['日付'], mhlw['死亡者数'] - mhlw['死亡者数'].shift().fillna(float(mhlw00['死亡者数'])),color='red')
ax.bar(nhk['日付'], nhk['国内の死者数_1日ごとの発表数'] ,width =0.3, color='blue')

厚労省のオープンデータと、NHKの公開しているCSVのデータをpandas DataFrameに読み込むと'日付'は文字列型になるようで、それを型変換しています。
mhlw2['日付'] = pd.to_datetime(mhlw2['日付'])
この部分です。
つづいて、(mhlw2['日付'] >= pd.Timestamp(start)) & \(mhlw2['日付'] <= pd.Timestamp(end))のところがブール演算。
start =< レンジ =< endの条件のレンジ(=範囲)をmask0が受け取っているということ。この手法の呼び方は、マスキングと条件文の論理積ということになるかもしれない。もっと詳しく知りたい場合「pandas」「期間」「マスキング」「論理積」で検索するとヒットするかもしれない。
例えば、pd.Timestamp

(mhlw2['日付'] >= pd.Timestamp(start))mhlw2['日付'] <= pd.Timestamp(end))が条件式で、この2つ両方を満たすというのが、論理積 &だけど、論理積のオペレーターに使えるのが&なのに注意が必要。andだと別の解釈になるよう。ここはなんか深い、というか「どっちだったっけ?」となりそうなので、深くつまづいておくほうがいいかも。
そうして、ここからここまで指定したmask0mhlw = mhlw2[mask0]として参照をmhlwに渡して、その後のax.bar()に引数として与えている。

厚生労働省のデータが赤で、NHKのデータが青

1月間死亡数.png

start = '2020-04-15'からend = '2020-05-15'までの期間のグラフが確認できる。

start = '2021-01-01'
end   = '2021-03-15'

この部分を変えるとレンジが変わります。ここでは元旦から3月15日までの間というのがレンジ(=期間)になります。
align = 'edge'を指定すると横並びのように棒グラフの位置をずらして表示。

ax.bar(mhlw['日付'], mhlw['死亡者数'] - mhlw['死亡者数'].shift().fillna(float(mhlw00['死亡者数'])),width =-0.3,align='edge',color='red')
ax.bar(nhk['日付'], nhk['国内の死者数_1日ごとの発表数'] ,width =0.3,align='edge', color='blue')

graph (2).png

さらに label を付け足して、 plt.legend() するとよりグラフっぽくなった気がする。( MHLW とは厚生労働省のこと)

ax.bar(mhlw['日付'], mhlw['死亡者数'] - mhlw['死亡者数'].shift().fillna(float(mhlw00['死亡者数'])),label='MHLW death',width =-0.3,align='edge',color='red')
ax.bar(nhk['日付'], nhk['国内の死者数_1日ごとの発表数'],label='NHK death',width =0.3,align='edge', color='blue')

plt.legend()

graph (3).png

NHKの発表のデータと厚生省の公表データを重ね合わせてみて見ると、2020-04-22と2020-05-08の死亡者数に大きな開きがでていることに気がつきます。発表のタイミングの違いとは別の面で気になるので、よく見てみると、同じ2020-04-22について、NHKだと16人の死者だけれども、厚生省のデータだと91人、2020-05-08について、NHKだと16人の死者だけれども、厚生省のデータだと49人というようになっています。

自分でプロットしたものではなく、他ではどうなっているのかを確認するために厚生労働省のデータを使われているグラフを探して、制作・運用:東洋経済オンライン編集部:では「4月22日より厚生労働省が確認中(突合作業中)のケースを含むため、また5月8日よりデータソースが変わったため」とよくあるご質問に掲載されています。2020-04-22の死亡者数はゼロ埋めになっています。

Toyo Keizai Online "Coronavirus Disease (COVID-19) Situation Report in Japan

新型コロナウイルス国内感染の状況
日本国内において現在確定している新型コロナウイルス感染症(COVID-19)の状況を厚生労働省の報道発表資料からビジュアル化した。
制作・運用:東洋経済オンライン編集部
最終更新:2021年3月16日
データソース:厚生労働省オープンデータ。4月22日より厚生労働省が確認中(突合作業中)のケースを含むため、また5月8日よりデータソースが変わったため、それぞれグラフの色を変えている。6月19日の死亡者数には、埼玉県においてPCR検査陽性者だが新型コロナウイルス感染症以外の疾病により死亡した患者13人が同日に一括計上された分が含まれる。その他の定義や注意事項はリンク先またはページ下部の注記を参照。

参照:
「札幌医大 フロンティア研 ゲノム医科学」管理人さんのご意見。
Q:「死亡者数について、ここで話題になっていますが、関連するので、もし、ご存じであれば教えていただきたいのですが、厚生省の公開データwww.mhlw.go.jp/stf/covid-19/kokunainohasseijoukyou.htmlと、NHKの発表データでは死者数が違いますよね。NHKの発表する死者数は累計でも日毎でも厚生省の公開データよりやや多いです。グラフにプロットするプログラムをいくつか比べていて、数が違うので、どうしてそうなるのかなとデータソースを比べていて不思議に思っているんですが、ご存知ではないでしょうか。」


では、期間をもうけて、その間の値をグラフに描くという他の例として、今年のコロナウィルス陽性者の数の値を厚生労働省データと、NHKデータを一緒に見てみましょう。

import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import pandas as pd

mhlw1 = pd.read_csv("https://www.mhlw.go.jp/content/pcr_positive_daily.csv", parse_dates=['日付'])
mhlw2 = pd.read_csv("https://www.mhlw.go.jp/content/death_total.csv", parse_dates=['日付'])
#mhlw3 = pd.read_csv("https://www.mhlw.go.jp/content/pcr_tested_daily.csv", parse_dates=['日付'])
#mhlw4 = pd.read_csv("https://www.mhlw.go.jp/content/severe_daily.csv", parse_dates=['日付'])

#'https://www3.nhk.or.jp/n-data/opendata/coronavirus/nhk_news_covid19_domestic_daily_data.csv'
nhk2 = pd.read_csv('/content/nhk_news_covid19_domestic_daily_data.csv', parse_dates=['日付'])

#print(nhk2)
#print(mhlw2)

locator = mdates.AutoDateLocator()
formatter = mdates.ConciseDateFormatter(locator)
fig, ax = plt.subplots(figsize=(15, 3))

ax.xaxis.set_major_locator(locator)
ax.xaxis.set_major_formatter(formatter)

mhlw2['日付'] = pd.to_datetime(mhlw2['日付'])
mhlw1['日付'] = pd.to_datetime(mhlw1['日付'])
nhk2['日付'] = pd.to_datetime(nhk2['日付'])


start = '2021-01-01'
end   = '2021-03-15'

mask2 = (mhlw2['日付'] >= pd.Timestamp(start)) & \
       (mhlw2['日付'] <= pd.Timestamp(end))
mask1 = (mhlw1['日付'] >= pd.Timestamp(start)) & \
       (mhlw1['日付'] <= pd.Timestamp(end))
mask01 = (nhk2['日付'] >= pd.Timestamp(start)) & \
       (nhk2['日付'] <= pd.Timestamp(end))

import pandas.tseries.offsets as offsets
mask00 = (mhlw2['日付'] >= pd.Timestamp(start)+offsets.Day(-1)) &\
         (mhlw2['日付'] < pd.Timestamp(start))

mhlw00 = mhlw2[mask00]
mhlw = mhlw2[mask2]
mhlw1 = mhlw1[mask1]
nhk = nhk2[mask01]
#print(mhlw0['死亡者数'])
#print(mhlw['死亡者数'].shift().fillna(float(mhlw0['死亡者数'])))

#ax.plot(mhlw3['日付'], mhlw3['PCR 検査実施件数(単日)'],color='pink')
#ax.bar(mhlw4['日付'], mhlw4['重症者数'],color='green')
ax.plot(mhlw1['日付'], mhlw1['PCR 検査陽性者数(単日)'],color='red')
ax.bar(nhk['日付'], nhk['国内の感染者数_1日ごとの発表数'] ,width =0.5, color='green')
#ax.plot(nhk2['日付'], nhk2['国内の死者数_1日ごとの発表数'] , color='blue')
#ax.bar(mhlw['日付'], mhlw['死亡者数'] - mhlw['死亡者数'].shift().fillna(float(mhlw00['死亡者数'])),color='red')
#ax.bar(nhk['日付'], nhk['国内の死者数_1日ごとの発表数'] ,width =1, color='blue')

plt.savefig("graph.png")

厚生労働省データ 赤

graph.png

グラフのスタイルが指定できるので、指定してみるといいかもしれない。

plt.style.use('fivethirtyeight')

こうなります。見やすいかも。

graph.png

グラフを画像として保存したければ

plt.savefig('graph.png')

で保存できる。

たぶん、この一連はすごく基本的なことなのだと思うが、ゼロ知識だと用語について語彙がないので、なかなか情報が集約できないから、例えばということで、このコードをコピーして jupyter notebook に貼り付けて、テンプレートにしていじくってみたら、もっとわかってくると思う。

必要な期間のデータをプロットできるようになると、あとは累計にするのどうするのかな?cumsum()
とか、扱いたいデータの形や必要に応じて調べていけばいい。

(qiita デザインが改新されてタブレットから投稿しやすくなったが、タブレットだと間違って押しやすいとこに delete ボタンが配置されるので、やはり間違って編集中にこの記事を消してしまい、残っていたブラウザのバッファからコピーして再投稿。)

1
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
5