注意
【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)では、一部エラーが出てもそのまま描画処理をしてくれるため、実用途に足るなら利用する


