web-dev-qa-db-ja.com

凡例のポイントに固定サイズを設定する

散布図をいくつか作成していますが、凡例のポイントのサイズを固定の等しい値に設定したいと思います。

今私はこれを持っています:

import matplotlib.pyplot as plt
import numpy as np

def Rand_data():
    return np.random.uniform(low=0., high=1., size=(100,))

# Generate data.
x1, y1 = [Rand_data() for i in range(2)]
x2, y2 = [Rand_data() for i in range(2)]


plt.figure()
plt.scatter(x1, y1, marker='o', label='first', s=20., c='b')
plt.scatter(x2, y2, marker='o', label='second', s=35., c='r')
# Plot legend.
plt.legend(loc="lower left", markerscale=2., scatterpoints=1, fontsize=10)
plt.show()

これはこれを生成します:

enter image description here

凡例のポイントのサイズはスケーリングされますが、同じではありません。 scatterプロットのサイズに影響を与えずに、凡例のポイントのサイズを同じ値に修正するにはどうすればよいですか?

29
Gabriel

matplotlibのソースコードを調べました。悪いニュースは、凡例に同じサイズのポイントを設定する簡単な方法がないように見えることです。散布図では特に困難です(間違っています:以下の更新を参照)。本質的に2つの選択肢があります。

  1. maplotlibコードを変更します
  2. 画像内のドットを表すPathCollectionオブジェクトに変換を追加します。変換(スケーリング)では、元のサイズを考慮する必要があります。

これらのどちらも非常に楽しいものではありませんが、#1の方が簡単なようです。 scatterプロットは、この点で特に困難です。

しかし、私はおそらくあなたが望むことをするハックを持っています:

import matplotlib.pyplot as plt
import numpy as np

def Rand_data():
    return np.random.uniform(low=0., high=1., size=(100,))

# Generate data.
x1, y1 = [Rand_data() for i in range(2)]
x2, y2 = [Rand_data() for i in range(2)]

plt.figure()
plt.plot(x1, y1, 'o', label='first', markersize=np.sqrt(20.), c='b')
plt.plot(x2, y2, 'o', label='second', markersize=np.sqrt(35.), c='r')
# Plot legend.
lgnd = plt.legend(loc="lower left", numpoints=1, fontsize=10)

#change the marker size manually for both lines
lgnd.legendHandles[0]._legmarker.set_markersize(6)
lgnd.legendHandles[1]._legmarker.set_markersize(6)
plt.show()

これは与える:

enter image description here

それはあなたが望んでいたもののようです。

変更:

  • scatterplotに変更され、マーカーのスケーリングが変更され(したがってsqrt)、マーカーサイズの変更を使用できなくなります(意図されている場合)
  • 凡例の両方のマーカーのマーカーサイズが手動で6ポイントに変更されました

ご覧のとおり、非表示のアンダースコアプロパティ(_legmarker)バグだらけです。 matplotlibの更新で破損する可能性があります。

更新

はぁ、見つけた。より良いハック:

import matplotlib.pyplot as plt
import numpy as np

def Rand_data():
    return np.random.uniform(low=0., high=1., size=(100,))

# Generate data.
x1, y1 = [Rand_data() for i in range(2)]
x2, y2 = [Rand_data() for i in range(2)]

plt.figure()
plt.scatter(x1, y1, marker='o', label='first', s=20., c='b')
plt.scatter(x2, y2, marker='o', label='second', s=35., c='r')
# Plot legend.
lgnd = plt.legend(loc="lower left", scatterpoints=1, fontsize=10)
lgnd.legendHandles[0]._sizes = [30]
lgnd.legendHandles[1]._sizes = [30]
plt.show()

_sizes(別のアンダースコアプロパティ)がトリックを行います。これはかなりのハックですが、ソースに触れる必要はありません。しかし、今ではscatterが提供するすべてを使用できます。

enter image description here

46
DrV

答えと同様に、すべてのマーカーを同じサイズにしたい場合:

lgnd = plt.legend(loc="lower left", scatterpoints=1, fontsize=10)
for handle in lgnd.legendHandles:
    handle.set_sizes([6.0])

