Python
matplotlib
実験屋のためのPython

matplotlibでcolor cycleのN番目の色を指定するいくつかの方法

More than 1 year has passed since last update.

実際の活用場面に即したタイトルにすると「同じ図に複数データをプロットする際にcolor cycleから同じ色を指定する方法」とでもなりますが、少し一般性を持たせるためにどういう作業をするかに注目して「color cycleのN番目の色を指定する方法」としました。最後の方法だけはN番目を指定するタイプの方法でありませんが、同じような目的を達成できるので一緒に載せました。

見た目調整の個別相談受け付けます。記事の最後にリンクがあります。


元ネタ

matplotlib - Get default line colour cycle - Stack Overflow

ほぼこの回答に沿った内容です。もう少し詳しく説明を加えています。


使えそうな場面


  • プロットスタイルで設定されてるいい感じの色の組み合わせを使って測定と計算の結果を同じ色に設定してプロットしたい。まさに元ネタ回答にあった下の図と同じようなプロット(例えば破線が測定、実線が計算)

FAOeF.png


  • 複数のデータをプロットした同じ体裁の図(例えば複数試料の温度依存性プロット)をたくさん作りたい。その都度Figure、Axesインスタンスを作ってると時間がかかるのでどちらも再利用したい。でもやってみたらcolor cycleが再利用時に継承されてしまい思った通りの配色にならない1


color cycleとは

一度に複数のデータをプロットした時に自動的に順番に設定される色のことです。matplotlib.pyplotrcParamsに定義されており、以下のコマンドでカラーコードを直接確認できます。

plt.rcParams['axes.prop_cycle'].by_key()['color']


output

['#1f77b4',

'#ff7f0e',
'#2ca02c',
'#d62728',
'#9467bd',
'#8c564b',
'#e377c2',
'#7f7f7f',
'#bcbd22',
'#17becf']

一見すると要素が10個のリストのようですが、これはby_key()で表示しているからであって、plt.rcParams['axes.prop_cycle']自体は11個目は最初の色に戻って…と無限に巡回できる Cyclerと呼ばれるオブジェクトです。

type(plt.rcParams['axes.prop_cycle'])


output

cycler.Cycler


このデフォルトのcolor cycleを使って12本のサインカーブを書いてみます。11本目、12本目が1本目と2本目の色と同じなのがわかります。

n = 12

x = np.linspace(0, 2*np.pi, 100)
for i in range(n):
plt.plot(x, np.sin(x+np.pi/n*i), label=i)

plt.legend(loc='upper left', bbox_to_anchor=(1,1))

download.png

ちなみにver. 2で導入されたこの10色はTableau(タブロー)と呼ばれる世界中で人気のビジネスインテリジェンスデータ可視化ソフトで採用されていたデフォルトのカラーパレットです(Tableau自体のデフォルトのカラーパレットは2016年にリニューアルされてます)。


デフォルトの10色と同じカラーマップtab10を使う

デフォルトであるTableauの10色で特に問題ないときは、その10色をそのままカラーマップにしたtab10を使うのが良いでしょう。公式サイトのカラーマップ一覧のQualitative colormapsに見本があります。

# https://stackoverflow.com/a/47773515/9131000 より

import numpy as np
import matplotlib.pyplot as plt

fig = plt.figure()
ax = fig.add_subplot(111)

t = np.arange(5)
cmap = plt.get_cmap("tab10") # ココがポイント

for i in range(4):
ax.plot(t,i*(t+1), color=cmap(i), linestyle = '-')
ax.plot(t,i*(t+1)+.3,color=cmap(i), linestyle = ':')
plt.show()

「使えそうな場面」でも示した図が実行結果です。以下、三つの方法でも出力は同じです。

FAOeF.png

cmapはリストオブジェクトではなくmatplotlib.colors.ListedColormapオブジェクトです。リストの要素を参照するcmap[i]ではなくcmap(i)であることに注意してください。ListedColormapオブジェクトは引数が色の数cmap.Nの範囲外だと一番外側の色を繰り返します。

print('cmap.N:', cmap.N)

print('cmap(-1):', cmap(-1))
print('cmap(0):', cmap(0))
print('cmap(9):', cmap(9))
print('cmap(10):', cmap(10))


output

cmap.N: 10

cmap(-1): (0.12156862745098039, 0.46666666666666667, 0.70588235294117652, 1.0)
cmap(0): (0.12156862745098039, 0.46666666666666667, 0.70588235294117652, 1.0)
cmap(9): (0.090196078431372548, 0.74509803921568629, 0.81176470588235294, 1.0)
cmap(10): (0.090196078431372548, 0.74509803921568629, 0.81176470588235294, 1.0)

colorsプロパティを見ると1に規格化されたRGB値の一覧が見えます。

cmap.colors


output

