ネ申エクセルについて¶
Excelで印刷物での見栄えを優先させて作成された表がしばしば見受けられます。 これらのExcelワークブックは、ネ申エクセル(神エクセル)と呼ばれたりします。 神エクセルは印刷物を作成すること(だけ)を念頭に置かれて作成されるため、 そこに含まれたデータを再利用する際に、余分な手間が必要となり、データの効率的な利用の障害となっています。
科研費申請書で有名になった方眼紙エクセルは極端な例ですが、 セルの結合を多用しているため、標準的な手法ではその中のデータを簡単にはプログラムに取り込めない ということはしばしばあります。
一例として、首相官邸ホームページに掲載されている都道府県別のコロナワクチン接種状況のデータを見て見ましょう。
都道府県別の実績 https://www.kantei.go.jp/jp/content/kenbetsu-vaccination_data2.xlsx
この表には、都道府県別に、一般接種および医療対象者の二つの接種対象毎に表が作成されています。 また、ワクチンの種別毎に1回目としての接種回数と2回目としての接種回数がデータにふくまれています。 一般接種と医療対象者等のデータはそれぞれ別ワークシートに収められており、 テーブルの形式もそれぞれで若干の違いがあります。 それらのテーブルでは、各列の項目名の行はセルの結合を組み合わせて作られており、 データを取り出す為には、worksheet毎にデータの処理を行う必要があります。
import pandas
from urllib.request import urlopen
import openpyxl
from io import StringIO,BytesIO
wb=openpyxl.load_workbook(
BytesIO(
urlopen("https://www.kantei.go.jp/jp/content/kenbetsu-vaccination_data2.xlsx").read()
))
df0=pandas.read_excel(
urlopen("https://www.kantei.go.jp/jp/content/kenbetsu-vaccination_data2.xlsx").read(),
header=2,sheet_name=0)
df0.columns, df0["都道府県名"]
(Index(['都道府県名', '接種回数', '内1回目', '内2回目', 'Unnamed: 4'], dtype='object'),
0 合計
1 01 北海道
2 02 青森県
3 03 岩手県
4 04 宮城県
5 05 秋田県
6 06 山形県
7 07 福島県
8 08 茨城県
9 09 栃木県
10 10 群馬県
11 11 埼玉県
12 12 千葉県
13 13 東京都
14 14 神奈川県
15 15 新潟県
16 16 富山県
17 17 石川県
18 18 福井県
19 19 山梨県
20 20 長野県
21 21 岐阜県
22 22 静岡県
23 23 愛知県
24 24 三重県
25 25 滋賀県
26 26 京都府
27 27 大阪府
28 28 兵庫県
29 29 奈良県
30 30 和歌山県
31 31 鳥取県
32 32 島根県
33 33 岡山県
34 34 広島県
35 35 山口県
36 36 徳島県
37 37 香川県
38 38 愛媛県
39 39 高知県
40 40 福岡県
41 41 佐賀県
42 42 長崎県
43 43 熊本県
44 44 大分県
45 45 宮崎県
46 46 鹿児島県
47 47 沖縄県
48 NaN
49 注:接種回数は一般接種(高齢者含む)と医療従事者等の合計。
50 注:一般接種(高齢者含む)はワクチン接種記録システム(VRS)への報告と、
51 医療従事者等はワクチン接種円滑化システム(V-SYS)への報告を、公表日で集計したもの。
52 注:公表日におけるデータの計上方法等の注釈については、以下を参照(https://www.k...
Name: 都道府県名, dtype: object)
このように、データ以外の情報 (ここでは"注”)もテーブル中にあり、プログラムでのデータ処理の例外扱いを増やしています。(注などは、テキストボックスや、フッターにいれることを検討して欲しいところです。ただ、フッターに入れることのできる文の長さには制限がある。~~テキストボックスはテーブルの長さが変わったときに位置が追随しないという問題はあります。~~ テキストボックスはプロパティの”セルに合わせて移動”を選択することで、セルの追加などに合わせてテキストボックスの位置が調整されます。)
Excelの「データの分析機能」でも注などをテーブルの中に埋め込んだ場合、これらも対象のデータとして取り込もうとしているようです。
openpyxlを用いて、ワークシートを読み込んだ場合、プログラム側でテーブルの構造を意識して、 明示的にセル位置を指定する必要があります。
print(wb.worksheets, end="\n\n")
for i in range(1,5):
[print(c.value, end=", ") for c in wb.worksheets[0][f"{i}"]]
print()
[<Worksheet "総接種回数">, <Worksheet "一般接種">, <Worksheet "医療従事者等">]
これまでのワクチン総接種回数(都道府県別), None, None, None, None,
None, None, None, (8月23日公表時点), None,
都道府県名, 接種回数, 内1回目, 内2回目, None,
合計, 112171725, 62773195, 49398530, None,
余分なデータがあることで、どんな影響がでるか、データをグラフ化して見て見ましょう。
import matplotlib
font = {'family' : "Hiragino Mincho ProN"}
matplotlib.rc('font', **font)
df0.plot.bar(x="都道府県名",y='接種回数',figsize=(16,9))
<AxesSubplot:xlabel='都道府県名'>
データの正規化¶
接種データは本来、各接種記録毎に、'接種場所','接種日時', '接種者の分類:一般、高齢者、医療従事者等','ワクチンの種別','ワクチン接種は1回目か2回目か'といった情報が含まれていると想像される。このファイルには、この生な接種情報を接種日時については集約したあとのデータを集積し、さらに’都道府県毎’にデータを集約していったものとみることができます。 このことは、このエクセルファイルの項目名でのセル結合の様子をみることでも裏付けられます。 このような視点で、できるだけ生データに近い形で、データを再整理したものが次の"接種回数DB"です。
df=pandas.read_excel("kenbetsu-vaccination_data2.xlsx",
sheet_name="接種回数DB",
index_col=0
)
#index_col=(0,1,2,3,4,))
#df=df[['接種地域', 'ワクチン供給者', '接種対象', '接種回','接種済数', 'ワクチン累積供給量']]
#df=df[['接種済数', 'ワクチン累積供給量']]
df.columns, df.index.names
(Index(['接種地域', 'ワクチン供給者', '接種対象', '接種回', '接種済数', 'ワクチン累積供給量'], dtype='object'),
FrozenList(['id']))
このように、データを再構成することで、簡単にデータをプログラムから読み込むことができます。 また、さまざま集約も標準の機能をつかうことで、簡単に結果をもとめることができます。
df.groupby(["ワクチン供給者"]).sum()
接種済数 | ワクチン累積供給量 | |
---|---|---|
ワクチン供給者 | ||
ファイザー社 | 102015703 | 124131930.0 |
武田/モデルナ社 | 10156022 | 22232900.0 |
df.groupby(["ワクチン供給者"]).sum().sum()
id 39903.0
接種済数 112171725.0
ワクチン累積供給量 146364830.0
dtype: float64
df.groupby(["接種地域",'接種回','接種対象']).sum().sort_index(),df.groupby(["接種地域"]).sum().sum()
( id 接種済数 ワクチン累積供給量
接種地域 接種回 接種対象
三重県 内1回目 一般接種 95 785109 1980355.0
医療従事者等 118 89383 0.0
内2回目 一般接種 377 634022 0.0
医療従事者等 259 81345 0.0
京都府 内1回目 一般接種 99 1096642 2957855.0
... ... ... ...
鳥取県 内2回目 医療従事者等 266 31073 0.0
鹿児島県 内1回目 一般接種 139 727002 1861520.0
医療従事者等 140 109133 0.0
内2回目 一般接種 421 586527 0.0
医療従事者等 281 95738 0.0
[188 rows x 3 columns],
id 39903.0
接種済数 112171725.0
ワクチン累積供給量 146364830.0
dtype: float64)
df.groupby(["接種地域"]).sum().sort_index()
id | 接種済数 | ワクチン累積供給量 | |
---|---|---|---|
接種地域 | |||
三重県 | 849 | 1589859 | 1980355.0 |
京都府 | 861 | 2238427 | 2957855.0 |
佐賀県 | 951 | 845812 | 985815.0 |
兵庫県 | 873 | 4957700 | 6100055.0 |
北海道 | 711 | 4628368 | 5979910.0 |
千葉県 | 777 | 5200353 | 6107845.0 |
和歌山県 | 885 | 1021734 | 1196545.0 |
埼玉県 | 771 | 5768529 | 6801570.0 |
大分県 | 969 | 1106544 | 1270095.0 |
大阪府 | 867 | 7458806 | 10757385.0 |
奈良県 | 879 | 1266858 | 1577330.0 |
宮城県 | 729 | 2039998 | 2579595.0 |
宮崎県 | 975 | 993101 | 1155855.0 |
富山県 | 801 | 952505 | 1135070.0 |
山口県 | 915 | 1545104 | 1836535.0 |
山形県 | 741 | 1125335 | 1365055.0 |
山梨県 | 819 | 692941 | 951420.0 |
岐阜県 | 831 | 1923575 | 2306565.0 |
岡山県 | 903 | 1869401 | 2246580.0 |
岩手県 | 723 | 1083575 | 1353435.0 |
島根県 | 897 | 658635 | 745740.0 |
広島県 | 909 | 2550235 | 3218420.0 |
徳島県 | 921 | 732888 | 917095.0 |
愛媛県 | 933 | 1250690 | 1556050.0 |
愛知県 | 843 | 6200562 | 8725620.0 |
新潟県 | 795 | 2146330 | 2686645.0 |
東京都 | 783 | 12134011 | 20893610.0 |
栃木県 | 759 | 1502088 | 1966330.0 |
沖縄県 | 987 | 1119842 | 1406405.0 |
滋賀県 | 855 | 1230505 | 1575270.0 |
熊本県 | 963 | 1878851 | 2204255.0 |
石川県 | 807 | 1108983 | 1349610.0 |
神奈川県 | 789 | 7424780 | 9002475.0 |
福井県 | 813 | 773434 | 939520.0 |
福岡県 | 945 | 4647952 | 5684120.0 |
福島県 | 747 | 1786718 | 2106990.0 |
秋田県 | 735 | 980253 | 1106205.0 |
群馬県 | 765 | 2008977 | 2599545.0 |
茨城県 | 753 | 2569535 | 3273575.0 |
長崎県 | 957 | 1396867 | 1654190.0 |
長野県 | 825 | 1867238 | 2292470.0 |
青森県 | 717 | 1198827 | 1428815.0 |
静岡県 | 837 | 3060754 | 3901980.0 |
香川県 | 927 | 841707 | 1097190.0 |
高知県 | 939 | 719429 | 874865.0 |
鳥取県 | 891 | 554709 | 651450.0 |
鹿児島県 | 981 | 1518400 | 1861520.0 |
df.groupby(["接種地域"]).sum().sum()
id 39903.0
接種済数 112171725.0
ワクチン累積供給量 146364830.0
dtype: float64
結論¶
ここで試みたことの結論は、
データ項目名のセル結合は、その表が(RDB的な意味で)正規化されていないことの具体的な表れであること。
その構造を理解することで、データの正規化をおこなうことで、プログラム処理が容易になる。
とまとめられるでしょう。また、
excelファイル作成時には、(正規化された)データだけのテーブルを含むworksheetと,ユーザーとの入出力のためのworksheetを分けたデザインを使う。
ことが望ましいでしょう。
テーブルの「正規化」を、もう少しわかり易い言葉遣いで説明できるように工夫すべきなのかもしれません。
ヘッダ/フッターやテキストボックスを利用することで、データに直接関係のないデータはテーブル(セル)に書き込まないようにできるということの啓蒙も必要でしょう。
df[(df["接種地域"]=="北海道")&(df['ワクチン供給者'] =="ファイザー社")].groupby("接種回").sum()
id | 接種済数 | ワクチン累積供給量 | |
---|---|---|---|
接種回 | |||
内1回目 | 96 | 2320702 | 5198310.0 |
内2回目 | 378 | 1956696 | 0.0 |
df[(df["接種地域"] == "北海道")&(df['ワクチン供給者'] == "ファイザー社")]
接種地域 | ワクチン供給者 | 接種対象 | 接種回 | 接種済数 | ワクチン累積供給量 | |
---|---|---|---|---|---|---|
id | ||||||
1 | 北海道 | ファイザー社 | 一般接種 | 内1回目 | 1991581 | 5198310.0 |
95 | 北海道 | ファイザー社 | 医療従事者等 | 内1回目 | 329121 | NaN |
142 | 北海道 | ファイザー社 | 一般接種 | 内2回目 | 1663807 | NaN |
236 | 北海道 | ファイザー社 | 医療従事者等 | 内2回目 | 292889 | NaN |
df[(df["接種地域"].isin(["北海道","青森県"]))&(df['ワクチン供給者'] == "ファイザー社")]
接種地域 | ワクチン供給者 | 接種対象 | 接種回 | 接種済数 | ワクチン累積供給量 | |
---|---|---|---|---|---|---|
id | ||||||
1 | 北海道 | ファイザー社 | 一般接種 | 内1回目 | 1991581 | 5198310.0 |
2 | 青森県 | ファイザー社 | 一般接種 | 内1回目 | 583289 | 1383915.0 |
95 | 北海道 | ファイザー社 | 医療従事者等 | 内1回目 | 329121 | NaN |
96 | 青森県 | ファイザー社 | 医療従事者等 | 内1回目 | 67672 | NaN |
142 | 北海道 | ファイザー社 | 一般接種 | 内2回目 | 1663807 | NaN |
143 | 青森県 | ファイザー社 | 一般接種 | 内2回目 | 471657 | NaN |
236 | 北海道 | ファイザー社 | 医療従事者等 | 内2回目 | 292889 | NaN |
237 | 青森県 | ファイザー社 | 医療従事者等 | 内2回目 | 59963 | NaN |
ef=pandas.ExcelFile("kenbetsu-vaccination_data2.xlsx")
df1=ef.parse(sheet_name="接種回数DB",
index_col=(0,1,2,3,4,))
# equivalent to:
#df1=pandas.read_excel("kenbetsu-vaccination_data2.xlsx",
# sheet_name="接種回数DB",
# index_col=(0,1,2,3,4,))
df1.columns
Index(['接種済数', 'ワクチン累積供給量'], dtype='object')
df1.index
MultiIndex([( 1, '北海道', 'ファイザー社', '一般接種', '内1回目'),
( 2, '青森県', 'ファイザー社', '一般接種', '内1回目'),
( 3, '岩手県', 'ファイザー社', '一般接種', '内1回目'),
( 4, '宮城県', 'ファイザー社', '一般接種', '内1回目'),
( 5, '秋田県', 'ファイザー社', '一般接種', '内1回目'),
( 6, '山形県', 'ファイザー社', '一般接種', '内1回目'),
( 7, '福島県', 'ファイザー社', '一般接種', '内1回目'),
( 8, '茨城県', 'ファイザー社', '一般接種', '内1回目'),
( 9, '栃木県', 'ファイザー社', '一般接種', '内1回目'),
( 10, '群馬県', 'ファイザー社', '一般接種', '内1回目'),
...
(273, '愛媛県', 'ファイザー社', '医療従事者等', '内2回目'),
(274, '高知県', 'ファイザー社', '医療従事者等', '内2回目'),
(275, '福岡県', 'ファイザー社', '医療従事者等', '内2回目'),
(276, '佐賀県', 'ファイザー社', '医療従事者等', '内2回目'),
(277, '長崎県', 'ファイザー社', '医療従事者等', '内2回目'),
(278, '熊本県', 'ファイザー社', '医療従事者等', '内2回目'),
(279, '大分県', 'ファイザー社', '医療従事者等', '内2回目'),
(280, '宮崎県', 'ファイザー社', '医療従事者等', '内2回目'),
(281, '鹿児島県', 'ファイザー社', '医療従事者等', '内2回目'),
(282, '沖縄県', 'ファイザー社', '医療従事者等', '内2回目')],
names=['id', '接種地域', 'ワクチン供給者', '接種対象', '接種回'], length=282)
MultiIndexに対しては、isin
メソッドを使って条件を記述する。
df1[df1.index.isin(["青森県","東京都"], level="接種地域")
& df1.index.isin(["ファイザー社"], level="ワクチン供給者")].groupby(["接種地域","接種対象","接種回"]).sum()
接種済数 | ワクチン累積供給量 | |||
---|---|---|---|---|
接種地域 | 接種対象 | 接種回 | ||
東京都 | 一般接種 | 内1回目 | 5117917 | 13310310.0 |
内2回目 | 3877286 | 0.0 | ||
医療従事者等 | 内1回目 | 610484 | 0.0 | |
内2回目 | 545945 | 0.0 | ||
青森県 | 一般接種 | 内1回目 | 583289 | 1383915.0 |
内2回目 | 471657 | 0.0 | ||
医療従事者等 | 内1回目 | 67672 | 0.0 | |
内2回目 | 59963 | 0.0 |
lambda
式をつかえば、より柔軟に記述できるが、記述量、読みやすさの観点からは、「最後の武器 」と考えるのが良いでしょう。
df1[[(lambda x:(x[1] == "北海道")
&(x[2] == "ファイザー社"))(i)
for i in df1.index]
]
接種済数 | ワクチン累積供給量 | |||||
---|---|---|---|---|---|---|
id | 接種地域 | ワクチン供給者 | 接種対象 | 接種回 | ||
1 | 北海道 | ファイザー社 | 一般接種 | 内1回目 | 1991581 | 5198310.0 |
95 | 北海道 | ファイザー社 | 医療従事者等 | 内1回目 | 329121 | NaN |
142 | 北海道 | ファイザー社 | 一般接種 | 内2回目 | 1663807 | NaN |
236 | 北海道 | ファイザー社 | 医療従事者等 | 内2回目 | 292889 | NaN |
df1[[(lambda x:(x[1] in ("北海道","青森県")) &
(x[2] == "ファイザー社"))(i) for i in df1.index]
].groupby(["接種地域","接種対象","接種回"]).sum()
接種済数 | ワクチン累積供給量 | |||
---|---|---|---|---|
接種地域 | 接種対象 | 接種回 | ||
北海道 | 一般接種 | 内1回目 | 1991581 | 5198310.0 |
内2回目 | 1663807 | 0.0 | ||
医療従事者等 | 内1回目 | 329121 | 0.0 | |
内2回目 | 292889 | 0.0 | ||
青森県 | 一般接種 | 内1回目 | 583289 | 1383915.0 |
内2回目 | 471657 | 0.0 | ||
医療従事者等 | 内1回目 | 67672 | 0.0 | |
内2回目 | 59963 | 0.0 |