seabornのswarmplotで点の色を直接指定する

【注意】この記事は完成度70%くらいです。一部の図が張れていないので正しく表示されません。気が向いたら読める形にします。

seabornが好きだ。
特に指定しなくても、大抵の場合はきれいな色で美しいグラフを描画してくれるし、 matplotlibでは簡単に描けないような複雑なグラフも一発で作れる。

seabornを使って多数のグラフを別々に作った場合に、色を合わせたい場合がある。
例えば「1月のデータは赤、2月は青」という決まりでグラフを書きたい場合である。 一部のグラフでその決まりが崩れていると、読んで理解するのに時間がかかってしまうだろう。 そこで今回は、swarmplotで色を直接指定する方法について調べた。
seaborn公式ドキュメントのswamplot関数の説明はこちら

準備

import seaborn as sns
import matplotlib

# --------------------

Duplicate key in file PosixPath('/usr/local/lib/python3.8/site-packages/matplotlib/mpl-data/matplotlibrc'), line 258 ('font.family : Hiragino sans')
# 動作環境の確認
# print(pd.__version__)
# print(np.__version__)
print(sns.__version__)
print(matplotlib.__version__)
!python3 --version

# --------------------

0.11.0
3.3.1
Python 3.8.5
# https://seaborn.pydata.org/generated/seaborn.swarmplot.html
# ★styleの設定どうする?
tips = sns.load_dataset("tips")
tips.dtypes

# --------------------

total_bill     float64
tip            float64
sex           category
smoker        category
day           category
time          category
size             int64
dtype: object

※上記のdtypesで、dayなどのカラムが文字列型ではなくカテゴリカル型であることに注意、
(文字列型だと異なる挙動をする可能性もある。今回は検証していない。)

seabornのswarmplotで、色を指定する方法は2つある。
1つが、xとyのうち片方に指定したカテゴリに合わせて色を指定する方法である。
もう1つが、軸に指定したものとは別のカテゴリに合わせて色を指定する方法である。
この順に紹介する。

1つ目:軸に指定したカテゴリに合わせて色を指定したい

ax = sns.swarmplot(x="day", y="total_bill", data=tips)

xにdayを指定すると、xに応じて点に色が付く。
この色を直接指定したい。
例えば、Thurを赤で、Friを黒で、……のように具体的な色の希望があった場合、どうすればよいのか?
色を単純に入れ替えたいだけならば、hue_orderで順序を指定すれば良い……かと思ったが、hue_orderを用いてもx軸上の並びは変わらなかった。
(たぶん元データの中でdayのデータ型がcategoryなので、hue_orderを指定しても無視されて、categoryが優先される?)

ax = sns.swarmplot(x="day", y="total_bill", hue_order=["Fri", "Sun", "Sat", "Thur"], data=tips)

正解は 引数のうち、paletteを指定すればよい。

palette palette name, list, or dict
Colors to use for the different levels of the hue variable. Should be something that can be interpreted by color_palette(), or a dictionary mapping hue levels to matplotlib colors.
https://seaborn.pydata.org/generated/seaborn.swarmplot.html より

palette引数に指定できるのは、paletteの名前かlistかdictである。
「paletteの名前」はpastelやbrightなどである。カラーパレットに関する公式ドキュメントの説明を参照。今回は具体的な色を指定したいので「paletteの名前」は使えない。 listを使って指定してみよう。以下のようになる。 listの中身である「具体的なそれぞれの色」の指定方法はいくつかあるが、今回は色の名前(文字列)を使う。

ax = sns.swarmplot(x="day", y="total_bill", palette=["purple", "green", "orange", "skyblue"], data=tips)

リストだと、どの値がどの色になるのか分かりづらい。dictも指定できて、この場合は対応関係が明確になる。

ax = sns.swarmplot(x="day", y="total_bill", palette={"Thur": "purple", "Fri": "green", "Sat": "orange", "Sun": "skyblue"}, data=tips)

ちなみにpaletteをリストで指定し、長さが足りない場合、繰り返しになる

ax = sns.swarmplot(x="day", y="total_bill", palette=["purple", "green"], data=tips)

