K_DM Book
用語集
対象者 標準
対象者別の入口 →

対象者の設定はこのブラウザの中にだけ保存されます。

言語 JA

matplotlibスタイルシート

入門

06.8.1

matplotlibスタイルシート

最終更新 2020-01-29 読了時間 6 分
まとめ
matplotlib のスタイルシート(拡張子 .mplstyle)は、線の色・マーカーの大きさ・軸ラベルと凡例の文字サイズ・グリッドの有無といった rcParams を 1 つのテキストファイルに書き出したものです。plt.style.use("k_dm.mplstyle") を 1 回呼ぶと、その後に描くすべての図にその設定が適用されるので、plt.scatter や plt.plot を呼ぶたびに color= や fontsize= を書き足す必要がなくなります。このページでは coolors のパレット 5 色を指定した自作の k_dm.mplstyle を読み込み、iris の散布図、sin / cos の折れ線、箱ひげ図、棒グラフ、stackplot の計 7 枚で、コード側は何も変えずに見た目だけが揃う様子を確認します。レポートや本サイトのように何十枚もの図を同じ配色で揃えたいとき、設定を 1 ファイルに集めるのがこの手法の狙いです。

直感 #

毎回 plt.plot(x, y, color="#2a9d8f", linewidth=2) と書くのは、Word で段落ごとにフォントと文字サイズを手で直すのと同じ手間です。スタイルシートは Word の「スタイル」に当たり、axes.prop_cycle(線の色の順番)、lines.markersize、axes.labelsize、legend.fontsize、axes.grid などの設定を k_dm.mplstyle という 1 ファイルに書いておきます。描画コードは色もサイズも指定しないまま、plt.style.use() の 1 行だけで全部の図が同じ見た目になります。以下の例では、k_dm.mplstyle に coolors のパレット(#264653・#2a9d8f・#e9c46a・#f4a261・#e76f51 の 5 色)を線の色として登録し、散布図のマーカーサイズ・軸ラベルの文字サイズ・凡例の文字サイズ・グリッド線の描き方も同じファイルで指定しています。

デフォルトで読み込まれるスタイルシートのファイルを確認する #

matplotlib は起動時に matplotlibrc という既定のスタイルシートを読み込んでいます。そのファイルの場所は、コマンドラインで python -c "import matplotlib;print(matplotlib.matplotlib_fname())" を実行すると表示されます。

1
2
3
python -c "import matplotlib;print(matplotlib.matplotlib_fname())"

plt.show()
C:\Users\xxx\env-py3.10\.venv\lib\site-packages\matplotlib\mpl-data\matplotlibrc

このファイルを直接書き換えてもスタイルは変わりますが、matplotlib を更新すると元に戻ります。以下に示すように plt.style.use("k_dm.mplstyle") で自作ファイルを読み込む方が、プロジェクトごとに設定を持ち運べて安全です。

自作したスタイルシートを読み込む #

ここからは自作のスタイルシート k_dm.mplstyle を読み込み、7 種類のグラフで見た目がどう変わるかを確かめます。ファイルの中身(axes.prop_cycle の 5 色や lines.markersize などの値)は github 上で確認できます。

1
2
3
4
5
6
7
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris

plt.style.use("k_dm.mplstyle")  # ここで自作したスタイルシートを読み込んでいます

plt.show()

散布図 #

iris の 150 本を、がく片の長さ(横軸)とがく片の幅(縦軸)で散布図にします。3 品種を for i in range(3) で 1 品種ずつ plt.scatter に渡すので、色はスタイルシートの axes.prop_cycle の 1〜3 色目、マーカーは markers リストの ○・△・▽ で区別されます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Xy = load_iris(as_frame=True)
X = Xy["data"].values
y = Xy["target"].values

markers = ["o", "^", "v"]
featname_1 = "sepal length (cm)"
featname_2 = "sepal width (cm)"

fig, ax = plt.subplots(figsize=(8, 8))
for i in range(3):
    plt.scatter(X[y == i, 0], X[y == i, 1], marker=markers[i])
plt.xlabel(featname_1)
plt.ylabel(featname_2)
plt.legend(Xy.target_names)
plt.title("sample title")
plt.show()

出力は 8×8 インチの正方形の散布図で、3 品種が coolors パレットの濃紺(#264653)・青緑(#2a9d8f)・黄(#e9c46a)の 3 色と ○・△・▽ の形で分かれています。setosa(○)は左上に固まり、versicolor と virginica は右側で重なります。

散布図

次は格子状の点を 3 組ずらして描き、plt.grid() を明示的に呼んだときのグリッド線の見え方を確認します。格子は 3 組とも 100 点です。

1
2
3
4
5
6
7
8
fig, ax = plt.subplots(figsize=(8, 8))
X, Y = np.meshgrid(np.linspace(0.0, 5.0, 10), np.linspace(0.0, 5.0, 10))
x, y = X.ravel(), Y.ravel()
ax.scatter(x, y, label="A")
ax.scatter(x + 2.5, y + 2.5, label="B")
ax.scatter(x - 2.5, y - 2.5, label="C")
plt.grid()
plt.show()

出力では濃紺・青緑・黄の 3 組の格子点が斜めに並び、その後ろを灰色のグリッド線が通ります。グリッドの色や線種を描画コードで指定していないのに前の散布図と同じ見た目になるのは、grid.* の設定がファイル側にあるためです。

散布図

プロット #

折れ線は 2 枚描きます。1 枚目は 100 点の滑らかな線、2 枚目は 40 点に減らして "x" や "^" のマーカー指定で描いたもので、線の色・凡例・グリッドのどれもスタイルシートに任せています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
plt.figure(figsize=(13, 7))
x = np.linspace(0, 10, 100) + 1.0
plt.plot(x, np.cos(x), label="cos")
plt.plot(x, np.sin(x), label="sin")
plt.plot(x, np.log(x), label="log")
plt.plot(x, np.cos(x) + np.sin(x), label="cos+sin")
plt.legend()
plt.title("sample title")
plt.grid()
plt.show()

plt.figure(figsize=(13, 7))
x = np.linspace(0, 10, 40) + 1.0
plt.plot(x, np.cos(x), "x", label="cos")
plt.plot(x, np.sin(x), "^", label="sin")
plt.plot(x, np.log(x), ".-", label="log")
plt.plot(x, np.cos(x) + np.sin(x), "v", label="cos+sin")
plt.legend()
plt.title("sample title")
plt.show()

1 枚目は 4 色の滑らかな曲線に灰色のグリッドが入り、凡例は左上に出ます。2 枚目は同じ 4 系列が ×・△・点付きの線・▽ で描かれ、plt.grid() を呼んでいないのでグリッドはありません。どちらも上と右の枠線が消えているのは、axes.spines.top と axes.spines.right を False にする設定がスタイルシートにあるためです。

プロットの図

プロットの図

箱髭図 #

3 つ目は箱ひげ図です。平均 1・5・10、標準偏差 1・2・3 の正規乱数を 200 個ずつ作り、ax.boxplot で 3 つの箱ひげ図を並べます。中央値の線の色はスタイルシートの boxplot.medianprops.color の設定が使われます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
np.random.seed(777)

x1 = np.random.normal(1, 1, 200)
x2 = np.random.normal(5, 2, 200)
x3 = np.random.normal(10, 3, 200)

fig = plt.figure(figsize=(13, 7))
ax = fig.add_subplot(1, 1, 1)
ax.boxplot([x1, x2, x3], labels=["A", "B", "C"])
plt.title("sample title")
plt.show()

A・B・C の 3 本の箱が左から順に高くなり、C は髭も長く、20 付近に外れ値の点が 1 つ出ます。箱と髭は黒のまま、中央値の線だけがパレットの青緑(#2a9d8f)で描かれていて、matplotlib 既定の橙色の中央値線とは配色が違います。

箱髭図

棒グラフ #

10 個の位置に 5 系列の棒を横並びにし、日本語ラベル(あいうえお〜なにぬねの)の凡例を付けます。5 系列はパレットの 5 色をちょうど使い切る数で、日本語ラベルはスタイルシートの font.family に日本語フォントが指定されているかの確認にもなります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
np.random.seed(777)

fig = plt.figure(figsize=(13, 7))
indexes = np.array([i for i in range(10)]) * 6
for i, label in enumerate(["あいうえお", "かきくけこ", "さしすせそ", "たちつてと", "なにぬねの"]):
    plt.bar(indexes + i, [np.random.randint(5, 10) + i for _ in range(10)], label=label)
plt.legend()
plt.title("sample title")
plt.grid(axis="y")
plt.show()

10 グループ × 5 本の棒がパレットの 5 色で並び、凡例に 5 つの日本語ラベルが出ます。横線だけのグリッドで棒の高さが読み取れ、棒どうしは縦線に邪魔されません。

棒グラフの図

stackplot #

最後は積み上げ面グラフです。matplotlib のギャラリーより Stackplots のコードを引用し、1950〜2018 年の大陸別人口(百万人)を ax.stackplot で描きます。公式ギャラリーのコードをそのまま使っても、plt.style.use() の 1 行があれば配色がこのページの他の図と揃うことを確認するのが目的です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
year = [1950, 1960, 1970, 1980, 1990, 2000, 2010, 2018]
population_by_continent = {
    "africa": [228, 284, 365, 477, 631, 814, 1044, 1275],
    "americas": [340, 425, 519, 619, 727, 840, 943, 1006],
    "asia": [1394, 1686, 2120, 2625, 3202, 3714, 4169, 4560],
    "europe": [220, 253, 276, 295, 310, 303, 294, 293],
    "oceania": [12, 15, 19, 22, 26, 31, 36, 39],
}

fig, ax = plt.subplots(figsize=(13, 7))
ax.stackplot(
    year,
    population_by_continent.values(),
    labels=population_by_continent.keys(),
    alpha=0.8,
)
ax.legend(loc="upper left")
ax.set_title("World population")
ax.set_xlabel("Year")
ax.set_ylabel("Number of people (millions)")

plt.show()

5 大陸の面がパレットの 5 色で下から積み上がり、asia の面が最も厚く、2018 年には総和が約 7,170 百万人に達します。凡例は左上の空白に収まり、タイトルと軸ラベルの文字サイズは散布図や棒グラフと同じです。

コードを引用していますの図

使いどころと落とし穴 #

スタイルシートが効くのは、レポートや Web サイトのように 10 枚以上の図を同じ配色・同じ文字サイズで揃えたい場面です。k_dm.mplstyle を 1 つ用意して plt.style.use() を各ノートブックの先頭に書けば、担当者が違っても図の見た目が揃います。逆に、1 回限りの探索的分析で 2〜3 枚の図を眺めるだけなら既定スタイルで足り、.mplstyle を書く時間の方が長くなります。似た効果をもっと短く得たいなら、seaborn.set_theme() を 1 行呼ぶだけで配色とグリッドが整った見た目になります。

落とし穴は 2 つあります。1 つ目は、plt.style.use() の後に plt.rcParams["font.size"] = 14 のような個別設定を散らばらせることです。どの図がどの設定で描かれたか追えなくなるので、変えたい値は .mplstyle 側に書き戻します。2 つ目は配色の選び方で、axes.prop_cycle に赤と緑を隣り合わせに入れると、色覚多様性のある読者には 2 系列が同じ色に見えます。coolors のパレットをそのまま使うのではなく、matplotlib 組み込みの tableau-colorblind10 のような配色と見比べて、明度差が付くように並べ替えてください。

まとめ #

matplotlib のスタイルシートは、axes.prop_cycle・lines.markersize・axes.labelsize・legend.fontsize・grid.* といった rcParams を .mplstyle ファイルに書き出し、plt.style.use("k_dm.mplstyle") の 1 行で読み込む仕組みです。このページの 7 枚の図はどれも描画コードに color= や fontsize= を書いていませんが、iris の散布図から公式ギャラリー引用の stackplot まで同じ 5 色と同じ文字サイズで描かれました。既定の matplotlibrc を直接書き換えると pip install --upgrade で消えるので、プロジェクトのフォルダに .mplstyle を置いて読み込む形にします。配色は 5 色を超える系列には足りず、赤と緑の隣接は色覚多様性に配慮が要るので、系列が多い図ではマーカーの形や線種も変えて区別します。