Pandasのpivotとgroupbyで最新Covid-19データを処理

その買うを、もっとハッピーに。|ハピタス

Covid-19(新型コロナウイルス)の最新データをPandasのpivot(pivot_table), groupby, shift等を使って処理してグラフにしようと思います。ピボットとグループ化の違いや、データフレームのコピー処理についても触れているので、Python、特に、Pandasの初心者にとってかなり役立つ内容になっているのではないかと自負しています。

スポンサーリンク

データの準備

api.covid19api.comからデータを拝借します。日付のフォーマットはdt.strftimeを使うと確実に好みの形式に変換できます。

import requests as req
import pandas as pd

country = 'japan'
data = req.get(f"https://api.covid19api.com/live/country/{country}").json()
df = pd.DataFrame.from_dict(data)
df['Date'] = pd.to_datetime(df['Date'])
df['Date'] = df["Date"].dt.strftime('%Y/%m/%d')
df.head(2)
ID Country CountryCode Province City CityCode Lat Lon Confirmed Deaths Recovered Active Date
0 0226eeec-e655-44aa-bf79-e73d49f9528b Japan JP Kagoshima 31.01 130.43 3587 40 3562 0 2021/06/25
1 0268b26f-f8cf-40b3-8efd-7e9308b39609 Japan JP Mie 34.51 136.38 5216 111 5154 0 2021/06/25

都道府県がバラバラなので、都道府県毎に並べ替えます。

df1 = df.sort_values(by = ['Province',"Date"])
df1.head(2)
ID Country CountryCode Province City CityCode Lat Lon Confirmed Deaths Recovered Active Date
28 a9b6ef32-69f2-4b6e-9f5b-db4aa571fe09 Japan JP Aichi 35.04 137.21 50870 929 48158 1783 2021/06/25
69 82b39a05-8702-4b90-86f3-d287a88df2db Japan JP Aichi 35.04 137.21 50936 931 48305 1700 2021/06/26

必要なコラムだけ残します。

df2 = df1[["Date","Province", "Confirmed","Deaths"]]
df2.head(2)
Date Province Confirmed Deaths
28 2021/06/25 Aichi 50870 929
69 2021/06/26 Aichi 50936 931

このままだと、死者数・感染者数は累計なので、日毎の死者数・感染者数に変換する必要が有ります。この時に以下のようにシフトを用います。

df2['Daily Deaths']=(df2['Deaths'] -df2['Deaths'].shift(1)).fillna(0).astype(int)
df2.reset_index(drop=True, inplace=True)
df2.head(2)
/tmp/ipykernel_2480/2188879570.py:1: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  df2['Daily Deaths']=(df2['Deaths'] -df2['Deaths'].shift(1)).fillna(0).astype(int)
Date Province Confirmed Deaths Daily Deaths
0 2021/06/25 Aichi 50870 929 0
1 2021/06/26 Aichi 50936 931 2

上の長ったらしい警告は、dataframeのcopyに関する警告で、以下のようにして簡単に消すことができます。

df2 = df2.copy()
df2['Daily Deaths']=(df2['Deaths'] -df2['Deaths'].shift(1)).fillna(0).astype(int)
df2.reset_index(drop=True, inplace=True)
df2.head(2)
Date Province Confirmed Deaths Daily Deaths
0 2021/06/25 Aichi 50870 929 0
1 2021/06/26 Aichi 50936 931 2
スポンサーリンク

都道府県毎にグラフ化

適当な都道府県をピックしてグラフ化しますが、各都道府県の先頭行は累計なので、ここの部分は省く必要があります。

from matplotlib.pyplot import *
from matplotlib.font_manager import FontProperties
from matplotlib import rcParams
style.use('ggplot')

fp = FontProperties(fname='/usr/share/fonts/opentype/ipaexfont-gothic/ipaexg.ttf', size=54)
rcParams['font.family'] = fp.get_name()
rcParams["font.size"] = "20"
fig, ax = subplots(figsize=(25,14))

df2.loc[df2['Province'] == 'Kanagawa'][['Date', 'Daily Deaths']][50:70].set_index('Date').plot(ax=ax,kind='barh',color='g',alpha=0.5,  width= .8, align = 'center')

xticks(np.arange(0,16,2));
for i in ax.patches:
    ax.text(i.get_width()+.1,i.get_y()+.5,\
      '{:,}人'.format(int(round((i.get_width()), 2))), fontsize=20, color='k')
ax.legend(["神奈川県",""],loc='best', prop={'size': 26});

