読まなくてええよ、技術ネタは少し入ってるけどくだらない

matplotlib は使わないとすぐ忘れる、な備忘録も兼ねてたりして。だけれども所詮「宝石の国」ネタに過ぎない。

ここんとこのアニメ(というか宝石の国)熱と絡めた matplotlib ネタ。ほんとは web でも使える jquery 関連で遊んだ方が個人的にはうまみはあるんだけれど、慣れたもので手っ取り早くやりたかったもんで。

Windows で cron 的なナニかMyAnimeList のスナップショットを収集してみてる、と書いた。これを可視化してみたくなったのよ。というのも、「評価の上昇の具合が、宝石の国(とネト充のススメ)がなんか際立って凄まじい」から。どんだけ「際立ってるか」をわかりやすくみたかった。スクリプトはこんな:

 1 # -*- coding: utf-8 -*-
 2 import io
 3 import re
 4 from bs4 import BeautifulSoup
 5 
 6 
 7 def get_snapshot_tabledata(htmldoc):
 8     if not hasattr(htmldoc, "read"):
 9         htmldoc = io.open(htmldoc, encoding="utf-8").read()
10     htmldoc = re.sub(r'<a [^<>]+class="icon\-watch[^<>]+>[^<>]+</a>', "", htmldoc)
11     soup = BeautifulSoup(htmldoc, 'html.parser')
12     result = {}
13     for tr in soup.find_all("tr")[1:]:
14         raw = [td.get_text().strip() for td in tr.find_all("td")[:-2]]
15         res_row = []
16         for i, t in enumerate(raw):
17             if i == 0:
18                 res_row.append(int(t))
19             elif i == 1:
20                 spl = t.split("\n")
21                 res_row.append(spl[0].strip())
22                 res_row.append(int(re.sub(r"\s+members", "", spl[-1].strip()).replace(",", "")))
23             elif i == len(raw) - 1:
24                 res_row.append(float(t))
25             else:
26                 res_row.append(t)
27         result[res_row[1]] = {
28             "rank": res_row[0],
29             "members": res_row[2],
30             "score": res_row[3],
31             }
32     return result
33 
34 
35 def get_all_tabledata():
36     from glob import glob
37     from datetime import datetime
38 
39     result = {}
40     for fn in glob("*_topanime.*"):
41         dt = datetime(
42             int(fn[:4]), int(fn[4:6]), int(fn[6:8]),
43             int(fn[8:10]), int(fn[10:12]))
44         res = get_snapshot_tabledata(fn)
45         for k in res.keys():
46             if k not in result:
47                 result[k] = {}
48             result[k][dt] = res[k]
49     return result
50 
51 
52 def visualize(result):
53     import matplotlib
54     import matplotlib.cm as cm
55     import matplotlib.pyplot as plt
56 
57     fig, ax = plt.subplots()
58     fig.set_size_inches(11.69, 16.53)
59 
60     for i, name in enumerate(
61         [name for rank, name in sorted(
62                 [(result[name][sorted(result[name].keys())[-1]]["rank"], name) for name in result.keys()])]):
63 
64         X = sorted(result[name].keys())
65         Y = [result[name][x]["score"] for x in X]
66         if len(X) > 6 \
67                 and min([result[name][x]["rank"] for x in X]) < 20:
68 
69             p = ax.plot(X, Y, label=name)
70             idx = i % int((len(X) / 3 * 2))
71             x_pos, y_pos = X[idx], Y[idx]
72             ax.text(x_pos, y_pos, name, fontsize=8, color=p[0].get_color())
73 
74     ax.set_title("https://myanimelist.net/topanime.php?type=airing")
75     ax.set_xlabel("snapshot date")
76     ax.set_ylabel("score")
77 
78     plt.savefig(
79         "mal_airing.pdf",
80         bbox_inches="tight")
81 
82 
83 if __name__ == '__main__':
84     result = get_all_tabledata()
85     visualize(result)

glob("*_topanime.*") してるのが「Windows で cron 的なナニか」で取得してるページのスナップショットね。全部を出すとごちゃごちゃし過ぎて読みづらいので、一度でも 20 位以内に入ったものという絞込みをしてる。あんまし分析に適したデータ抽出してないんで、コードはもっさりはしてるけど、まぁそれはいい。

結果はこんなだ:


注意して欲しいのは、「score」だけを可視化している、という点。実際「評価者数」(members)も同時に見ないと本当の実情は必ずしも反映してない。つまり「大勢が低評価」してればほんとに評判が悪いことになるが、「少数が高評価」してるものがほんとに高評価なのかどうかは、詳細に検討しないと本当のところはわからない。今みたい宝石の国については、members は今時点で 44,388 人、これは「やや少ない」が激しく少ないわけではない。(魔法使いの嫁は 210,532 人。これは多い。)

