注意
【matplotlib v2.1.1以前】の時のエラーです。
【matplotlib v2.1.2以降】の方はあまり関係ないかもしれません。
経緯
雑にpythonのseabornを使ってグラフを書いていたら、
同じカテゴリカルデータを使って書いた2つのグラフを重ねて表示したくなった。
sns.pointplot(x="x", y="fitting_y", hue="y_limit", data=result, markers="")
sns.pointplot(x="x", y="y", hue="y_limit", data=result, join=False)
と書いたら、以下のようなエラーが出てきて処理が止まった。
File "${HOME}/anaconda3/lib/python3.6/site-packages/matplotlib/legend.py", line 1344, in _in_handles
if f_h.get_facecolor() != h.get_facecolor():
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
上記のエラーを修正しようとした。
経緯(図解)
こういう2つのグラフがある
これらを……
↓ こうしたい
解決法(正攻法)
skotaroさんにコメントでコードを教えてもらったため、記述させていただきます。
groupby
を利用して描画した後、legendを利用して凡例を記述するのが正攻法です。
fig, ax = plt.subplots()
grouped = result.groupby('y_limit')
for key, gr in grouped:
gr.plot('x', 'fitting_y', ax=ax)
color = ax.lines[-1].get_color()
gr.plot.scatter('x', 'y', marker='o', ax=ax, color=color)
ax.legend(ax.collections, list(grouped.groups.keys()), loc='upper left', bbox_to_anchor=(1,1))
解決法(邪道)
どうしてもseaborn
を利用して、プロットしたい人向け
ax = sns.pointplot(x="x", y="fitting_y", hue="y_limit", data=result, markers="")
ax.collections.clear() # 「今回の肝」 コレが無いとエラーで落ちる。
sns.pointplot(x="x", y="y", hue="y_limit", data=result, join=False, ax=ax)
hueを連続して使いたいなら、ax.collectionsに溜まる色付きラベルPathCollection
を末尾以外で随時削除するべし。
原因【matplotlib v2.1.1以前】
seabornで hueを指定した際、legend(凡例設定)が自動的に作成され、hueから自動生成されたPathCollection
(色付きラベル) [※1] が凡例表示用にaxに保存される。
連続してplotする際に、二度目の関数でもhueが指定されている場合、やはりlegendを自動生成・追記しようとする。
hueからPathCollection
が自動生成されるが、これは保存済みの※1と同じもの である。
ここで 問題となるのが、axの凡例表示用PathCollection
の保存時に重複チェックがある こと。
凡例に同じ色を保存しようとすると AttributeError
(属性エラー)系で落ちてしまう 重複が取り除かれる予定だった
コレが今回エラーで落ちた原因だったようだ。
追記(2018/04/23):PathCollection
は描画処理に関するものらしく記述が怪しすぎるので削除。
(実際のエラーは上記にも書いたようにValueError
)
追記
本当の原因はValueError
。
np.repeat([[True,True,True, False]],19,axis=0)
のような値を取るf_h.get_facecolor() != h.get_facecolor()
について
any()
or all()
してせずにif文にぶち込んでいること
要するにライブラリーのバージョン固有のバグでした。
解決法の説明【matplotlib v2.1.1以前】
コレを防ぐためには、 直前のseabornのplotでaxに保存されている 凡例表示用のPathCollection
と衝突を回避する必要がある 。
今回は同じカテゴリカル変数を利用した描画のため、PathCollection
の削除を選択した。
凡例表示用のPathCollection
は ax.collections (type:list) に保存されているため削除関数clearを利用する
ax.collections.clear()
clear後は、その直後のseabornの関数でhue(とpalette)を指定して、グラフを重ねようとした場合にエラー落ちしなくなるようだ。
全部コードを読んだわけじゃないので推測も混じってます。
何か見識あればコメントでお願いします。
#【matplotlib v2.1.2以降】のお話(追記)
ax = sns.pointplot(x="x", y="fitting_y", hue="y_limit", data=result, markers="")
sns.pointplot(x="x", y="y", hue="y_limit", data=result, join=False)
と連続して記述してもエラー落ちはしなくなりました。
ごっそり重複チェックの部分のコードが消去されたためです(_in_handles
関数)。
しかし、凡例は壊れますので、groupby
を利用した正攻法を利用したほうが良いでしょう。
※一応、ax.collection.clear()を挟むことで解決することができます。しかし、今後安定した動作であるかの保証は無いです。詳細はコメント欄にて。
###壊れる凡例の例
_in_handles
関数が消去でなくエラー修正であったらならの妄想
ちなみにmatplotlib v2.1.1
のlegend.py
内にある_in_handles
関数をおいてany()
関数を利用してエラー部分を修正していたとしたら、PathCollection
の指定時にax.collections.clear()
を使用せずとも、連続で書かれたseaborn.pointplot
にて、壊れずに凡例の表示ができそうであった。
その他雑記(alpha値の扱いとか)
ax.collections[0].get_facecolor() != ax.collections[1].get_facecolor()
が
np.repeat([[True,True,True, False]],19,axis=0)
とほぼ等しくなっていた原因は、get_facecolor()
関数とseaborn.pointplot
の色の管理の方法に問題があった。
get_facecolor()
ではalpha
値まで管理されているが、seaborn.pointplot
内の色の管理はseaborn.color_palette
で行われる。
そのため、palette=
の指定を行った場合は、必ずalpha
値が1
に統一されてしまう。
どういうことかというと、内部でseaborn.color_palette
に変換されるが、seaborn.color_palette
はRGB変換処理によりalpha
値の情報が削ぎ落とされてしまう。そのため、同じラベルに違う色を付けたところでalpha値の部分で常にFalseが出てしまう。
現在,seaborn.pointplot
ではalpha値を制御して描画する機能はついていないようだ。(たぶん)
ただしseaborn.regplot
などをみる限りalpha
値を関数描画時にscatter_kws
等の引数を用いて指定できるようになっているようだ。
seaborn.pointplot
もそのうち機能がつくかも……しれないこともないかもしれない。
解決法の見つけ方
探し方が悪かったのかどこにも載ってなかったので、
pycharmでbreakpoint debugしながら、いじいじしてた。
ソースコード
詳細なソースコードは以下のgistのURLに置いておきますので気になった方は参照ください。
seabornでhueを使いながら、複数のグラフを重ねたかった
その他の解法とか?
- hueを使わずgroup_byしてfor文で回して、legendを自作する(上記記載済み)
- jupter-notebook(web brower)では、一部エラーが出てもそのまま描画処理をしてくれるため、実用途に足るなら利用する