fig.tight_layout()
ax.invert_yaxis();
スポンサーリンク

ピボットテーブルを使う

上のやり方よりもスマートにするために、pivot tableを使います。

pvt = df.pivot_table(index=['Date'], columns=[ 'Province'], values = 'Deaths')
pvt.tail(5)
Province Aichi Akita Aomori Chiba Ehime Fukui Fukuoka Fukushima Gifu Gunma Shizuoka Tochigi Tokushima Tokyo Tottori Toyama Wakayama Yamagata Yamaguchi Yamanashi
Date
2021/09/22 1109 26 33 977 81 36 603 174 209 171 203 111 64 2804 5 48 59 57 89 29
2021/09/24 1117 26 35 985 81 36 605 174 209 171 204 111 64 2838 5 48 59 57 89 29
2021/09/25 1122 26 35 986 81 36 607 174 209 171 204 111 64 2853 5 49 59 57 89 29
2021/09/26 1126 26 35 988 81 36 609 174 210 171 204 113 64 2861 5 49 59 57 89 29
2021/09/27 1127 26 35 990 81 36 609 174 210 171 204 113 64 2872 5 50 59 57 89 29

5 rows × 47 columns

東京の9月24日の死者数は15人なので、このデータは正確ではない可能性があります。そしてデータをよく見ると、9月23日分のデータがありません。とりあえず確認してみます。

df[df['Date']=="2021/09/23"]
ID Country CountryCode Province City CityCode Lat Lon Confirmed Deaths Recovered Active Date

9月23日のデータが何故か存在しません。とりあえず厚労省のサイトからダウンロードしたデータと比べてみます。

df3 = pd.read_csv("https://covid19.mhlw.go.jp/public/opendata/deaths_cumulative_daily.csv")
df3['Date'] = pd.to_datetime(df3['Date'])
df3['Date'] = df3["Date"].dt.strftime('%Y/%m/%d')
df3[df3['Prefecture']=='Tokyo'].tail(5)
Date Prefecture Deaths(Cumulative)
24109 2021/09/23 Tokyo 2838
24157 2021/09/24 Tokyo 2853
24205 2021/09/25 Tokyo 2861
24253 2021/09/26 Tokyo 2872
24301 2021/09/27 Tokyo 2883

9月23日のデータがあります。こっちが当然のことながら正確なデータです。このデータにピボットテーブルを適用します。

pvt1 = df3.pivot_table(index=['Date'], columns=[ 'Prefecture'], values = 'Deaths(Cumulative)')
pvt1.tail(5)
Prefecture ALL Aichi Akita Aomori Chiba Ehime Fukui Fukuoka Fukushima Gifu Shizuoka Tochigi Tokushima Tokyo Tottori Toyama Wakayama Yamagata Yamaguchi Yamanashi
Date
2021/09/23 17368 1113 26 35 983 81 36 602 174 209 204 111 64 2838 5 48 59 56 89 29
2021/09/24 17414 1117 26 35 985 81 36 604 174 209 204 111 64 2853 5 49 59 56 89 29
2021/09/25 17446 1122 26 35 986 81 36 606 174 210 204 113 64 2861 5 49 59 56 89 29
2021/09/26 17475 1126 26 35 988 81 36 607 174 210 204 113 64 2872 5 50 59 56 89 29
2021/09/27 17504 1127 26 35 990 81 36 607 174 211 204 113 64 2883 5 50 60 56 89 29

5 rows × 48 columns

死者数が累計なので、日毎に変換します。

pvt2 = pvt1.diff()
pvt2.tail(5)
Prefecture ALL Aichi Akita Aomori Chiba Ehime Fukui Fukuoka Fukushima Gifu Shizuoka Tochigi Tokushima Tokyo Tottori Toyama Wakayama Yamagata Yamaguchi Yamanashi
Date
2021/09/23 56.0 4.0 0.0 1.0 6.0 0.0 0.0 1.0 0.0 0.0 1.0 0.0 0.0 18.0 0.0 0.0 0.0 0.0 0.0 0.0
2021/09/24 46.0 4.0 0.0 0.0 2.0 0.0 0.0 2.0 0.0 0.0 0.0 0.0 0.0 15.0 0.0 1.0 0.0 0.0 0.0 0.0
2021/09/25 32.0 5.0 0.0 0.0 1.0 0.0 0.0 2.0 0.0 1.0 0.0 2.0 0.0 8.0 0.0 0.0 0.0 0.0 0.0 0.0
2021/09/26 29.0 4.0 0.0 0.0 2.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 11.0 0.0 1.0 0.0 0.0 0.0 0.0
2021/09/27 29.0 1.0 0.0 0.0 2.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 11.0 0.0 0.0 1.0 0.0 0.0 0.0