((0.12156862745098039, 0.4666666666666667, 0.7058823529411765),

(1.0, 0.4980392156862745, 0.054901960784313725),
(0.17254901960784313, 0.6274509803921569, 0.17254901960784313),
(0.8392156862745098, 0.15294117647058825, 0.1568627450980392),
(0.5803921568627451, 0.403921568627451, 0.7411764705882353),
(0.5490196078431373, 0.33725490196078434, 0.29411764705882354),
(0.8901960784313725, 0.4666666666666667, 0.7607843137254902),
(0.4980392156862745, 0.4980392156862745, 0.4980392156862745),
(0.7372549019607844, 0.7411764705882353, 0.13333333333333333),
(0.09019607843137255, 0.7450980392156863, 0.8117647058823529))

このRGB値に255をかけてint型にしてformatterを使って16進法にすると、先ほどのrcParamsで確認したデフォルトの10色と同じ色あることがわかります。

for rgb in np.array(cmap.colors)*255:

rgb = rgb.astype(int)
print('#{:02x}{:02x}{:02x}'.format(*tuple(rgb)))


output

#1f77b4

#ff7f0e
#2ca02c
#d62728
#9467bd
#8c564b
#e377c2
#7f7f7f
#bcbd22
#17becf


color cycleの色をリストとして取得する

matplotlib.style.use()によってプロットスタイルを変えて、plt.rcParams['axes.prop_cycle']がデフォルトの10色でなくなっている場合は、この記事の最初のほうでカラーコードを表示したのと同じ方法で色のリストを取得することでcolor cycleの色に自在にアクセスできます。

# https://stackoverflow.com/a/47773515/9131000 より

import numpy as np
import matplotlib.pyplot as plt

fig = plt.figure()
ax = fig.add_subplot(111)

t = np.arange(5)
cycle = plt.rcParams['axes.prop_cycle'].by_key()['color'] # ココがポイント

for i in range(4):
ax.plot(t,i*(t+1), color=cycle[i], linestyle = '-')
ax.plot(t,i*(t+1)+.3,color=cycle[i], linestyle = ':')

plt.show()

前項のcmapと違い、ここではcycleはリストオブジェクトなのでcycle[i]で各要素にアクセスできます。


CN記法を使う

Ver. 2から採用された機能です。色の指定に関する公式チュートリアルでも説明されています。

Artistインスタンス(つまりFigureAxes)が生成された時にその時点でのplt.rcParams['axes.prop_cycle']の最初の10色を参照するC0からC9が定義されます。Cは大文字でないとダメです。このCN記法は色の指定を期待される場所であればどこでも使えますが、逆にいうと色の文脈でしか正しく解釈されないようです2CNは文字列として操作できるので下の例のように"C{}".format(i)などとしてforループで順番にアクセスできます。

# https://stackoverflow.com/a/47773515/9131000 より

import numpy as np
import matplotlib.pyplot as plt

fig = plt.figure()
ax = fig.add_subplot(111)

t = np.arange(5)

for i in range(4):
# color="C{}".format(i)がポイント
ax.plot(t,i*(t+1), color="C{}".format(i), linestyle = '-')
ax.plot(t,i*(t+1)+.3,color="C{}".format(i), linestyle = ':')

plt.show()

color cycleから色のリストを取得する方法と同じく、この方法も任意のプロットスタイルで有効なことが以下のコードからもわかります。

# https://matplotlib.org/tutorials/colors/colors.html の例を少し変更

import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl

th = np.linspace(0, 2*np.pi, 128)

fig = plt.figure(figsize=(5,2.5))

def demo(sty, subplot):
mpl.style.use(sty)
ax = fig.add_subplot(subplot)
ax.set_title('style: {!r} (color=C0)'.format(sty), color='C0')
ax.plot(th, np.cos(th), 'C1', label='C1')
ax.plot(th, np.sin(th), 'C2', label='C2')
ax.legend()

demo('default', 121)
demo('seaborn', 122)

download-1.png


Line2D.get_color()を使う

color cycleのN番目の色を指定する方法ではないですが、「同じ図に複数データをプロットする際にcolor cycleから同じ色を指定する方法」と考えるとLine2Dインスタンスのget_color()も使えます。これまで挙げた方法は「複数のデータに同じ色を指定する」というアプローチでしたが、この方法は「先にプロットしたデータの線(Line2Dインスタンス)から色を取得して、後からプロットするデータにその色を指定する」というアプローチです。

# https://stackoverflow.com/a/47773515/9131000 より

import numpy as np
import matplotlib.pyplot as plt

fig = plt.figure()
ax = fig.add_subplot(111)

t = np.arange(5)

for i in range(4):
line, = ax.plot(t,i*(t+1), linestyle = '-')
ax.plot(t,i*(t+1)+.3,color = line.get_color(), linestyle = ':')

plt.show()

line, =となっているのは、ax.plot(x, y)の返すLine2Dインスタンスが1つだけ入ったリストをunpackするためです3


個別相談始めました

matplotlib 最後の一歩

見た目の微調整に苦労してる方、コーヒー1杯で相談に乗ります。





  1. たぶん実際にやったことのある人でないと意味がわからないと思います。再利用に関する記事は後日公開です。 



  2. この辺の詳細はおそらくソースコードを読まないとわからないでしょう。 



  3. ax.plot(x1, y1, x2, y2)といった書き方で複数データを一気に描画できるax.plot()は常にLine2Dのリストを返す仕様になっています。