辞書でkeyがない場合は、エラーになる。そのdayを何色で塗ればいいか分からないからね。

# 辞書のkeyにFriがないのでエラー 
ax = sns.swarmplot(x="day", y="total_bill", palette={"Thur": "purple", "Sat": "orange", "Sun": "skyblue"}, data=tips)

# --------------------

---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-9-c9f97ba5b082> in <module>
      1 # 辞書のkeyにFriがないのでエラー
----> 2 ax = sns.swarmplot(x="day", y="total_bill", palette={"Thur": "purple", "Sat": "orange", "Sun": "skyblue"}, data=tips)

/usr/local/lib/python3.8/site-packages/seaborn/_decorators.py in inner_f(*args, **kwargs)
     44             )
     45         kwargs.update({k: arg for k, arg in zip(sig.parameters, args)})
---> 46         return f(**kwargs)
     47     return inner_f
     48 
/usr/local/lib/python3.8/site-packages/seaborn/categorical.py in swarmplot(x, y, hue, data, order, hue_order, dodge, orient, color, palette, size, edgecolor, linewidth, ax, **kwargs)
   2989         warnings.warn(msg, UserWarning)
   2990 
-> 2991     plotter = _SwarmPlotter(x, y, hue, data, order, hue_order,
   2992                             dodge, orient, color, palette)
   2993     if ax is None:
/usr/local/lib/python3.8/site-packages/seaborn/categorical.py in __init__(self, x, y, hue, data, order, hue_order, dodge, orient, color, palette)
   1171         """Initialize the plotter."""
   1172         self.establish_variables(x, y, hue, data, orient, order, hue_order)
-> 1173         self.establish_colors(color, palette, 1)
   1174 
   1175         # Set object attributes
/usr/local/lib/python3.8/site-packages/seaborn/categorical.py in establish_colors(self, color, palette, saturation)
    304                 else:
    305                     levels = self.hue_names
--> 306                 palette = [palette[l] for l in levels]
    307 
    308             colors = color_palette(palette, n_colors)
/usr/local/lib/python3.8/site-packages/seaborn/categorical.py in <listcomp>(.0)
    304                 else:
    305                     levels = self.hue_names
--> 306                 palette = [palette[l] for l in levels]
    307 
    308             colors = color_palette(palette, n_colors)
KeyError: 'Fri'

2つ目:軸に指定したものとは別のカテゴリに合わせて色を指定したい

# Color the points using a second categorical variable:
# 2つ目のカテゴリカル変数を用いて、点の色を指定する
ax = sns.swarmplot(x="day", y="total_bill", hue="sex", data=tips)

xにdayというカテゴリカル変数を指定して、色のパラメータhueにはsexという別のカテゴリカル変数を指定するパターン。 色を単純に入れ替えたいだけならば、hue_orderで順序を指定すれば良い。以下のようになる。

ax = sns.swarmplot(x="day", y="total_bill", hue="sex", data=tips, hue_order=["Female", "Male"])

次に、「男性が緑、女性が紫」のように、直接色を指定したい場合はどうすればよいだろうか? palette変数だよなー多分。
palette palette name, list, or dict
Colors to use for the different levels of the hue variable. Should be something that can be interpreted by color_palette(), or a dictionary mapping hue levels to matplotlib colors.

ax = sns.swarmplot(x="day", y="total_bill", hue="sex", palette=["green", "purple"], data=tips)

リストだと、どの値がどの色になるのか分かりづらい。dictも指定できて、この場合は対応関係が明確になる。

ax = sns.swarmplot(x="day", y="total_bill", hue="sex", palette={"Male": "green", "Female": "purple"}, data=tips)

# https://matplotlib.org/stable/api/_as_gen/matplotlib.colors.to_hex.html
    
print(matplotlib.colors.to_hex("green"))
print(matplotlib.colors.to_hex("purple"))

# --------------------

#008000
#800080
ax = sns.swarmplot(x="day", y="total_bill", hue="sex", palette={"Male": "#008000", "Female": "#800080"}, data=tips)




余談:color変数の挙動

color matplotlib color, optional
Color for all of the elements, or seed for a gradient palette.

