こんな人にオススメ
pythonのplotly
でレーダーチャートって描ける?描けるんならどうやって描くんだい?
ということで、今回はpythonのplotly
を使用してレーダーチャートを作成する。レーダーチャートっていうのはなんかの評価とかに使う、中心が最低点で外枠が最高点となる円もしくは多角形のやつ。下の図みたいなやつ。
ふとこれって作れるんかなと思ったので調べてここでまとめる。plotly
自体にレーダーチャート専用の関数が用意されているわけではなく、既存の関数を流用する感じ。バブルチャートと同じ。
-
-
【plotly&バブルチャート】plotlyで各国の収入と平均寿命をバブルチャートで描く
続きを見る
python環境は以下。
- Python 3.9.4
- pandas 1.2.4
- plotly 4.14.3
下準備
import sys import pandas as pd import plotly.graph_objects as go import plotly.io as pio sys.path.append('../../') import plotly_layout_template as template
まずは下準備のimport
関連。今回は各項目でデータを作成するので一括での作成はしない。plotly_layout_template
は自作のplotly
のテンプレートを入れた関数が入ってるファイル。2つ上のディレクトリにあるので'../../'
レーダーチャートを作成
まずはシンプルにレーダーチャートを作成する。チャートの色は変えているが、基本は上の図のように白地に水色っぽい灰色っぽい色のチャートができる。
シンプルに作成すると途切れる
def radar_chart(r: list, theta: list, save_name: str, fill='none'): """チャート部分の色を変更したレーダーチャートを作成 Parameters ---------- r : list 各項目の値を入れたlist theta : list 各項目名(軸名)を入れたlist save_name : str ファイル保存時の追加ファイル名 fill : str, optional チャートを塗りつぶすか否か, by default 'none' """ plot = [] d = go.Scatterpolar( r=r, # レーダーチャートの各データ theta=theta, # レーダーチャートの各項目名 fill=fill, # 塗りつぶしするか否か line=dict(color='mediumvioletred'), # 各プロット点の線の色 marker=dict(color='black'), # 各プロット点の線の色 fillcolor='rgba(106, 90, 205, 0.2)' # 塗りつぶしの色 ) plot.append(d) layout = go.Layout( title='title', # レーダーチャートのグラフ全体のタイトル xaxis=dict(title='x'), # 指定しても表示なし yaxis=dict(title='y'), # 指定しても表示なし ) fig = go.Figure(data=plot, layout=layout) fig.show() file = f"radar_chart_{save_name}" pio.write_html(fig, f"{file}.html") pio.write_image(fig, f"{file}.png")
ということでレーダーチャートを作成するが、使用する関数はgo.Scatterpolar
で極座標系のグラフを描くときに使う関数。すなわち、角度theta
°に中心からr
離れた部分にプロットすることでレーダーチャートを作成するということ。
プロットに必要なのは引数はr
とtheta
で配列で渡して各値と各項目名とする。fill
でチャートを塗りつぶしするかどうかを決められる。後のline
, marker
, fillcolor
で見た目を変更している。
r
, theta
を設定して実際にプロットすると以下。なお、x軸、y軸という概念がないので軸ラベルは設定してもつかない。
# データが軸の数ピッタリだと最初のデータと最後のデータが繋がらない r = [1, 2, 3, 4, 5] theta = ['data1', 'data2', 'data3', 'data4', 'data5'] radar_chart( r=r, theta=theta, save_name='gap', fill='none', # 塗りつぶしなし=線だけ )
fill
は'none'
にしているので塗りつぶしはなくて線のみ。で、初めと終わりのデータがくっついていないことがわかるだろう。Referenceを見てもgo
だと途切れているチャートしかなくて困惑した。途切れているのは気にしないのか。
最後の値と初めの値を同じにする
# 初めの値に戻るように設定 r = [1, 2, 3, 4, 5, 1] theta = ['data1', 'data2', 'data3', 'data4', 'data5', 'data1']
最初のデータと最後のデータを一致させることでデータの途切れを解消することができる。このデータを使ってチャートを作成したら以下。
radar_chart( r=r, theta=theta, save_name='toself', fill='toself', # 自分自身を塗りつぶし=範囲内を塗りつぶし )
radar_chart( r=r, theta=theta, save_name='none', fill='none', )
データの数と軸の数が異なる
さっきのチャートだとデータの数と軸の数が同じだった。ここではデータと軸の数が異なる時にはどのような挙動になるのかを検証。
軸の数の方が多い
# 軸の数の方が多い場合 # データの数に合わせられる r = [1, 2, 3, 4, 5] theta = ['data1', 'data2', 'data3', 'data4', 'data5', 'data6'] radar_chart( r=r, theta=theta, save_name='many_axis', fill='toself', )
まずは軸の数の方が多い場合。上のコードではデータr
の数が5つなのに対し、軸theta
の数は6つで軸の方が多い。この場合は数の少ないデータの方にチャートは合わせられる。すなわち軸が6ではなく5となる。
データの数の方が多い
# データの数の方が多い場合 # 軸の数に合わせられる r = [1, 2, 3, 4, 5] theta = ['data1', 'data2', 'data3', 'data4'] radar_chart( r=r, theta=theta, save_name='many_data', fill='toself', )
じゃあ反対にデータの数の方が多い場合はどうだろうか。この場合も少ない方に合わせられる。ここでは軸の数が4つなのでデータ数も初めから4爪までが使用される。
同じ軸名がある場合
検証はまだ続く。ここでは軸名が同じものがある場合にはどうなるのかを検証。上の例を見ればわかるように、ダブる。
同じ軸名でも構わずプロット
r = [1, 2, 3, 4, 5] # data1がかぶっている場合 theta = ['data1', 'data1', 'data3', 'data4', 'data5'] radar_chart( r=r, theta=theta, save_name='same_name1', fill='toself', )
このコードだと'data1'
が初めに2つダブっている状況。これでチャートを作成すると、r = 1
, r = 2
は同じ'data1'
にプロットされる。要するに勝手に軸を追加とかはないということ。
離れるとクロスするかも
r = [1, 2, 3, 4, 5] # data1が離れているとクロスしてしまう theta = ['data1', 'data2', 'data3', 'data1', 'data5'] radar_chart( r=r, theta=theta, save_name='same_name2', fill='toself', )
同じ名称の軸が離れていると、途中で軸に戻るのでチャートがクロスすることになる。なるほど、勝手に軸は追加されない。
同じデータの場合は考えない
軸名がダブっている場合は考えたけど、同じデータの場合はどうなん。となるが、この場合は単純にプロットされるだけ。別に星4.5の評価がいくらあってもいい。高評価なんていくつあってもいいですからね。
プロット数が1, 2, 3データの違い
腐るほど検証しているとけど気になるから許して。先ほどまではプロット数がr = 1, 2, 3, 4, 5
の5つがベースだった。ではプロット数が1の時や2の時はどうなるのか。
プロット数が1の時は点、2の時は線、3からは面
# 1の時は点、2の時は線、3から面となる r = [] theta = [] for num in range(1, 3 + 1): r += [num] theta += [f"data{num}"] radar_chart( r=r, theta=theta, save_name=f"{num}lines", fill='toself', )
1種類ずつ試すのが面倒だったのでfor
で簡略化。list
を空で作成し、このlist
にループごとに要素を足していくことでチャートを作成。
結果、プロット数が1の時は点、2の時は線、3の時は面となった。当たり前。3以降は面ができるので全部面。
軸名を色々な型にしてみる
ここでは軸に指定する型を色々と試してみる。無駄だとは思うが気になるんだもん。使用する関数は以下。
def radar_chart_complex(fill: str, save_name: str, **kwargs): plot = [] keys = list(kwargs.keys()) values = list(kwargs.values()) d = go.Scatterpolar( r=keys, theta=values, fill=fill, ) plot.append(d) layout = go.Layout( title='title', # レーダーチャートのグラフ全体のタイトル ) fig = go.Figure(data=plot, layout=layout) fig.show() file = f"radar_chart_{save_name}_{fill}" pio.write_html(fig, f"{file}.html") pio.write_image(fig, f"{file}.png")
引数を可変長引数**kwargs
にして、値と軸名を辞書型で入れるという戦法。可変長引数については以下参照。
-
-
【python&関数化】defとかargsとかを使って関数を作成する
続きを見る
今回使用する軸の型は以下。なかなかヤバいメンツ。でも$\LaTeX$は表示されない。
# アルファベット、漢字、True、list、None、 # 改行・タブありの文字列、NaN、空のdict、 # LaTeX、LaTeX、 # False、HTMLコード、Unicode r_theta = { '1': 'A', '2': '十', '3': True, '4': [1, 2, 3], '5': None, '6': 'aa\\nbb\\tc', '7': float('nan'), '8': {}, '9': r'$$\\sum_{n=2}^{5}x$$', '10': r'$\\sum_{n=2}^{5}x$', '11': False, '12': '🤔', '13': u'\\u1F914', }
radar_chart_complex
関数とr_theta
よりレーダーチャートを作成。線のみのバージョンは以下。この章の初めのチャートは以下のグラフは塗りつぶしバージョンなので、線バージョンの匹数指定のfillを'toself'
にすればいい。
# 線バージョン radar_chart_complex( fill='none', save_name='complex', **r_theta, )
できたグラフを見る限り、以下の型では軸が表示されないことがわかる。False
はセーフなのは意外。あとUnicode入力がバグ(?)った。
None
NaN
None
やNaN
を軸名に使用するときは非表示になるということを忘れずに。
複数データのレーダーチャート
ここまでは1つのデータのみを使用してレーダーチャートを作成した。ここからは複数のデータを同時にレーダーチャートに描く方法とその時の挙動について書いていく。
基本的には今までのgo.Scatter
と同じように、関数に必要なデータを入れてlist
に格納すればいい。
2データ
def radar_chart_args(save_name: str, opacity: float, **kwargs): plot = [] for color, r in kwargs.items(): d = go.Scatterpolar( r=r, theta=['data1', 'data2', 'data3', 'data4', 'data5'], fill='toself', fillcolor=color, line_color=color, opacity=opacity, ) plot.append(d) layout = go.Layout( title='title', ) fig = go.Figure(data=plot, layout=layout) fig.show() file = f"radar_chart_{save_name}" pio.write_html(fig, f"{file}.html") pio.write_image(fig, f"{file}.png")
使用するコードは上。可変長引数で色とデータを同時に取得、チャート化するという方法。可変長引数のkeys
に色、values
にデータを入れて使用する。
# 2データ dct2 = {'violet': [4, 5, 3, 5, 5], 'deepskyblue': [3, 3, 4, 1, 5],} radar_chart_args( **dct2, save_name='args_2data_1', opacity=None, )
しかし、透明度をNone
にしてしまうと完全に塗りつぶされてしまい下側のチャートの内容がわからなくなる。透明度opacity
を1より小さくすることで下のチャートを見えるようにできる。
radar_chart_args( **dct2, save_name='args_2data_2', opacity=0.5, )
3データ
# 3データ dct3 = { 'violet': [4, 5, 3, 5, 5], 'deepskyblue': [3, 3, 4, 1, 5], 'rgba(143, 188, 143, 0.8)': [5, 2, 3, 4, 5], } radar_chart_args( **dct3, save_name='args_3data_1', opacity=0.4, )
3データの時も同様、データ数が増えるだけ。ただし今回はrgba
の色指定も行ってみた。rgba
のa
が透明度alpha
を示すんだが、この値とopacity
が同時に指定されている場合は値が小さい方が優先されて指定されるようだ。
今回の例ではopacity
の方が値が小さいので緑系のチャートにはopacity
の0.4
が適用されているようだ。他のviolet
もdeepskyblue
も0.4
の透明度が適用されている。
一方で、opacity
の方が値が高い場合はviolet
, deepskyblue
にはopacity
の値が適用される一方で、rgba
指定した緑系のチャートは0.4
のままのようだ。
dct3 = { 'violet': [4, 5, 3, 5, 5], 'deepskyblue': [3, 3, 4, 1, 5], 'rgba(143, 188, 143, 0.4)': [5, 2, 3, 4, 5], } radar_chart_args( **dct3, save_name='args_3data_2', opacity=0.8, )
レイアウトを調節する
ここまででレーダーチャートの基本的な挙動については終わった。長かった。ここではレーダーチャートのレイアウトを色々と編集してみる。
template
を'none'
にするだけでレイアウトリセット
def radar_chart_layout(*args, theta: list, save_name: str, showlegend=True, polar_bgcolor='rgba(0, 0, 0, 0)', radialaxis_visible=True, radialaxis_showline=True, radialaxis_showgrid=True, radialaxis_size=None, radialaxis_gridcolor='black', angularaxis_size=None, angularaxis_linecolor='black', angularaxis_gridcolor='black', gridshape='circular', paper_bgcolor='rgb(255, 255, 255)',): plot = [] for r in args: d = go.Scatterpolar( r=r, theta=theta, fill='toself', ) plot.append(d) layout = go.Layout( template='none', # テンプレートをオフに title='title', # レーダーチャートのグラフ全体のタイトル showlegend=showlegend, # 凡例を表示するか否か polar=dict( bgcolor=polar_bgcolor, # レーダーチャートの背景色 radialaxis=dict( # 軸目盛関係 visible=radialaxis_visible, # 目盛、目盛線、グリッドを表示するか否か showline=radialaxis_showline, # 目盛線を表示するか否か showgrid=radialaxis_showgrid, # グリッドを表示するか否か tickfont=dict( size=radialaxis_size, # 目盛のフォントサイズ ), gridcolor=radialaxis_gridcolor, # グリッドの色を黒に ), angularaxis=dict( # レーダーチャートの各項目関係 tickfont=dict( size=angularaxis_size, # 項目のフォントサイズ ), linecolor=angularaxis_linecolor, # チャート部分の枠線の色 gridcolor=angularaxis_gridcolor, # 各項目から中心に向かう線の色 ), # circularで円形、linearで多角形のレーダーチャート gridshape=gridshape, ), paper_bgcolor=paper_bgcolor, # グラフ全体の背景色 ) fig = go.Figure(data=plot, layout=layout) fig.show() file = f"radar_chart_{save_name}" pio.write_html(fig, f"{file}.html") pio.write_image(fig, f"{file}.png")
いきなり超ロングなコードだが、このコードに書いてある項目が結構使いそうなレイアウトの設定なのかなと思う。それぞれの設定を関数の引数にしているので自分で指定可能。
シンプルに必要なデータだけ入力するとシンプルなレーダーチャートが出来上がる。これはgo.Layout
の引数template
を'none'
にしたから。こうすることでレイアウトがリセットされるようだ。
r1 = [1, 2, 3, 4, 5] theta = ['data1', 'data2', 'data3', 'data4', 'data5'] radar_chart_layout( r1, theta=theta, save_name='layout0', )
また、背景を灰色にしてチャートを描くとチャート部分まで灰色になっている。すなわちチャート部分の色が無色透明になっているということ。そうなのか。
radar_chart_layout( r1, theta=theta, save_name='layout1', paper_bgcolor='lightgray', )
グリッドを消したり色をいじったり
グリッド線を消したりチャートの色をいじったりすると上のようにドギツイチャートができる。まあこれは変に改造したものだけど、こんなことも一応できる。
radar_chart_layout( r1, theta=theta, save_name='layout2', polar_bgcolor='violet', radialaxis_visible=False, # Falseに radialaxis_showline=False, # Falseに radialaxis_showgrid=False, # Falseに angularaxis_linecolor='blue', # blueに angularaxis_gridcolor='green', # greenに gridshape='linear', # linearに paper_bgcolor='lightgray', # lightgrayに )
キレイ目なレーダーチャート
一方でうまく引数の設定をしてあげるとかなりキレイなレーダーチャートが出来上がる。背景を無色透明にしたりグリッドをシルバーにしたり5角形にしたりすればかなり体裁としては整っているように見える。
r2 = [3, 2, 2, 5, 4] radar_chart_layout( r1, r2, theta=theta, save_name='layout3', polar_bgcolor='rgba(0, 0, 0, 0)', radialaxis_size=20, # 20に radialaxis_gridcolor='silver', # silverに angularaxis_size=20, # 20に angularaxis_linecolor='black', # blackに angularaxis_gridcolor='silver', # silverに gridshape='linear', # linearに paper_bgcolor='rgba(0, 0, 0, 0)', # rgba(0, 0, 0, 0)に )
自作テンプレートを適用
以下の記事でplotly
のテンプレートを紹介したが、このテンプレートを使用してガッツリキレイなレーダーチャートを作成してみた。内容もリアリティのある成績関連にした。要するに今回の集大成。
-
-
【plotlyテンプレート】plotlyのグラフ作成テンプレート
続きを見る
def radar_chart_template(df, save_name: str): plot = [] for num, name in enumerate(df): color = ['violet', 'deepskyblue'][num] d = go.Scatterpolar( r=df[name], name=name, theta=df.index, fill='toself', fillcolor=color, # 塗りつぶし色を設定 line_color=color, # 線の色を設定 opacity=0.2, hovertemplate=f"{name}<br>" + '%{r} 点/100点<br>' + '%{theta}<br>' + "<extra></extra>", ) plot.append(d) layout = go.Layout( template=template.plotly_layout(), title=dict(text='成績', x=0.5, xanchor='center'), polar=dict(gridshape='linear',), radialaxis=dict(range=(0, 100),), polar_bgcolor='rgba(0, 0, 0, 0)', ) fig = go.Figure(data=plot, layout=layout) fig.show(config=template.plotly_config()) file = f"radar_chart_{save_name}" pio.write_html(fig, f"{file}.html", config=template.plotly_config()) pio.write_image(fig, f"{file}.png")
使用するデータは以下のように架空の2つの成績。科目は突発的に決めた。
score1 = { '国語A': 80, '国語B': 90, '数学I': 93, '数学II': 95, '英語1': 70, '英語2': 85, '物理': 79, '化学': 91, '地理': 71, } score2 = { '国語A': 60, '国語B': 100, '数学I': 30, '数学II': 98, '英語1': 88, '英語2': 76, '物理': 78, '化学': 100, '地理': 60, }
これらのdict
をpandas
に入れて整理して関数に入れると完成。キレイにできたと思う。
df = pd.DataFrame([score1, score2], index=['学生1', '学生2']).T print(df) # 学生1 学生2 # 国語A 80 60 # 国語B 90 100 # 数学I 93 30 # 数学II 95 98 # 英語1 70 88 # 英語2 85 76 # 物理 79 78 # 化学 91 100 # 地理 71 60 radar_chart_template(df, save_name='score')
その他のレイアウトの引数で使えそうなもの
polar = dict( sector=[0, 270], # 0 degから270 degだけ表示する(270 < deg < 360は非表示) hole=0.5, # 中心から50%の部分を無効にし、50%以降の範囲でレーダーチャート作成 radialaxis=dict( # 軸目盛関係 range=[0, 5], # チャートの表示範囲を0から5に変更 angle=10, # 目盛を1つ目のデータから反時計方向へ10 deg回転させる # counterclockwiseで目盛を1, 2つ目のデータの間に記載 # clockwise1つ目、最後のデータの間に記載(初期はclockwise) side='counterclockwise', title='axis title', # 目盛のタイトル linecolor='red', # 目盛線の色 ticks='inside', # 目盛を1, 2つ目のデータの間に記載(outsideだと1つ目、最後間) tickcolor='green', # 目盛の色 color='blue', # 目盛フォント、グリッドとかの色を一括指定 ), angularaxis=dict( # レーダーチャートの各項目関係 # counterclockwiseで真右が1つ目のデータで反時計回り # clockwiseで真上が1つ目のデータで時計回り direction='counterclockwise', ticks='outside', # 項目部分の目盛の向き tickfont=dict( color='red', # 項目のフォントカラーを赤に ), ), )
上にまあまあ使えるかなレベルの引数を示しておく。今回は使用しなかったけどいつか使えるだろう的なもの。参考までに。
これって結構使える?
今回はplotlyを使用してレーダーチャートなるものを作成した。今までレーダーチャートなんてExcelでも作成したことがなかったから今回作成して新鮮。思ったのがイヤホンのレビュー時にこのレーダーチャートを載せてもいいのかなってこと。
このブログはガジェットとpythonがメインだからガジェット好きの人が多少でもpythonに興味を持ってくれればということで考えている。やってみるか。
関連記事
-
-
【python&関数化】defとかargsとかを使って関数を作成する
続きを見る
-
-
【plotly&バブルチャート】plotlyで各国の収入と平均寿命をバブルチャートで描く
続きを見る
-
-
【WF-1000XM4】SONY最新イヤホンを比較して悩む
続きを見る
-
-
【Jabra Elite 75tレビュー】今でも十分戦える高性能イヤホン
続きを見る