これの中でこっそりやってたヤツなんだけれど、独立して書いといたほうがいいかなと思って。
要領が悪い、というんかなぁ、あるいは勘が鈍いというべきか、「答えを見つけているのに気付いてない」ということを、ほんと、頻繁にやっている。
見出しにした「twinx」の、まぁ「普通」というか、主たる用途というのは、おそらくこれ:
1 # -*- coding: utf-8 -*-
2 import csv
3 import datetime
4 import matplotlib.pyplot as plt
5 import matplotlib.font_manager
6
7
8
9 # 「東洋経済オンライン「新型コロナウイルス 国内感染の状況」」より抽出・加工(大阪)
10 _tkoprefsdata = """\
11 date,testedPositive,hospitalized,serious
12 2020-12-26,299,3502,161
13 2020-12-28,150,3458,158
14 2020-12-30,307,3433,159
15 2021-01-01,262,3671,165
16 2021-01-03,253,3834,169
17 2021-01-05,394,4167,161
18 2021-01-07,607,4467,168
19 2021-01-09,647,5190,168
20 2021-01-11,480,5667,169
21 2021-01-13,536,6029,172
22 2021-01-15,568,6417,187
23 2021-01-17,464,6364,186
24 2021-01-19,525,6355,179
25 2021-01-21,501,6131,174
26 2021-01-23,525,6137,174
27 2021-01-25,273,5796,179
28 2021-01-27,357,5632,182
29 2021-01-29,346,5309,174
30 2021-01-31,214,4927,185
31 2021-02-02,211,4456,172
32 2021-02-04,207,3786,166
33 2021-02-06,188,3464,147
34 2021-02-08,119,3298,153
35 2021-02-10,127,2934,143
36 2021-02-12,89,2289,144
37 2021-02-14,98,1934,140
38 2021-02-16,98,1689,133
39 2021-02-18,89,1475,110
40 2021-02-20,94,1401,105
41 2021-02-22,98,1358,102
42 2021-02-24,62,1201,98
43 2021-02-26,77,1088,92
44 2021-02-28,54,1051,90
45 2021-03-02,81,1023,83
46 2021-03-04,81,981,79
47 2021-03-06,82,923,75
48 2021-03-08,38,935,74
49 2021-03-10,84,913,62
50 2021-03-12,111,956,61
51 2021-03-14,92,1000,60
52 2021-03-16,86,1017,61
53 2021-03-18,141,1148,54
54 2021-03-20,153,1271,55
55 2021-03-22,79,1265,61
56 2021-03-24,262,1491,61
57 2021-03-26,300,1854,63
58 2021-03-28,323,2323,71
59 2021-03-30,432,2748,90
60 2021-04-01,616,3630,96
61 2021-04-03,666,4561,124
62 2021-04-05,341,5129,143
63 2021-04-07,878,6185,158
64 2021-04-09,883,7408,174
65 2021-04-11,760,8346,203
66 2021-04-13,1351,9522,233
67 2021-04-15,1208,10940,261
68 2021-04-17,1161,12384,281"""
69
70 def _vis(dat):
71 T = [datetime.date(*(list(map(int, row[0].split("-"))))) for row in dat]
72 Y1 = [int(row[1]) for row in dat] # testedPositive
73 Y2 = [int(row[2]) for row in dat] # hospitalized
74 Y3 = [int(row[3]) for row in dat] # serious
75
76 fontprop = matplotlib.font_manager.FontProperties(
77 fname="c:/Windows/Fonts/meiryo.ttc")
78 fig, ax1 = plt.subplots(tight_layout=True)
79 fig.set_size_inches(16.53, 11.69)
80 ax1.set_xlabel(
81 "「東洋経済オンライン「新型コロナウイルス 国内感染の状況」」を加工",
82 fontproperties=fontprop)
83 # -----------------------------------------------------------
84 # twinx の用途の、おそらくメジャーなほう。異なるデータを x を共有して
85 # プロットする、というやつ。
86 ax2 = ax1.twinx()
87 ax1.plot(T, Y1, color="tab:blue")
88 ax1.set_ylabel("testedPositive", fontproperties=fontprop, color="tab:blue")
89 ax2.plot(T, Y2, color="tab:red")
90 ax2.set_ylabel("hospitalized", fontproperties=fontprop, color="tab:red")
91 ax1.grid(True)
92 # -----------------------------------------------------------
93
94 fig.savefig("tko_prefs_osaka_1.png")
95 #plt.show()
96 plt.close(fig)
97
98
99 if __name__ == '__main__':
100 reader = csv.reader(_tkoprefsdata.strip().split("\n"))
101 next(reader)
102 tkoprefs = list(reader)
103 _vis(tkoprefs)
すなわち、「X 軸を共有する、まったく別のデータを重ねてプロットする」。説明が足りないと誤解を受けやすいプレゼンテーションにはなりがちかとは思うけれど、ただ、例にしたやつだとtestedPositive の波形と hospitalized の波形の関係、のようなものを視覚的にわかりやすくするのに役立つ。「重傷者数の波は新規感染者数の波から二週間くらい遅れている」…のかどうか、とかね。
この「標準的な使い方」に対して、そうでないが重要な使い方、というのがあって。「同じデータの別の言い方」を Y軸で表現する、という発想で、真っ先に思いつく例としては、気温のグラフを「セルシウスとファーレンハイトの両方で表現する」。データは無論単一で、まさに「言い方が違うだけ」てわけだ。そしてワタシはおそらく最低でも一年くらい前に同じことをしたくなり、そしてこういう検索でこれに辿り着いてる。
でもね。おかしいんだよなぁ。その正解が書かれてる stackoverflow のそのページは、かなり鮮明に記憶にある、のだが、「素晴らしい、そうすればいいのか」と思った記憶が皆無だし、実践している形跡もない。「アハ体験」てやつね、これがないものというのはすなわち「身につかない」の証明。結果としては「ワタシはこのやり方を知らなかった」というに等しい状態だった。まぁほんと頻繁に起こる経験だけど、無論これは「とても悔しい」。
というわけで今度こその「アハ!」を記録しておかんとなぁ、と思った次第。
まず「アハ!」でない、不愉快な「正解」から:
1 ax2 = ax1.twinx()
2 ax1.plot(T, Y1, color="tab:blue")
3 ax1.set_ylabel("testedPositive", fontproperties=fontprop)
4 ax2.plot(T, [y * 1.5 for y in Y1], color="tab:blue")
5 ax2.set_ylabel("testedPositive (東京換算)", fontproperties=fontprop)
6 ax1.grid(True)
何が不愉快なのかはわかる、よね? 一つのデータを二回プロットしたい、のではないのだ。
「愉快な正解」のポイントは非常にシンプルで、「リミットを ax1, ax2 で共有する」ことと、tick のフォーマッタを変えることの二点のみ。後者はワタシは気付いてたし、おそらく前にその「正解」を見つけたときもそこは理解してて、前者の意図を読み取りそこねたみたいなんだよね。こういうことね:
1 # -*- coding: utf-8 -*-
2 import csv
3 import datetime
4 import matplotlib.pyplot as plt
5 import matplotlib.font_manager
6
7
8
9 # 「東洋経済オンライン「新型コロナウイルス 国内感染の状況」」より抽出・加工(大阪)
10 _tkoprefsdata = """\
11 date,testedPositive,hospitalized,serious
12 2020-12-26,299,3502,161
13 2020-12-28,150,3458,158
14 2020-12-30,307,3433,159
15 2021-01-01,262,3671,165
16 2021-01-03,253,3834,169
17 2021-01-05,394,4167,161
18 2021-01-07,607,4467,168
19 2021-01-09,647,5190,168
20 2021-01-11,480,5667,169
21 2021-01-13,536,6029,172
22 2021-01-15,568,6417,187
23 2021-01-17,464,6364,186
24 2021-01-19,525,6355,179
25 2021-01-21,501,6131,174
26 2021-01-23,525,6137,174
27 2021-01-25,273,5796,179
28 2021-01-27,357,5632,182
29 2021-01-29,346,5309,174
30 2021-01-31,214,4927,185
31 2021-02-02,211,4456,172
32 2021-02-04,207,3786,166
33 2021-02-06,188,3464,147
34 2021-02-08,119,3298,153
35 2021-02-10,127,2934,143
36 2021-02-12,89,2289,144
37 2021-02-14,98,1934,140
38 2021-02-16,98,1689,133
39 2021-02-18,89,1475,110
40 2021-02-20,94,1401,105
41 2021-02-22,98,1358,102
42 2021-02-24,62,1201,98
43 2021-02-26,77,1088,92
44 2021-02-28,54,1051,90
45 2021-03-02,81,1023,83
46 2021-03-04,81,981,79
47 2021-03-06,82,923,75
48 2021-03-08,38,935,74
49 2021-03-10,84,913,62
50 2021-03-12,111,956,61
51 2021-03-14,92,1000,60
52 2021-03-16,86,1017,61
53 2021-03-18,141,1148,54
54 2021-03-20,153,1271,55
55 2021-03-22,79,1265,61
56 2021-03-24,262,1491,61
57 2021-03-26,300,1854,63
58 2021-03-28,323,2323,71
59 2021-03-30,432,2748,90
60 2021-04-01,616,3630,96
61 2021-04-03,666,4561,124
62 2021-04-05,341,5129,143
63 2021-04-07,878,6185,158
64 2021-04-09,883,7408,174
65 2021-04-11,760,8346,203
66 2021-04-13,1351,9522,233
67 2021-04-15,1208,10940,261
68 2021-04-17,1161,12384,281"""
69
70 def _vis(dat):
71 T = [datetime.date(*(list(map(int, row[0].split("-"))))) for row in dat]
72 Y1 = [int(row[1]) for row in dat] # testedPositive
73 Y2 = [int(row[2]) for row in dat] # hospitalized
74 Y3 = [int(row[3]) for row in dat] # serious
75
76 fontprop = matplotlib.font_manager.FontProperties(
77 fname="c:/Windows/Fonts/meiryo.ttc")
78 fig, ax1 = plt.subplots(tight_layout=True)
79 fig.set_size_inches(16.53, 11.69)
80 ax1.set_xlabel(
81 "「東洋経済オンライン「新型コロナウイルス 国内感染の状況」」を加工",
82 fontproperties=fontprop)
83 # -----------------------------------------------------------
84 # twinx の用途の、もうひとつ。ひとつのデータについて、値の表現を二つ使う、
85 # というやつ。
86 ax2 = ax1.twinx()
87 ax1.plot(T, Y1, color="tab:blue")
88 ax1.set_ylabel("testedPositive", fontproperties=fontprop)
89 ax2.set_ylim(ax1.get_ylim())
90 # 人口比が、東京:大阪=1.58:1 なので、「東京換算」として見せたい、として。
91 ax2.yaxis.set_major_formatter(lambda x, pos: "{:.1f}".format(x * 1.58))
92 ax2.set_ylabel("testedPositive (東京換算)", fontproperties=fontprop)
93 ax1.grid(True)
94 # -----------------------------------------------------------
95
96 fig.savefig("tko_prefs_osaka_2.png")
97 #plt.show()
98 plt.close(fig)
99
100
101 if __name__ == '__main__':
102 reader = csv.reader(_tkoprefsdata.strip().split("\n"))
103 next(reader)
104 tkoprefs = list(reader)
105 _vis(tkoprefs)
毎度毎度の調べごとのたびにちゃんと「アハ!」っとけば、もちっと効率よくコトを進められるんだがなぁ、と後悔しても後のカーニバル。まぁそんなもんだ、とは思うけれど、もちっと集中力があれば、もしくは集中しておけば、と。
念の為にもう一つ。「左右に別表現」が採用できなくなる日もきっと来る。つまり、ひとつ目の用途の方「も」使いたくなってしまったら、アウト。COVID-19 のデータの例で言えば、新規感染者数グラフと重傷者数グラフを重ねてプロットする、というのを同時にやりたいなら、もう「twinxで左右に別目盛り」とはいかない。ので、まぁこういうことをするんだろうね:
1 # ...(省略)...
2
3 # -----------------------------------------------------------
4 # ひとつのデータについて、値の表現を二つ使う、というやつの、twinx を
5 # 使わない例。
6 ax1.plot(T, Y1, color="tab:blue")
7 ax1.set_ylabel("testedPositive", fontproperties=fontprop)
8 # 人口比が、東京:大阪=1.58:1 なので、「東京換算」として見せたい、として。
9 ax1.yaxis.set_major_formatter(lambda x, pos: "{}\n({:.1f})".format(x, x * 1.58))
10 ax1.set_ylabel("testedPositive (カッコ内は東京換算)", fontproperties=fontprop)
11 ax1.grid(True)
12 # -----------------------------------------------------------
13
14 # ...(省略)...