[matplotlib] 続・「ヒストグラムの推移」を延々並べるわけにもいかんので

これの少し続き。

「box プロットしたかった」と書いたが、要は前回描いたような「全部」じゃなくて統計的な「要約」が欲しい、というわけで、なので近い用が足せるなら box プロットそのものにこだわる必要はなかったりする。

一応 IQR を直接計算出来たりするけれど、やってみたら(やるまでもなくなのかもな)目的のと違うんだよね、これが。具体的には、「1~10の値を取り得るスコアの分布」というデータを持っている場合に IQR を求めようとしても、元が整数しかないなら整数しか返らない。(これは median も同じ。そらそーだ、て話だが、こういう基礎を結構忘れている、ワタシは。)

で、要するにワタシは「分布の形状の要約」が欲しいわけだから、よく考えたら「μ ± σ」の推移をプロットしちゃえばいいんじゃないかしら、と。これも基礎を忘れててすごく個人的に困ったが、「標準偏差の値」がσでいいんだっけか? とするとこのσは直感よりデカいんだけど…:

前回のおさらいをしておくと、元のデータはこんな具合:

1 '2017-12-20 17:18:01', [45, 30, 60, 167, 278, 510, 1236, 2110, 1957, 1615]
2 '2017-12-20 20:09:01', [46, 30, 60, 167, 278, 508, 1237, 2117, 1961, 1620]
3 '2017-12-21 02:35:34', [48, 30, 61, 169, 280, 511, 1237, 2130, 1964, 1629]
4 '2017-12-21 08:09:10', [48, 29, 61, 170, 278, 517, 1239, 2131, 1973, 1633]
5 ...(snip)...
6 '2017-12-30 10:58:17', [60, 39, 68, 191, 354, 735, 2227, 4926, 5559, 3782]
7 '2017-12-30 14:09:19', [60, 39, 68, 192, 354, 737, 2242, 4953, 5594, 3805]
8 '2017-12-30 20:09:12', [61, 38, 68, 193, 357, 748, 2252, 5008, 5666, 3844]

で、前回は contourf を使った:

 1 def build_score_dist_boxplot(dba, name):
 2     from datetime import datetime
 3     import numpy as np
 4     import matplotlib.pyplot as plt
 5 
 6     fig, ax = plt.subplots()
 7     x_f = []
 8     x_d = []
 9     z = []
10     first = None
11     for dtstr, dist in dba.query_score_dist_by_name(name):
12         dt = datetime.strptime(dtstr[:-3], "%Y-%m-%d %H:%M")
13         if first is None:
14             first = dt
15         x_f.append((dt - first).total_seconds())
16         x_d.append(dtstr[5:-3])
17         z.append([float(d) / max(dist) for d in dist])
18 
19     X, Y = np.meshgrid(x_f, list(range(1, 11)))
20     pc = ax.contourf(X, Y, np.array(z).T, 15, cmap=plt.get_cmap("rainbow"))
21     ax.set_xticks(())
22     fig.tight_layout()
23     fig.savefig(name + ".png")
24     plt.close(fig)

dba.query_score_dist_by_name が上に示したデータを返してくる。

で、さっきの絵は今度はこうした:

 1 def build_score_dist_boxplot2(dba, name):
 2     from datetime import datetime
 3     import numpy as np
 4     from scipy.stats import norm
 5     import matplotlib.pyplot as plt
 6     import itertools
 7 
 8     fig, ax = plt.subplots()
 9     x_f = []
10     y = {
11         "m1s": [],
12         "mean": [],
13         "p1s": []
14         }
15     first = None
16     for dtstr, dist in dba.query_score_dist_by_name(name):
17         dt = datetime.strptime(dtstr[:-3], "%Y-%m-%d %H:%M")
18         if first is None:
19             first = dt
20         x_f.append((dt - first).total_seconds())
21         a = list(itertools.chain.from_iterable([[i + 1] * d for i, d in enumerate(dist)]))
22         sigma = np.std(a)
23         mean = np.mean(a)
24         med = np.median(a)
25         y["m1s"].append(mean - sigma)
26         y["mean"].append(mean)
27         y["p1s"].append(mean + sigma)
28 
29     ax.plot(x_f, y["m1s"], 'b--')
30     ax.plot(x_f, y["mean"], 'r-')
31     ax.plot(x_f, y["p1s"], 'b--')
32     ax.set_xticks(())
33     ax.set_ylim((1, 10))
34     ax.grid(True)
35     fig.tight_layout()
36     fig.savefig(name + "_2.png")
37     plt.close(fig)

ただ、さっきのグラフ、やっぱし「要約しすぎ」なのよね、「詳細も要約も両方欲しいのだわぃ」という気分になっちゃうのよね。なので二つのグラフを混ぜちゃおうかと:

 1 def build_score_dist_boxplot(dba, name):
 2     from datetime import datetime
 3     import numpy as np
 4     import matplotlib.pyplot as plt
 5     import itertools
 6 
 7     fig, ax = plt.subplots()
 8     x_f = []
 9     x_d = []
10     z = []
11     #
12     y = {
13         "m1s": [],
14         "mean": [],
15         "p1s": []
16         }
17     #
18     first = None
19     for dtstr, dist in dba.query_score_dist_by_name(name):
20         #print("%r, %r" % (dtstr, dist))
21         dt = datetime.strptime(dtstr[:-3], "%Y-%m-%d %H:%M")
22         if first is None:
23             first = dt
24         x_f.append((dt - first).total_seconds())
25         x_d.append(dtstr[5:-3])
26         z.append([float(d) / max(dist) for d in dist])
27         #
28         a = list(itertools.chain.from_iterable([[i + 1] * d for i, d in enumerate(dist)]))
29         sigma = np.std(a)
30         mean = np.mean(a)
31         med = np.median(a)
32         y["m1s"].append(mean - sigma)
33         y["mean"].append(mean)
34         y["p1s"].append(mean + sigma)
35         #
36     X, Y = np.meshgrid(x_f, list(range(1, 11)))
37     pc = ax.contourf(X, Y, np.array(z).T, 15, cmap=plt.get_cmap("rainbow"))
38     ax.plot(x_f, y["m1s"], 'b--')
39     ax.plot(x_f, y["mean"], 'w-')
40     ax.plot(x_f, y["p1s"], 'b--')
41     #ax.set_xticklabels(x_d, rotation=90)
42     ax.set_xticks(())
43     fig.tight_layout()
44     fig.savefig(name + ".png")
45     plt.close(fig)

ということをして、3つのグラフを描いてみた:



悪くないかな。

ただ、今相手にしてるデータたちって、ほぼ全てが「7 を中心に分布」する傾向が強くて(つまりほとんどが上に偏る)、+σがどうしてもおかしな感じになるんだよねぇ。もう少し工夫が必要な気がしますわな。