ax = sns.swarmplot(x="day", y="total_bill", color="red",  data=tips)

png

なぜかFemaleの色だけを赤に指定するっぽい?

# Color the points using a second categorical variable:
# 2つ目のカテゴリカル変数を用いて、点の色を指定する
ax = sns.swarmplot(x="day", y="total_bill", hue="sex", color="red", data=tips)

png

ではcolorとして色の配列を渡せば良さそうに見えるが、それではエラーになる。

# Color the points using a second categorical variable:
# 2つ目のカテゴリカル変数を用いて、点の色を指定する
ax = sns.swarmplot(x="day", y="total_bill", hue="sex", color=["red", "gray"],  data=tips)

# --------------------

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-18-9e9e98d7907f> in <module>
      2 # 2つ目のカテゴリカル変数を用いて、点の色を指定する
      3 
----> 4 ax = sns.swarmplot(x="day", y="total_bill", hue="sex", color=["red", "gray"],  data=tips)

/usr/local/lib/python3.8/site-packages/seaborn/_decorators.py in inner_f(*args, **kwargs)
     44             )
     45         kwargs.update({k: arg for k, arg in zip(sig.parameters, args)})
---> 46         return f(**kwargs)
     47     return inner_f
     48 
/usr/local/lib/python3.8/site-packages/seaborn/categorical.py in swarmplot(x, y, hue, data, order, hue_order, dodge, orient, color, palette, size, edgecolor, linewidth, ax, **kwargs)
   2989         warnings.warn(msg, UserWarning)
   2990 
-> 2991     plotter = _SwarmPlotter(x, y, hue, data, order, hue_order,
   2992                             dodge, orient, color, palette)
   2993     if ax is None:
/usr/local/lib/python3.8/site-packages/seaborn/categorical.py in __init__(self, x, y, hue, data, order, hue_order, dodge, orient, color, palette)
   1171         """Initialize the plotter."""
   1172         self.establish_variables(x, y, hue, data, orient, order, hue_order)
-> 1173         self.establish_colors(color, palette, 1)
   1174 
   1175         # Set object attributes
/usr/local/lib/python3.8/site-packages/seaborn/categorical.py in establish_colors(self, color, palette, saturation)
    293                     colors = light_palette(color, n_colors)
    294                 elif self.default_palette == "dark":
--> 295                     colors = dark_palette(color, n_colors)
    296                 else:
    297                     raise RuntimeError("No default palette specified")
/usr/local/lib/python3.8/site-packages/seaborn/palettes.py in dark_palette(color, n_colors, reverse, as_cmap, input)
    541 
    542     """
--> 543     rgb = _color_to_rgb(color, input)
    544     h, s, l = husl.rgb_to_husl(*rgb)
    545     gray_s, gray_l = .15 * s, 15
/usr/local/lib/python3.8/site-packages/seaborn/palettes.py in _color_to_rgb(color, input)
    465         color = xkcd_rgb[color]
    466 
--> 467     return mpl.colors.to_rgb(color)
    468 
    469 
/usr/local/lib/python3.8/site-packages/matplotlib/colors.py in to_rgb(c)
    344 def to_rgb(c):
    345     """Convert *c* to an RGB color, silently dropping the alpha channel."""
--> 346     return to_rgba(c)[:3]
    347 
    348 
/usr/local/lib/python3.8/site-packages/matplotlib/colors.py in to_rgba(c, alpha)
    187         rgba = None
    188     if rgba is None:  # Suppress exception chaining of cache lookup failure.
--> 189         rgba = _to_rgba_no_colorcycle(c, alpha)
    190         try:
    191             _colors_full_map.cache[c, alpha] = rgba
/usr/local/lib/python3.8/site-packages/matplotlib/colors.py in _to_rgba_no_colorcycle(c, alpha)
    263         raise ValueError(f"Invalid RGBA argument: {orig_c!r}")
    264     if len(c) not in [3, 4]:
--> 265         raise ValueError("RGBA sequence should have length 3 or 4")
    266     if not all(isinstance(x, Number) for x in c):
    267         # Checks that don't work: `map(float, ...)`, `np.array(..., float)` and
ValueError: RGBA sequence should have length 3 or 4