5 rows × 48 columns

7月1日からの首都圏+大阪府の死者数の推移を見てみます。

df4 = pvt2.loc['2021/07/01':'2021/09/27']
df4 = df4[['ALL', 'Chiba', 'Tokyo', 'Saitama', 'Kanagawa', 'Ibaraki', 'Osaka']]
df4.head(2)
Prefecture ALL Chiba Tokyo Saitama Kanagawa Ibaraki Osaka
Date
2021/07/01 21.0 0.0 2.0 1.0 1.0 1.0 6.0
2021/07/02 22.0 2.0 2.0 0.0 2.0 0.0 3.0
from matplotlib.pyplot import *
from matplotlib.font_manager import FontProperties
from matplotlib import rcParams
import matplotlib.dates as dates
style.use('ggplot')

fp = FontProperties(fname='/usr/share/fonts/opentype/ipaexfont-gothic/ipaexg.ttf', size=54)
rcParams['font.family'] = fp.get_name()
rcParams["font.size"] = "20"
fig, ax = subplots(figsize=(24,14))
df4.plot(ax = ax)
xticks(range(0,len(df4.index),2),df4.index[::2],rotation=90);
yticks(np.arange(0, 101, 5),
       ['{}人'.format(int(x)) for x in np.arange(0, 101, 5)]);
ylabel('死者数', fontsize=30,fontweight='heavy')
fig.tight_layout()
margins(x=0)

データが正しいのかどうかは別にして、ごちゃごちゃして見難いことだけは確かです。次にgroupbyを使って都道府県別死者数を多い順に並べます。

スポンサーリンク

groupbyを使ったデータ操作

取り敢えず、今年に入ってからの死者数を算出します。基本としては、2020年12月31日の累積死者数から最新の累積死者数を引くことで、2021年の総死者数を求めることができます。

df5 = df3[df3['Date'] >'2020/12/30']
df5.set_index('Date', inplace=True)
df5.head(2)
Prefecture Deaths(Cumulative)
Date
2020/12/31 ALL 3459
2020/12/31 Hokkaido 453

グループ分けした各都道府県の最終行(2021/09/27)の数値から先頭行(2020/12/31)の数値を引けば2021年の死者数を求めることができるのですが、簡単な方法として、最大値(2021/09/27)から最小値(2020/12/31)を引くことでも今年の死者数を求めることができます。

df6 = df5.groupby('Prefecture')['Deaths(Cumulative)'].agg(['max','min'])
df6['diff'] = df6['max']-df6['min']
df6.head(5)
max min diff
Prefecture
ALL 17504 3459 14045
Aichi 1127 209 918
Akita 26 1 25
Aomori 35 8 27
Chiba 990 117 873

np.ptpを使うともっと簡潔になります。

df6 = df5.groupby('Prefecture')['Deaths(Cumulative)'].agg(np.ptp)
df6.head(5)
Prefecture
ALL       14045
Aichi       918
Akita        25
Aomori       27
Chiba       873
Name: Deaths(Cumulative), dtype: int64

死者数の多い都道府県トップ20を抽出してグラフにします。

style.use('ggplot')

rcParams["font.size"] = "18"
fp = FontProperties(fname='/usr/share/fonts/opentype/ipaexfont-gothic/ipaexg.ttf', size=54)
rcParams['font.family'] = fp.get_name()
rcParams["font.size"] = "25"
fig, ax = subplots(figsize=(20,15))
df6.sort_values(ascending=False, inplace=False)[1::].head(20).plot(kind='barh',width=.8,color='b')
rc('xtick', labelsize=30)
rc('ytick', labelsize=30)
xticks(np.arange(0,2601,200))
ax.legend(["2021年死者数"],loc='upper right', prop={'size': 26})
for i in ax.patches:
    ax.text(i.get_width()+12.5,i.get_y()+.28,\
      '{:,}人'.format(int(round((i.get_width()), 2))), fontsize=20, color='k');
#ax.invert_yaxis();

何となく正確なデータのような気もします。何れにしても、2021年の死者数に関しては、大阪・東京が断トツ多いのは納得がいくからです。結論として、Pandasのgroupbyとpivot tableを使い分けることで、目的のデータ処理が簡単にできることが分かったかと思います。

スポンサーリンク
スポンサーリンク