項目軸をもっとわかりやすく!ライブラリ「Bokeh」で項目軸を2段表示する

2019.12.19

データアナリティクス事業本部@札幌の佐藤です。

描画するにあたってX軸やY軸を2段にしたグラフの作成することがあると思います。
Bokehで項目軸を2段にする実装は以外にはまるのかなと思い、今回は項目軸を2段にするグラフについて書いていきたいと思います。

Bokehのバージョンは1.4.0です。

項目軸を2段にするグラフのイメージ

今回はコンプティーク2019年10月号での「アイカツ!」特集のために実施されたTwitter上でのアンケート結果から、『アイカツスターズ!』のエピソードの投票数を描画していきたいと思います。

inputの情報としては以下のように第1話から最終話までを時系列として扱っています。

date episode_num episode_title vote
2016-04-07 第1話 ゆめのはじまり 2
2016-04-14 第2話 ふたりはライバル! 1
2016-04-21 第3話 わたし色の空へ 0
2016-04-28 第4話 いつだって100%! 0
2016-05-05 第5話 マイ ドレスメイク! 0
: : : :
2018-02-22 第95話 孤独な太陽 2
2018-03-01 第96話 みんなで輝く! 20
2018-03-08 第97話 Bon Bon Voyage! 6
2018-03-15 第98話 ゆずっとリリィ☆ 11
2018-03-22 第99話 ふたりの忘れ物 15
2018-03-29 第100話 まだ見ぬ未来へ☆ 15

結果として、こんな感じのグラフができます。

この2016と第1話となっているY軸の実装方法となります。

実際に描画する

import pandas as pd
# bokeh系のライブラリ
from bokeh.io import output_notebook, show
from bokeh.models import  LabelSet
from bokeh.models import ColumnDataSource, FactorRange
from bokeh.models.sources import ColumnDataSource
from bokeh.plotting import figure

# jupyter notebookで出力させるのでoutput_notebook()を記載
output_notebook()

df = pd.read_csv('XXXX/aikatsu_story.tsv', engine='python', encoding="utf-8", sep='\t',  parse_dates=[0])

DataFrameとして処理し、事前に日付型を明示させておきます。

# Bokehのデータソースの作成
# 項目軸を2段にする。
y = [(str(i), str(j)) for i, j in zip(df["date"].dt.year, df["episode_num"])]

# 各半期決算の累計をまとめる
counts = df["vote"]

source = ColumnDataSource(data=dict(y=y, counts=counts, title=df["episode_title"]))

項目軸を2段にするためには、表示したい値をTupleにしてListに格納しておく必要があります。
以下のように左から粒度の荒い順番にするイメージです。

[('2016', '第1話'), ('2016', '第2話'), ('2016', '第3話'), ('2016', '第4話'), ('2016', '第5話'), …… ('2018', '第95話'), ('2018', '第96話'), ('2018', '第97話'), ('2018', '第98話'), ('2018', '第99話'), ('2018', '第100話')]

p = figure(y_range=FactorRange(factors=[*x], # 2段にした項目軸
                               range_padding=0.01), # 項目軸の両端を幅調整
           plot_width=900,
           plot_height=1100, 
           title="『アイカツスターズ!』放送回別投票数")

# 折れ線グラフの作成
p.line(y="y", 
       x="counts", 
       color="#ff99cc", 
       line_width=2, 
       source=source)
p.circle(y="y", 
         x="counts", 
         radius=0.1, 
         line_color="#ff99cc", 
         fill_color="orange", 
         source=source)

# 各話タイトル
labels = LabelSet(y="y", 
                  x="counts", 
                  text="title", 
                  x_offset=10, # 座標の調整
                  y_offset=-5,  # 座標の調整
                  text_font_size="8pt",  # 文字フォントサイズ
                  source=source)
p.add_layout(labels)


p.xaxis.axis_label = "投票数"
p.ygrid.grid_line_color = None # Y軸のgrid線を消す

show(p)

実際に項目軸を2段にするには figure() 内の y_range ( x_range )に対しFactorRangeを使用します。
引数 factors に先ほど作成したTupleが格納されているListを可変長列として渡します。
引数 factors はTupleである必要があるので、事前に処理をしていました。

また見栄えの調整として引数 range_padding で両端のラベルを若干内側に寄せています。
グラフ上に各話タイトルをセットしているので、フォントサイズ上デフォルトのままだとグラフがきれいに表示されないためです。

最後に

投票傾向を見るとやはり物語のクライマックスを迎える第50話、第100話近辺が盛り上がっているのが分かりますね。

第86話「涙の数だけ」は強大な相手に対し果敢に立ち向かっていく『アイカツスターズ!』らしいエピソード且つ、クライマックスの前哨戦のため人気が高いようです。
このエピソードで桜庭ローラが好きになった人もいるのではないかと思います。

私の推しキャラである双葉アリアの主役エピソード第76話、第77話、第82話、第91話は、あまり投票されていませんね……。
第91話はアイカツ!エンジョイ勢の双葉アリアが初めて競うということを意識する、これも『アイカツスターズ!』らしい回なんですけど、エピソードに超人しか出てこないせいか投票数が0でした。
もっと投票されていいエピソードなのになあ。

ちなみに私は『アイカツスターズ!』ではなく、『アイカツ!』 第71話「キラめきはアクエリアス」に投票しました。