matplotlib な twinx の二つの用途

これの中でこっそりやってたヤツなんだけれど、独立して書いといたほうがいいかなと思って。

要領が悪い、というんかなぁ、あるいは勘が鈍いというべきか、「答えを見つけているのに気付いてない」ということを、ほんと、頻繁にやっている。

見出しにした「twinx」の、まぁ「普通」というか、主たる用途というのは、おそらくこれ:

mpl_example_twinx1.py
  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)

tko_prefs_osaka_1
すなわち、「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 のフォーマッタを変えることの二点のみ。後者はワタシは気付いてたし、おそらく前にその「正解」を見つけたときもそこは理解してて、前者の意図を読み取りそこねたみたいなんだよね。こういうことね:

mpl_example_twinx2.py
  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)

tko_prefs_osaka_2
毎度毎度の調べごとのたびにちゃんと「アハ!」っとけば、もちっと効率よくコトを進められるんだがなぁ、と後悔しても後のカーニバル。まぁそんなもんだ、とは思うけれど、もちっと集中力があれば、もしくは集中しておけば、と。


念の為にもう一つ。「左右に別表現」が採用できなくなる日もきっと来る。つまり、ひとつ目の用途の方「も」使いたくなってしまったら、アウト。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     # ...(省略)...

tko_prefs_osaka_3



Related Posts