[matplotlib] 任意の数の色を使って色分けする

※ この記事を書く際に利用したコードに関連するライブラリのバージョンは以下になります．

```
Python: 3.9.7
matplotlib:  3.4.2
numpy:  1.20.3
```

# 要点だけまとめ
色分けで使いたい色の数に応じた対応:

- 3~5色程度で色分けしたい: 手動でラベルから色に対応する配列を作る．
- ~20色: `matplotlib.tab10`や`matplotlib.tab20`などを使う．
- 20色以上: `matplotlib.colors.CSS4_COLORS`を使う．

# クラスタやカテゴリごとに色分けしたい
## 使う場面
以下のように，散布図の点たちをクラスタごとに（もしくは，クラスタに限らず，何らかの条件で）色分けしたいようなケースを考えます．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/462721/59719702-8834-efef-a2b8-798541fb17eb.png)

クラスタ数が3など小さい場合は，`label2color = ['r', 'g', 'b']`のように設定して，以下のような関数を使えばよいです．

```python:クラスタごとに点を色分けしてプロットする
def plot_points(d, label2color):
  fig, ax = plt.subplots(figsize=(8, 8))
  for i in range(len(d)):
    # d[i]: クラスタiに属する点[x,y]のリスト
    ax.scatter(d[i][:, 0], d[i][:, 1], s=6,
              c=label2color[i], label=i) # ここの引数cで色を設定している
    ax.set_xlabel('x')
    ax.set_ylabel('y')
    ax.legend(fontsize=12, prop={'size':24}, 
            loc='center left', bbox_to_anchor=(1,0.5), 
            markerscale=10)
  plt.show()
```
しかし，クラスタ数が多くなってくると，手動で`label2color`のような配列を設定するのは骨が折れます．

## この記事で説明すること
この記事では，任意の数の色で色分けする方法について説明します．
結論を述べると，10色~20色ほど使いたい場合，`matplotlib.cm.tab10`や`matplotlib.cm.tab20`などのカラーマップが用意されているのでそれを使えばいいです．
また，それ以上の数の異なる色を使いたい場合は，`matplotlib.colors.CSS4_COLORS`を使うと良いかと思います．

# ~20色程度の色を使いたい場合
この場合は，`matplotlib.cm.tab10`や`matplotlib.cm.tab20`を使うとよいです．
例えば，上で示したコード中の`ax.scatter()`において，引数`c=matplotlib.cm.tab20(i)` (`i`はクラスタの番号)のように設定します．
すると，さきほど手動で3色分定義していた，`label2color`のような配列を定義しなくても自動で色分けしてくれます．

```python:tab20を使って色分けしてプロットする
def plot_points_tab(d):
  fig, ax = plt.subplots(figsize=(8, 8))
  for i in range(len(d)):
    ax.scatter(d[i][:, 0], d[i][:, 1], s=6,
              c=matplotlib.cm.tab20(i), label=i) 
    ax.set_xlabel('x')
    ax.set_ylabel('y')
    ax.legend(fontsize=12, prop={'size':24}, 
            loc='center left', bbox_to_anchor=(1,0.5), 
            markerscale=10)
  plt.show()
```

クラスタ数=20として点列を生成して，上の関数を利用してプロットすると以下のようになります．
20個のクラスタが異なる色で色分けされていることがわかります（結構似ている色もありますが）．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/462721/851437c4-2e1b-3b65-7209-f095b5331b5b.png)


# 20色以上程度の色を使いたい場合
先ほど使っていた`matplotlib.cm.tab20`は20色しか用意されていないので，それ以上の数の色を使いたい場合は違う方法を取らないといけません．

## `matplotlib.colors.CSS4_COLORS`について
そこで，今回は `matplotlib.colors.CSS4_COLORS` を使う方法を紹介します．
`matplotlib.colors.CSS4_COLORS`では148色の色が定義されています．

```python:matplotlib.colors.CSS4_COLORSについて確認
print(type(matplotlib.colors.CSS4_COLORS)) # >> <class 'dict'>
print(len(matplotlib.colors.CSS4_COLORS))  # >> 148
```

つまり，`matplotlib.colors.CSS4_COLORS`は辞書型で148個の要素からなることがわかります．
実際の中身は，以下のように，キー = 利用できる色名，値 = 対応するカラーコード として格納されています．
`{'aliceblue': '#F0F8FF', 'antiquewhite': '#FAEBD7', 'aqua': '#00FFFF', 'aquamarine': '#7FFFD4', 'azure': '#F0FFFF', 'beige': '#F5F5DC', 'bisque': '#FFE4C4', 'black': '#000000', ...`

## ランダムに任意の数のカラーコードを取得してリストにする
今回は，必要な数だけカラーコードを取得するために，この辞書からランダムに抽出を行います．
それを行う関数を以下に示します．

```python:任意の数のカラーコードを生成する関数
def choose_colors(num_colors):
  # matplotlib.colors.CSS4_COLORSの値だけをリストにする
  tmp = list(matplotlib.colors.CSS4_COLORS.values())
  # リストにしたものをランダムにシャッフルする
  random.shuffle(tmp)
  # 必要な数だけ先頭から取り出してreturnする
  label2color = tmp[:num_colors]
  return label2color
```

この関数 `choose_colors()` を使えば，自動で各ラベルに対応する色のリストを作ることができます．
つまり，冒頭で示したような，ラベルと色の対応を表すリスト`label2color`が，任意のサイズで作れることになります．
実際に，この関数 `choose_colors()` と冒頭のプロット用の関数 `plot_points()` を使って，クラスタ数=30でプロットしてみると以下のようになりました．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/462721/a9693d36-e872-dfe6-ef75-88d7ff44bd0f.png)

ほとんど見分けがつかないが微妙に違う色(25番と28番など)がありますが，この辺を解決するのは難しそう．．．

[追記]
クラスタの数が多い場合，凡例を横に出しておくと見づらいです．
以下のように，クラスタの中心にラベルを記載しておくことで多少はマシになると思います（コメント頂いた方ありがとうございました）．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/462721/29372b67-8d5e-4d8e-417f-24a1198ffcfa.png)

```python:クラスタ中心にラベルを記載してプロットする関数
def plot_points_annotated(d, label2color):
  fig, ax = plt.subplots(figsize=(8, 8))
  for i in range(len(d)):
    ax.scatter(d[i][:, 0], d[i][:, 1], s=6,
              c=label2color[i], label=i)
    ax.set_xlabel('x')
    ax.set_ylabel('y')
    # 各クラスタの中心にラベルを記載
    ax.annotate(i,
              xy=tuple(np.mean(d[i], axis=0)), fontsize=10, color='k',
              bbox={'facecolor':label2color[i], 'edgecolor':'k', 'alpha':0.8})
  plt.show()
```


ちなみに148色以上使いたい場合（そんなことなかなかなさそうですが）は，`matplotlib.colors.XKCD_COLORS`というものがあり，こちらは949色使えるそうです．

# 参考
[matplotlibで色をたくさん使う](https://blog.imind.jp/entry/2019/05/10/031150)