この記事は古川研究室 AdventCalendar 4日目の記事です.
本記事は古川研究室の学生が学習の一環として書いたものです.
内容が曖昧であったり表現が多少異なったりする場合があります.
まえがき
matplotlib で描画関数を呼び出す度に
色をグラデーション的に変えたいと思ったことはありますでしょうか.
私にはあります.
今回はその方法について書かせていただこうと思います.
何番煎じかわかりませんが,
様々な記事を参考にさせていただき
個人的に好みな実装にたどり着いたので投稿しようと思った次第です.
この記事を読んでくれた方には
ぜひ参考文献にある記事とも比較して
どの方法を採用するかを決めていただけたらと思っております.
また,本記事には個人的な好みが散りばめられていることをご了承ください.
お急ぎの方は実装例までジャンプしてもらって OK です.
matplotlib
matplotlib は言わずと知れた Python の描画ライブラリです.
静的なグラフはもちろん,アニメーションやマウス操作などによるインタラクションなども
包括的にサポートしています.
matplotlib の機能として,
plot
や scatter
などの描画関数を複数回呼び出す時に
グラフの色を変えてくれるというものがあります1.
デフォルトでは 青→オレンジ→緑→・・・のように変化します.
本記事のゴールは,これをグラデーション的は配色に変えることです.
デフォルトの配色はどうなっているのか
デフォルトの配色はどうなっているかという話は,
下のページに詳しく書かれています.
デフォルトでは,描画関数を複数回呼び出すと#1f77b4
(青)→#ff7f0e
(オレンジ)→#2ca02c
(緑)→...のように色が変化します(上図の上から順に)2.
これらの色には 'cyan' など特別な名前が付いているわけではありません.
この配色や順番は,視認性の観点から非常によく練られています3.
デフォルトの配色も非常に良いのですが,
「どういう順番になっているか」という情報が欲しいときには少し不便です.
まず,配色からどういう順番になっているかを読み取るには色の順番を把握する必要があります.
また,慣れていれば色の順番はわかってくるものですが,わかっていたとしても
描画数が多くなるとごちゃごちゃして視認性が悪くなってしまいます.
例えば,$$y_i = \sin(x) + \frac{i}{10}, i \in \left\{0, 1, \dots, 29 \right\}, x\in [ -\pi, + \pi ] $$
のように,縦方向に $0.1$ ずつシフトしたサインカーブを 30 個ほど描画すると下図ようになります.
下から上に連続的に変化していることが見えづらい上に,目がチカチカしてしまいますね.
そこで,描画する度にグラデーション的に配色を変えたいというモチベーションが湧きました.
How to
どうやってグラデーションにするのかの方針を立てます.
コード全体は実装例にあるので参考にされるのであればそちらをおすすめします.
Step 1. 色のリストを作成
まず色のリストを作成します.
一般的なのはmatplotlib.cm
にある ColorMap を用いる方法かと思います.
色の指定は RGB のタプルでも可能ですので自作するのもアリです.
# Cool なグラデーションになります.
colors = [ mpl.cm.cool(i) for i in np.linspace(0, 1, 10, endpoint=False) ]
from colorsys import hsv_to_rgb
# 色相を 0~1 に変化させることで虹色のグラデーションになります.
colors = [ hsv_to_rgb(h, 0.9, 1.0) for h in np.linspace(0, 1, 10, endpoint=False) ]
Step 2. Axes オブジェクトに反映
次は配色のリストをどうやって反映させるのかという話になります.
公式ドキュメントには,以下のようにグローバルに設定する方法が紹介されていました.
from cycler import cycler
mpl.rcParams['axes.prop_cycle'] = cycler(color='bgrcmyk')
個人的にプログラム全体に影響を及ぼすことは避けたいため,
Axesクラスの資料にもありますように,matplotlib.axes.Axes.set_prop_cycle
を用いて,
以下のようにAxes
オブジェクトごとに配色を設定したいと思います.
fig = plt.figure(figsize=(7, 7))
ax = fig.add_subplot(111)
ax.set_prop_cycle(color=colors) # Step 1 で定義した colors
colors
自体はリストですが,
matplotlib.axes.Axes.set_prop_cycle
の 内部でitertools.cycle
に変換してくれているので
公式のサンプルのcycler
を用いたときと同様,
色リストの終端に来たら先頭に戻るような挙動をしてくれます.
実装例
先程の30個のサインカーブを
デフォルトの配色で描画した場合とグラデーションで描画した場合とで比較します.
import numpy as np
import matplotlib as mpl
from matplotlib import pyplot as plt
if __name__ == '__main__':
# Making multiple sine curves.
num_tasks = 30
num_samples = 1000
x = np.linspace(-np.pi, np.pi, num_samples)
Y = np.empty((num_tasks, num_samples))
for i in range(num_tasks):
Y[i] = np.sin(x) + 0.1*i
# Making list of colors.
colors = [ mpl.cm.cool(i) for i in np.linspace(0, 1, num_tasks, endpoint=False) ]
# Drawing sine curvies.
fig = plt.figure(figsize=(7, 7))
ax = fig.add_subplot(111)
ax.set_aspect('equal')
ax.set_prop_cycle(color=colors) # Setting color cycle !
for y in Y:
ax.plot(x, y)
plt.show()
Cool なグラデーションで見やすくなっています.
plot
関数の引数で色を指定する必要はなく,自動で色を指定してくれます.
おまけ:2次元のグラデーション
色のリストを自作することで,2次元のグラデーションも実現できそうだったので書いてみました.
HSV色空間の**H(色相)とV(明度)**を変化させるサンプルを以下に示します.
import numpy as np
import matplotlib as mpl
from matplotlib import pyplot as plt
from itertools import product
from colorsys import hsv_to_rgb
def make_grid(resolution, domain):
mesh = np.linspace(domain[0], domain[1], resolution)
return np.array(list(product(mesh, repeat=2)))
def gen_star_dots(center, radius):
thetas = (np.linspace(0, 4, 6, endpoint=True) + 0.1) * np.pi
x = radius * np.cos(thetas) + center[0]
y = radius * np.sin(thetas) + center[1]
return np.concatenate([x[:, np.newaxis], y[:, np.newaxis]], axis=1)
if __name__ == '__main__':
# Making multiple stars (7*7).
resolution = 7
grid = make_grid(resolution, domain=(0, 1))
X = np.empty((resolution**2, 6, 2))
for i, center in enumerate(grid):
X[i] = gen_star_dots(center, radius=0.05)
# Making list of colors (7*7 × RGB).
colors = [
hsv_to_rgb(h, 0.8, v) for h, v in make_grid(resolution, domain=(0.3, 1))
]
# Drawing sine stars.
fig = plt.figure(figsize=(7, 7))
ax = fig.add_subplot(111)
ax.set_aspect('equal')
ax.set_prop_cycle(color=colors) # Setting color cycle !
for x in X:
ax.plot(x[:, 0], x[:, 1], lw=3)
plt.show()
make_grid
関数では,2次元平面でグリッド状に座標を振っています.
また gen_star_dots
関数では center
の座標を中心に,星の形になるように点群を生成しています.
2次元のグラデーションも比較的簡単にできます.
デフォルトの配色だと無秩序な感じになってしまっていますね4.
今回の例に限らず,配色を上手く利用することで
視認性を上げたり,場合によっては得られる情報を増やしたりすることができます.
おわりに
私が本記事にある内容を調べようと思った理由は,
研究で「マルチ多様体モデリングによるメタ学習」を扱う際に,
学習結果を描画する際に "タスクごとに" プロットする色をグラデーション的に変えたいとなったからです.
「マルチ多様体モデリングによるメタ学習」とは何ぞやと興味を持っていただいた方は
是非以下のリンクをご参照ください(宣伝).
参考にさせていただいた文献
- matplotlibでcolor cycleのN番目の色を指定するいくつかの方法(Qiita)
- matplotlibで色をグラデーション的に選択(Qiita)
- matplotlibで色をグラデーションにする(Qiita)
- matplotlibのcolor cycleをいい感じにしてみる(Qiita)
- Changes to the default style(matplotlib)
- matplotlib.axes.Axes.set_prop_cycle(matplotlib)
- Choosing Colormaps in Matplotlib(matplotlib)