Matplotlibの円グラフ(極座標系)#
※記事内に商品プロモーションを含むことがあります。
公開日
Matplotlibの円グラフを極座標系で出力する方法を解説します。
時間のサイクル(1日の時間の使い方など)を円グラフで表示する場合、円周方向に目盛りを表示できると見やすいです。 このような場合、通常の円グラフよりも極座標系の円グラフにすると便利です。
極座標系の円グラフでは、plt.subplots()のsubplot_kwオプションに{'projection': 'polar'}を与え、ax.bar(ax.pieではありません)を使用します。
極座標系のグラフ全般については以下の記事も参考にしてください。
簡単な極座標系円グラフ#
まず、極座標系円グラフの簡単な例を以下に示します。
import matplotlib.pyplot as plt
import numpy as np
# プロットしたいデータ
vals = [7, 5, 3, 1]
# 角度データ[rad]に変換する
vals_norm = vals / np.sum(vals) * 2 * np.pi
vals_left = np.cumsum(np.append(0, vals_norm[:-1]))
# 色の設定
cmap = plt.colormaps["tab10"]
colors = cmap(range(len(vals)))
fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})
ax.bar(x=vals_left, width=vals_norm, color=colors, height=1, align="edge")
ax.set_axis_off()
plt.show()
上のコードについて解説します。
極座標系では、値のリストvalsをそのままax.barに与えても正しく表示されません。
そのため、以下の通りvalsの合計が2πとなるように正規化し、極座標系でデータを表示する幅[rad]を計算します。
vals_norm = vals / np.sum(vals) * 2 * np.pi
次に、各データの表示を始める位置[rad]を求めます。
np.cumsum()は配列の累積和を求めるNumPyの関数です。
vals_leftの最初の値が0[rad]となるようにします。
vals_left = np.cumsum(np.append(0, vals_norm[:-1]))
上のグラフにおけるvals_norm, vals_leftの値を以下に示します。
print(f"{vals_norm=}")
print(f"{vals_left=}")
vals_norm=array([2.74889357, 1.96349541, 1.17809725, 0.39269908])
vals_left=array([0. , 2.74889357, 4.71238898, 5.89048623])
次に、以下でグラフの色を定義します。
ここではtab10というカラーマップから色を取得しています。
cmap = plt.colormaps["tab10"]
colors = cmap(range(len(vals)))
カラーマップについては以下の記事を参照してください。
最後に以下の通りグラフを定義・表示します。
fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})
ax.bar(x=vals_left, width=vals_norm, color=colors, height=1, align="edge")
ax.set_axis_off()
plt.show()
subplot_kw={'projection': 'polar'}によって極座標系のグラフとなります。
極座標系の場合、ax.barの引数は以下のようになります。
x: 各データの描画開始位置[rad](周方向)width: 各データの描画幅[rad](周方向)height: データの高さ(半径方向)align:xの基準となる位置(周方向){"edge","center"}
また、ax.set_axis_off()によって目盛りや罫線を消しています。
データのラベル#
グラフにデータのラベルを表示する場合、ax.text()を使用します。
主な引数を以下に示します。
x(float): 円周方向の位置[rad]y(float): 半径方向の位置(円の中心を0として、値が大きいほど外側に位置する)s(str): ラベルhorizontalalignment(str): 文字の横揃え位置
軸の目盛り#
円周方向の軸の目盛りはax.set_thetagrids()で設定できます。
主な引数を以下に示します。
angles(list[float]): 表示位置[度]labels(list[str]): 目盛りの文字列
極座標系の円グラフに、1日の予定を表示する例を以下に示します。
回転方向が時計回りになるように、ax.set_theta_direction(-1)としています。
また、ax.set_theta_zero_location("N")によって、円周方向の開始位置を上(0時の位置)としています。
# 表示したいデータ
vals = [7, 1, 1, 8, 2, 1, 4]
labels = ["sleep", "breakfast", "commute", "school", "sport", "dinner", "free"]
# 角度データに変換する
vals_norm = vals / np.sum(vals) * 2 * np.pi
vals_left = np.cumsum(np.append(0, vals_norm[:-1]))
# 色の設定
cmap = plt.colormaps["tab10"]
colors = cmap(range(len(vals)))
fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})
ax.bar(x=vals_left, width=vals_norm, color=colors, height=1, align="edge")
# データのラベルを設定
for i, label in enumerate(labels):
ax.text(x=vals_left[i]+vals_norm[i]/2, y=0.6, s=label, horizontalalignment="center")
# 円周方向の目盛りを設定
ax.set_theta_direction(-1)
ax.set_theta_zero_location("N")
ax.set_thetagrids(angles=range(0, 360, 15), labels=[str(hour) for hour in range(24)])
# 半径方向のラベルと軸を非表示にする
ax.yaxis.set_visible(False)
ax.grid(visible=False)
plt.show()
枠線を引く#
グラフのデータの間に枠線を引いた例を以下に示します。
edgecolorで色を、linewidthで線の幅を指定できます。
fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})
ax.bar(x=vals_left, width=vals_norm, color=colors, height=1, align="edge",
edgecolor="white", linewidth=1)
ax.set_axis_off()
plt.show()
ドーナツグラフ#
円グラフの中心に空白を設けてドーナツグラフにする場合、ax.bar()にbottomを指定します。
bottomには、グラフの中心と円グラフの内側の距離を指定します。
以下はheight=0.3(グラフの半径方向の幅), bottom=0.7とした例です。
fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})
ax.bar(x=vals_left, width=vals_norm, color=colors,
height=0.3, bottom=0.7, align="edge")
ax.set_axis_off()
plt.show()
二重ドーナツグラフ#
ドーナツグラフを二重にする場合、ax.barを2つ定義し、重ならないようにbottomとheightを調節します。
次の例では、以下のような位置関係となっています。
円の中心から0.38~0.68: 内側の円グラフ
円の中心から0.7~1: 外側の円グラフ
inner_vals = [1, 2, 3]
# 角度データに変換する
inner_vals_norm = inner_vals / np.sum(inner_vals) * 2 * np.pi
inner_vals_left = np.cumsum(np.append(0, inner_vals_norm[:-1]))
fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})
ax.bar(x=inner_vals_left, width=inner_vals_norm, color=colors,
height=0.3, bottom=0.38, align="edge") # 内側
ax.bar(x=vals_left, width=vals_norm, color=colors,
height=0.3, bottom=0.7, align="edge") # 外側
ax.set_axis_off()
plt.show()