LoginSignup
12
13

More than 1 year has passed since last update.

matplotlib で描画する度に色をグラデーション的に変える(2次元 ver もあるよ!)

Last updated at Posted at 2021-08-23

この記事は古川研究室 AdventCalendar 4日目の記事です.
本記事は古川研究室の学生が学習の一環として書いたものです.
内容が曖昧であったり表現が多少異なったりする場合があります.

まえがき

↓こういうことができるような実装を紹介します.
star.png

matplotlib で描画関数を呼び出す度に
色をグラデーション的に変えたいと思ったことはありますでしょうか.
私にはあります.

今回はその方法について書かせていただこうと思います.
何番煎じかわかりませんが,
様々な記事を参考にさせていただき
個人的に好みな実装にたどり着いたので投稿しようと思った次第です.

この記事を読んでくれた方には
ぜひ参考文献にある記事とも比較して
どの方法を採用するかを決めていただけたらと思っております.
また,本記事には個人的な好みが散りばめられていることをご了承ください.

お急ぎの方は実装例までジャンプしてもらって OK です.

matplotlib

matplotlib は言わずと知れた Python の描画ライブラリです.
静的なグラフはもちろん,アニメーションやマウス操作などによるインタラクションなども
包括的にサポートしています.

matplotlib の機能として,
plotscatter などの描画関数を複数回呼び出す時に
グラフの色を変えてくれるというものがあります1
デフォルトでは 青→オレンジ→緑→・・・のように変化します.
本記事のゴールは,これをグラデーション的は配色に変えることです.

デフォルトの配色はどうなっているのか

デフォルトの配色はどうなっているかという話は,
下のページに詳しく書かれています.

newcolors.png
(公式サイトより抜粋)

デフォルトでは,描画関数を複数回呼び出すと#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 個ほど描画すると下図ようになります.

default.png

下から上に連続的に変化していることが見えづらい上に,目がチカチカしてしまいますね.
そこで,描画する度にグラデーション的に配色を変えたいというモチベーションが湧きました.

How to

どうやってグラデーションにするのかの方針を立てます.
コード全体は実装例にあるので参考にされるのであればそちらをおすすめします.

Step 1. 色のリストを作成

まず色のリストを作成します.
一般的なのはmatplotlib.cmにある ColorMap を用いる方法かと思います.
色の指定は RGB のタプルでも可能ですので自作するのもアリです.

ColorMapを用いる場合
# 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個のサインカーブを
デフォルトの配色で描画した場合とグラデーションで描画した場合とで比較します.

sin.py
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()

実行結果はこちら
sin.png

Cool なグラデーションで見やすくなっています.
plot 関数の引数で色を指定する必要はなく,自動で色を指定してくれます.

おまけ:2次元のグラデーション

色のリストを自作することで,2次元のグラデーションも実現できそうだったので書いてみました.
HSV色空間のH(色相)V(明度)を変化させるサンプルを以下に示します.

star.py
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 の座標を中心に,星の形になるように点群を生成しています.

実行結果はこちら
star.png

2次元のグラデーションも比較的簡単にできます.
デフォルトの配色だと無秩序な感じになってしまっていますね4
今回の例に限らず,配色を上手く利用することで
視認性を上げたり,場合によっては得られる情報を増やしたりすることができます.

おわりに

私が本記事にある内容を調べようと思った理由は,
研究で「マルチ多様体モデリングによるメタ学習」を扱う際に,
学習結果を描画する際に "タスクごとに" プロットする色をグラデーション的に変えたいとなったからです.

「マルチ多様体モデリングによるメタ学習」とは何ぞやと興味を持っていただいた方は
是非以下のリンクをご参照ください(宣伝).

参考にさせていただいた文献


  1. 同じ描画関数を複数回呼び出した時に色が変化します.plotscatterのように呼び出しても色は変わりません.  

  2. 色のサンプルが見れるの凄い. 

  3. 公式が出している記事で見た記憶がありますが,時間の関係でソースにはたどり着けませんでした. 

  4. これはこれでキレイだという意見もあるかも知れませんが,そういう話ではありません. 

12
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
13