MatPlotlib 2.0.0を使用

14
Bruno Morais

おそらく私のユースケースは独特ですが、@ DrVのソリューションを使用してもあまり成功しませんでした。ポイントの密度のため、最小のマーカーサイズ、つまりplt.plot(x, y, '.', ms=1, ...)を使用しており、凡例シンボルを大きくしたいです。

matplotlibフォーラム で見つけた推奨事項に従いました。

  1. データをプロットします(ラベルなし)
  2. レコード軸の制限(xlimits = plt.xlim()
  3. 凡例に適したシンボルの色とサイズを使用して、実際のデータから遠く離れた偽のデータをプロットする
  4. 軸の制限を復元します(plt.xlim(xlimits)
  5. 凡例を作成する

これがどのようになったかです(このため、ドットは実際には線ほど重要ではありません): enter image description here

これが他の誰かに役立つことを願っています。

7

ここに別の選択肢があります。これには、「プライベート」メソッドを使用しないという利点があり、凡例にあるスキャター以外のオブジェクトでも動作します。重要なのは、スキャターPathCollectionHandlerPathCollectionにマッピングし、それに更新関数を設定することです。

def update(handle, orig):
    handle.update_from(orig)
    handle.set_sizes([64])

plt.legend(handler_map={PathCollection : HandlerPathCollection(update_func=update)})

完全なコード例:

import matplotlib.pyplot as plt
import numpy as np; np.random.seed(42)
from matplotlib.collections import PathCollection
from matplotlib.legend_handler import HandlerPathCollection, HandlerLine2D

colors = ["limegreen", "crimson", "Indigo"]
markers = ["o", "s", r"$\clubsuit$"]
labels = ["ABC", "DEF", "XYZ"]
plt.plot(np.linspace(0,1,8), np.random.Rand(8), marker="o", markersize=22, label="A line")
for i,(c,m,l) in enumerate(Zip(colors,markers,labels)):
    plt.scatter(np.random.Rand(8),np.random.Rand(8), 
                c=c, marker=m, s=10+np.exp(i*2.9), label=l)

def updatescatter(handle, orig):
    handle.update_from(orig)
    handle.set_sizes([64])

def updateline(handle, orig):
    handle.update_from(orig)
    handle.set_markersize(8)

plt.legend(handler_map={PathCollection : HandlerPathCollection(update_func=updatescatter),
                        plt.Line2D : HandlerLine2D(update_func = updateline)})

plt.show()

enter image description here

選択したマーカーサイズが異なる場合を除き、選択したマーカーに似たLine2Dオブジェクトを作成し、それを使用して凡例を作成できます。これは、Axesにオブジェクトを配置する必要がなく(潜在的にサイズ変更イベントをトリガーする)、隠し属性を使用する必要がないため、素晴らしいです。唯一の本当の欠点は、オブジェクトとラベルのリストから明示的に凡例を作成する必要があることですが、これは十分に文書化されたmatplotlib機能であるため、使用してもかなり安全です。

from matplotlib.lines import Line2D
import matplotlib.pyplot as plt
import numpy as np

def Rand_data():
    return np.random.uniform(low=0., high=1., size=(100,))

# Generate data.
x1, y1 = [Rand_data() for i in range(2)]
x2, y2 = [Rand_data() for i in range(2)]

plt.figure()
plt.scatter(x1, y1, marker='o', label='first', s=20., c='b')
plt.scatter(x2, y2, marker='o', label='second', s=35., c='r')

# Create dummy Line2D objects for legend
h1 = Line2D([0], [0], marker='o', markersize=np.sqrt(20), color='b', linestyle='None')
h2 = Line2D([0], [0], marker='o', markersize=np.sqrt(20), color='r', linestyle='None')

# Set axes limits
plt.gca().set_xlim(-0.2, 1.2)
plt.gca().set_ylim(-0.2, 1.2)

# Plot legend.
plt.legend([h1, h2], ['first', 'second'], loc="lower left", markerscale=2,
           scatterpoints=1, fontsize=10)
plt.show()

resulting figure