morikomorou’s blog

自分が学んだことなどの備忘録的なやつ

【python】matplotlibの棒グラフや散布図の色を部分的に変える方法

はじめに

棒グラフや散布図を書くときに、注目したい要素のみ部分的に色を変更したいときありませんか?

今回はmatplotlibを使って上図のように棒グラフや散布図の色を部分的に変える方法についてお伝えします。
また、それを応用して、クリックした部分だけ色を変える方法についても説明していきたいと思います。



要素ごとに色を変更する方法

色を要素ごとに変える方法はいくつかありますが、私がよく使うのは以下の2通りです。

  1. 全要素の色をはじめに指定してプロットする
  2. プロット後に要素を指定して色を変更する

それぞれの方法について説明していきます。

①全要素の色をはじめに指定してプロット

プロット時のcolorプロパティに色のリストを与えてあげることで要素ごとに色を変えられます。
散布図の実装例は以下です。
3番目の要素を赤に、それ以外はグレーにしております。

import matplotlib.pyplot as plt
import numpy as np

# 色を指定
c = ['gray' for _ in range(10)]
c[2] = 'red'     # 3番目の要素を赤に変更

x = [i for i in range(10)]
y = np.random.rand(10)

# プロット
fig, ax = plt.subplots()
ax.scatter(x, y,color=c)
plt.show()

出力は下図のようになります。

棒グラフも同様に実装可能です。

import matplotlib.pyplot as plt
import numpy as np

# 色を指定
c = ['gray' for _ in range(10)]
c[2] = 'red'     # 3番目の要素を赤に変更

x = [i for i in range(10)]
y = np.random.rand(10)

# プロット
fig, ax = plt.subplots()
ax.bar(x, y,color=c)
plt.show()

出力は下図のようになります。

②プロット後に要素を指定して色を変更する

こちらはプロット後に色を変えたい要素を抜き出し、set_color()メソッドで色を変える方法です。
棒グラフの場合はこちらの方法のほうが簡単なのでいいと思います。
実装例は以下です。

import matplotlib.pyplot as plt
import numpy as np

c = 'gray'
x = [i for i in range(10)]
y = np.random.rand(10)

# プロット
fig, ax = plt.subplots()
rect = ax.bar(x, y,color=c)
rect[2].set_color('red')    # 3番目の要素を赤に変更
plt.show()

結果は同じなので割愛します。
ちなみに散布図(plt.scatter)だとこの方法はとれません。
なぜ、棒グラフでは可能なのかといいますと、棒グラフの出力クラスと散布図の出力クラス構造が異なることに起因します。
棒グラフの出力クラスを見てみましょう。

print(rect)

# output
# <BarContainer object of 10 artists>

plt.bar()で帰ってくるのはBarContainerというオブジェクトです。
これを公式ドキュメントで見てみますと以下のことが書いてあります。

Bases: matplotlib.container.Container
Container for the artists of bar plots (e.g. created by Axes.bar).
The container can be treated as a tuple of the patches themselves.

Conteinerというクラスを継承しており、タプル構造のクラスになっているようです。
つまり、BarContainerは四角形のパッチがタプルになった構造をとっていることを意味しています。
インデックスを指定するとそのインデックス番号の四角形パッチが得られるわけです。
実際に指定して結果を見てみましょう。

print(rect[0])

# output
# Rectangle(xy=(-0.4, 0), width=0.8, height=0.426805, angle=0)

Rectangleクラスが返ってきました。
Rectangleクラスはset_color()メソッドを持っているので、色を指定すればそのインデックスの要素のみ色を変えることができます。
散布図の場合も見てみましょう。

sc = ax.scatter(x, y)
print(sc[0])

# output
# TypeError: 'PathCollection' object is not subscriptable

エラーが返ってきました。
散布図の場合はplt.scatterの返り値はPathCollectionというクラスになっています。
こちらの場合は公式ドキュメントいろいろ見てみましたが、どうしていいのかさっぱりわかりませんでした。
何かわかったら記事書こうと思います。とりあえず散布図は①の方法で何とかやるしかないですね。。。

クリックした要素のみ色を変更する

今までさんざんクリックイベントを紹介知ってきたので、クリックで色を変えれるようにしましょう。
過去の記事で紹介したように、ピックイベントとfigureを紐づけます。
【python】クリックでmatplotlibのグラフの色や透明度を変える - morikomorou’s blog

散布図の色をクリックした要素のみ変える方法

前述のとおり、①の方法で実装します。マウスで選択した際にevent.indに選択した要素のインデックス番号が入ります。
さきに作っておいた色のリストを参照して、対応するインデックスの色を書き換えた後、最後にすべての点の色を色のリストで更新するという処理を行います。
コードは下記のとおりです。

import matplotlib.pyplot as plt
import numpy as np

def onpick(event):
    global c
    gco = event.artist
    ind = event.ind[0]   # 要素が重なっていて複数選択してしまった場合インデックスの若いものを選択
    if c[ind] != 'gray':
        c[ind] = 'gray'
    else:
        c[ind] = 'red'
    gco.set_color(c)
    plt.draw()

c = ['gray' for _ in range(10)]
x = [i for i in range(10)]
y = np.random.rand(10)
fig, ax = plt.subplots()
ax.scatter(x, y, color=c, picker=10)
fig.canvas.mpl_connect('pick_event', onpick)
plt.show()

実行結果は以下です。

クリックするたびに灰色と赤を切り替えれるようにしています。

棒グラフの色をクリックした要素のみ変える方法

棒グラフの場合は、散布図と同様の方法ではできません。
散布図の場合は要素を選択した際にevent.indにインデックス番号が入りますが、棒グラフの場合はエラーになってしまいます。
ですので、②の方法を使います。
選択した要素はevent.articleにはいるので、3番目の要素をクリックしてその内容を見てみるとRectangleクラスが返ってきていることがわかります。
つまり、もうすでにBarContainer内の選択した要素がevent.artistに入ってきているのでインデックスで指定する必要がないということみたいです。

実装コードは以下になります。

import matplotlib.pyplot as plt
import numpy as np

def onpick(event):
    gco = event.artist
    c = gco.get_fc()
    if c != (0.5019607843137255, 0.5019607843137255, 0.5019607843137255, 1.0):
        gco.set_color('gray')
    else:
        gco.set_color('red')
    plt.draw()


x = [i for i in range(10)]
y = np.random.rand(10)
fig, ax = plt.subplots()
sc = ax.bar(x, y,color='gray', picker=10)
fig.canvas.mpl_connect('pick_event', onpick)
plt.show()

実行結果は以下。

Rectangleクラスの場合、色の取得はget_fc()メソッド(塗りつぶし色)、get_ec()メソッド(枠の色)で行えます。
今回はどちらも同じなのでget_fc()を使います。
get_fc()メソッドで返ってくるのがrgba(赤、緑、青、透明度)のタプルです。
めんどくさいので、grayのrgbaの値を事前にprintで出力しておいて分岐条件にコピペしてます。

終わりに

クリックして色を変えるのはあんまり用途ないかもですが、一部分だけ色を変えて目立たせるっていうのは発表資料等で使えるんじゃないでしょうか!?