もう一つ注意すべきは、「MAL を利用している人たち」がどういう人たちなのか、てこと。これはワタシにも良くわかんない。ただ、members の数はそこそこあるみたいなので、そんなに「あてにならん」ことでもないかとは思うけれど、「いわゆるオタク層」に偏っているのか、一般層に偏っているのかは、ワタシにはわかんない。(まぁ日本のアニメをすすんで観るんだから、少なくとも本人たちはオタクを自称してるんだとは思うけれど、海外の人が言う「オタク」って、日本のそれとは微妙に違うからね。むしろ日本人が使う「西洋かぶれ」に近いと思う。)

「評価の上昇の具合が、宝石の国(とネト充のススメ)がなんか際立って凄まじい」と言ったが、要するに最初の評価が異常に低かった、つーことだ。宝石の国は結局今20位以内に入ってるわけだから、ただ上がり幅が大きかったというだけではないわけな。100位が50位になるのだってスゲーだろうけど、「上がっても50位かよっ」てことではない、てこと。そして、現在宝石の国の上位にいる「新規もの」(二期以降ものでない、ということ)は、魔法使いの嫁、ピングー in ザ・シティ、いぬやしきの3つだけ。つまり新規だけでのランキングでは4位てことね。(ヴァイオレットエヴァーガーデンは「airing」言うておるのにこやつはまだ放映してない「期待値」。)

日本でのランキングとは結構違うだろうなぁ、とは思うんだけれど、どの程度違うのかはよくわかんない。宝石の国については、上昇の度合いはこちら(MAL)の方がなんかスゴそうだ。つまり、日本でのほうがまだ回が浅い頃ももうちっと評価されてた印象がある。(逆に言えば、とりたてて「評価が急上昇してる」て感じはしない。)

MAL でのこの傾向はあれよな、「入り口でつまづくかどうかで評価が丸っきり違う」ということを露骨にあらわしてる、つーことよの。入り口で絶対つまづきやすいと思うし、つまづいた人は絶対に後から再評価できるタイプでもない。観続けた人は軒並み好評価をつけ、初回で切るなどした人はすべからく極端な低評価を付けた、つーことね。(「単に面白くない」で 5 をつけるとして、それ以上に不満を抱けば 1 や 2 を付けるであろう。後者ね。)

なお、このマインドの傾向は実際日本でも同じように思える。海外ほど極端ではないようだけれど、入り口でダメだった人は「至上稀にみる駄作」というくらい攻撃してる人々も、決して少なくはない。実際そういう作品だとは思う。ワタシだって CG で「一つも気になるところがない」なんて思ってなくて、何回かこれはヤダ、と思った瞬間はあった。これに引っかかってたら、ワタシもダメだった可能性もないではない。(黒沢ともよの芝居がダメな人もいそうだと思う。これがダメなら絶対ダメだもんなぁ、これ。)

2週間後が最終回後一週間てとこなんだけど、このあたりがたぶんリアルタイムとしての評価確定、だろうね。今の上昇率のまま続くと、score は 8.12 くらいになりそうなのね。今宝石の国の上にいる Kekkai Sensen & Beyond ~ いぬやしきが上げどまりか下がってるんで、12位で終わることになるかもしんない。初回付近の評価 7 が「低過ぎかもなぁ」はいいとしても、ここまで上がるのは果たして妥当とみるか、過大とみるかは意見は分かれそうだね。まぁこのままずっと上がり続けるとは限らんけど。まだ明日の11話入れて 2話あるし。(ちなみに自分で MAL に入って評価付けるとしたら、8 か 9 を付けるかな。10 は付けない。)


宝石の国以外の評価についてもちょっとみておく。

魔法使いの嫁、のコンスタントな高評価はこれはなんなんだろうか。ワタシも結構好きだけれど、こうまで評価されてるのも今ひとつピンと来ない。だってこれ、ワタシには「今やってるものの中では二番目に良い」というだけのもので、多分来年の冬には絶対記憶からも消えてる。何をそんなにお気に入ったんだろうかねぇ?

キノの旅は「そうでもないなぁ」という気分が下降線にじんわり出てるよね。これはこんなもんだと思う。ワタシは最初からゆる~く観てるんで、ガッカリ感はまったく持ってないけど、この下降傾向は、割と高い期待からはじまったことをあらわしていそうだ。

クジラ。これが一番おもろい推移になっておるよね。皆が疑問符を抱き始めた3話を境に目も当てられないほどに下降し続けているが、それよりもなによりも、3話までは期待が大きく膨らんだ、てことがはっきり出てるのがオモロイのよね。あぁ、お前もそう思ったか、て感じ。

「少女終末旅行」は宝石の国ほどではない(スタートがそう低くもないし)けどじっくり評価を上げてきたんだね。なんかわかる。宝石の国以上に入りにくいのは間違いない。これはわからない人には絶対わからない世界だし、まぁわからないからといって損をするようなもんでもないし。

観てないものについては何も言うつもりはないが、「おそ松さん」は下げ幅が大きいみたいだね。「ラブライブ! サンシャイン!!」は下がってはいるものの、最初にガクっと下がった後は下降率はさほどでもないので、まぁ安定はしてるてことかもね。ピングー in ザ・シティが不思議だね。これは最初が高過ぎた、てことだけなんだろうな、てことだとは思うけれど、そもそもこれ、普通に低年齢層向けじゃないの? よくわかんないけど。そういうのがこういう高評価過ぎるとこから始まったのって、一体何があったんだろうか?


