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

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

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

# 元ネタ
[matplotlib - Get default line colour cycle - Stack Overflow](https://stackoverflow.com/a/47773515/9131000)
ほぼこの回答に沿った内容です。もう少し詳しく説明を加えています。

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

![FAOeF.png](https://qiita-image-store.s3.amazonaws.com/0/222849/8a90826d-5b07-9850-9a8d-78608a2a43fe.png)

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

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

```python
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`と呼ばれるオブジェクトです。

```python
type(plt.rcParams['axes.prop_cycle'])
```
```:output
cycler.Cycler
```

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

```python
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](https://qiita-image-store.s3.amazonaws.com/0/222849/c86a6222-550a-0b27-ff06-871fcda867e5.png)

ちなみに[ver. 2で導入された](https://matplotlib.org/2.0.2/users/dflt_style_changes.html#colors-color-cycles-and-color-maps)この10色は[Tableau（タブロー）](https://www.tableau.com/)と呼ばれる世界中で人気のビジネスインテリジェンスデータ可視化ソフトで採用されていたデフォルトのカラーパレットです（Tableau自体のデフォルトのカラーパレットは2016年に[リニューアル](https://www.tableau.com/about/blog/2016/7/colors-upgrade-tableau-10-56782)されてます）。

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

デフォルトであるTableauの10色で特に問題ないときは、その10色をそのままカラーマップにした`tab10`を使うのが良いでしょう。公式サイトの[カラーマップ一覧](https://matplotlib.org/examples/color/colormaps_reference.html)のQualitative colormapsに見本があります。

```python
# 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](https://qiita-image-store.s3.amazonaws.com/0/222849/8a90826d-5b07-9850-9a8d-78608a2a43fe.png)

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

```python
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値の一覧が見えます。

```python
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色と同じ色あることがわかります。

```python
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の色に自在にアクセスできます。

```python
# 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から採用された機能](https://matplotlib.org/2.0.2/users/dflt_style_changes.html#colors-color-cycles-and-color-maps)です。[色の指定に関する公式チュートリアル](https://matplotlib.org/tutorials/colors/colors.html)でも説明されています。
`Artist`インスタンス（つまり`Figure`や`Axes`）が生成された時にその時点での`plt.rcParams['axes.prop_cycle']`の最初の10色を参照する`C0`から`C9`が定義されます。`C`は大文字でないとダメです。この`CN`記法は色の指定を期待される場所であればどこでも使えますが、逆にいうと色の文脈でしか正しく解釈されないようです[^2]。`CN`は文字列として操作できるので下の例のように`"C{}".format(i)`などとして`for`ループで順番にアクセスできます。

```python
# 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から色のリストを取得する方法と同じく、この方法も任意のプロットスタイルで有効なことが以下のコードからもわかります。

```python
# 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](https://qiita-image-store.s3.amazonaws.com/0/222849/1de39dbc-7a4b-b378-411d-cf21db30e5ad.png)

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

```python
# 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 最後の一歩](https://sites.google.com/view/matplotlib-last-one-step/)
見た目の微調整に苦労してる方、コーヒー1杯で相談に乗ります。


[^1]: たぶん実際にやったことのある人でないと意味がわからないと思います。再利用に関する記事は後日公開です。
[^2]: この辺の詳細はおそらくソースコードを読まないとわからないでしょう。
[^3]: `ax.plot(x1, y1, x2, y2)`といった書き方で複数データを一気に描画できる`ax.plot()`は常に`Line2D`のリストを返す仕様になっています。