実際の活用場面に即したタイトルにすると__「同じ図に複数データをプロットする際にcolor cycleから同じ色を指定する方法」とでもなりますが、少し一般性を持たせるためにどういう作業をするかに注目して「color cycleのN番目の色を指定する方法」__としました。最後の方法だけはN番目を指定するタイプの方法でありませんが、同じような目的を達成できるので一緒に載せました。
見た目調整の個別相談受け付けます。記事の最後にリンクがあります。
元ネタ
matplotlib - Get default line colour cycle - Stack Overflow
ほぼこの回答に沿った内容です。もう少し詳しく説明を加えています。
使えそうな場面
- プロットスタイルで設定されてるいい感じの色の組み合わせを使って測定と計算の結果を同じ色に設定してプロットしたい。まさに元ネタ回答にあった下の図と同じようなプロット(例えば破線が測定、実線が計算)
- 複数のデータをプロットした同じ体裁の図(例えば複数試料の温度依存性プロット)をたくさん作りたい。その都度Figure、Axesインスタンスを作ってると時間がかかるのでどちらも再利用したい。でもやってみたらcolor cycleが再利用時に継承されてしまい思った通りの配色にならない1。
color cycleとは
一度に複数のデータをプロットした時に自動的に順番に設定される色のことです。matplotlib.pyplot
のrcParams
に定義されており、以下のコマンドでカラーコードを直接確認できます。
plt.rcParams['axes.prop_cycle'].by_key()['color']
['#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'])
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))
ちなみに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()
「使えそうな場面」でも示した図が実行結果です。以下、三つの方法でも出力は同じです。
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))
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
((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)))
#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
インスタンス(つまりFigure
やAxes
)が生成された時にその時点でのplt.rcParams['axes.prop_cycle']
の最初の10色を参照するC0
からC9
が定義されます。C
は大文字でないとダメです。このCN
記法は色の指定を期待される場所であればどこでも使えますが、逆にいうと色の文脈でしか正しく解釈されないようです2。CN
は文字列として操作できるので下の例のように"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)
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杯で相談に乗ります。