「宝石の国」を観てるとどうしてもキャラたちの体重が気になる。雑な計算でもいいからどんくらいか知りたい。

まずは比重か密度がわかればいいね。比重は 911 Metallurgist に、密度は The Engineering ToolBox にあった。後者は以前何かでお世話になったことがあるな、多分仕事で。

フォスの情報は上2つにはなし。なのでメジャーなダイヤモンドで。密度は 3.51 g/cm3

あとは体積がわからないといけないが、金剛先生が 2m の設定だそうだから、他の宝石たちの体積はおよそ現代人の平均体積がわかれば良いだろう。これは google 検索窓に「average volume of a human」と打ち込んだだけで 95 liters と答えが出た(95000 cm3)。

333kg。うげぇ。まぢっすか。(「小錦 体重」で検索したら 287 kg だと。) 逆に人間と同じくらいの体重設定にするには、50cm くらいの身長じゃないといけない。50cm くらいだ、と思えばカワイイが、333kg だ、と思うと全然かわいくない。むぅ。(インクルージョンの存在があるので単一結晶計算よりは軽いということではあるだろうけど、そうだとしても人間並みの体重という設定にするためにはかなり強引な仮定が必要そうだ。)


08:00追記:上のグラフで加重平均でも試そうかと思ったが、真の姿とは言いがたいものしか出来なかった。とりあえず members も一緒にみれるようにはしてみる:

def visualize 以外は省略
 1 def visualize(result):
 2     import matplotlib
 3     import matplotlib.cm as cm
 4     import matplotlib.pyplot as plt
 5 
 6     fig, (ax1, ax2) = plt.subplots(1, 2)
 7     fig.set_size_inches(11.69 * 2, 16.53)
 8  
 9     max_members = -1
10     for name in result.keys():
11         X = sorted(result[name].keys())
12         max_members = max(max_members, max([result[name][x]["members"] for x in X]))
13 
14     for i, name in enumerate(sorted(result.keys())):
15         X = sorted(result[name].keys())
16         Y = [result[name][x]["score"] for x in X]
17         if len(X) > 10 and "One P" not in name \
18                 and min([result[name][x]["rank"] for x in X]) < 20:
19 
20             p = ax1.plot(X, Y, label=name)
21             idx = i % int((len(X) / 3 * 2))
22             x_pos, y_pos = X[idx], Y[idx]
23             ax1.text(x_pos, y_pos, name, fontsize=8, color=p[0].get_color())
24     ax1.set_title("https://myanimelist.net/topanime.php?type=airing")
25     ax1.set_xlabel("snapshot date")
26     ax1.set_ylabel("score")
27 
28     for i, name in enumerate(sorted(result.keys())):
29         X = sorted(result[name].keys())
30         Y = [result[name][x]["members"] for x in X]
31         if len(X) > 10 and "One P" not in name \
32                 and min([result[name][x]["rank"] for x in X]) < 20:
33 
34             p = ax2.plot(X, Y, label=name)
35             idx = i % int((len(X) / 3 * 2))
36             x_pos, y_pos = X[idx], Y[idx]
37             ax2.text(x_pos, y_pos, name, fontsize=8, color=p[0].get_color())
38     ax2.set_xlabel("snapshot date")
39     ax2.set_ylabel("members")
40 
41     plt.savefig(
42         "mal_airing2.pdf",
43         bbox_inches="tight")


ワンピースの母数が異様に多いので除外してるのと、スナップショット数が増えたので「6以上」から「10以上」に変えてるの以外はコードは基本的には同じ。

魔法使いの嫁は members の増え方もスゴいんだよな。評価するメンバーが増えても score が安定してる。無論宝石の国も母数は1/5だけど魔法使いの嫁と傾向は同じ。つまりともに新たにリストに入れた(Add To List した = members 増)人が高評価をつけている、てこと。魔法使いの嫁が「最初から高評価」なのに対し宝石の国が「最初はハゲしく低評価」なのが違うだけ。


ドラマの話。

「精霊の守り人 3話」を…、なんと、一週間近くも塩漬けしたのちに、ようやっとさっき観た。2話が酷かったもんで、なかなか手が出なくてなぁ。

3話は結構良かったぞ。ヲィ、は相変わらずあるにはあったけれど、全体としてはまぁまぁちゃんと観れた感じ。そして確信。やっぱ役者の差でしかないんだな。このドラマのバラつきは。低いところを上下してる、という意味ではある意味「安定」だったりもするけれども、極端に下の回があんのよね。第二シーズンは「極端に低い回しかない」だったことから考えると、最終章は初回と今回が「そこまで酷くない」わけだから、やっぱり第二シーズンよりは随分印象はいい。

ほんともったいないドラマだよなぁ。今回も「うまく間違えれば没入出来そう」なところはないではないんだもん。アニメでの印象と、「冷静に筋だけ追っかける」限りにおいてのドラマの印象からは、原作が描く世界はやはり好きな方なんだよねぇ。