web-dev-qa-db-ja.com

スケールmatplotlib.pyplot.Axes.scatterはxスケールでマーカー化します

X/y軸上の点の数に基づいて、matplotlib.pyplot.Axes.scatterプロットのmarkersizeをスケーリングしたいと思います。

import matplotlib.pyplot as plt
import numpy as np

vmin = 1
vmax = 11

x = np.random.randint(vmin, vmax, 5)
y = np.random.randint(vmin, vmax, 5)

fig, ax = plt.subplots()
for v in np.arange(vmin, vmax):
    ax.axvline(v - 0.5)
    ax.axvline(v + 0.5)
    ax.axhline(v - 0.5)
    ax.axhline(v + 0.5)

ax.set_xlim(vmin - 0.5, vmax + 0.5)
ax.set_ylim(vmin - 0.5, vmax + 0.5)
ax.scatter(x, y)

ax.set_aspect(1)
plt.show()

axは常に等しいアスペクト比を使用しており、両方の軸のlim値は同じです。

現在、上記を実行すると、次のプロットが生成されます... enter image description here

...そしてvmax = 41の値を変更する enter image description here

両方のプロットのmarkersizeはデフォルトのままです。つまり、markersize=6です。

私の質問は、markersizesが各セルの端に接触するようにmarker値を計算するにはどうすればよいですか? (各セルには最大1つのデータポイントがあります。)

8
fsimkovic

サークルの使用

簡単なオプションは、散乱を半径0.5のPatchCollectionで構成されるCirclesに置き換えることです。

circles = [plt.Circle((xi,yi), radius=0.5, linewidth=0) for xi,yi in Zip(x,y)]
c = matplotlib.collections.PatchCollection(circles)
ax.add_collection(c)

enter image description here

データ単位のサイズのマーカーで散布図を使用する

別の方法として、散布図が必要な場合は、マーカーサイズを更新してデータ単位にすることもできます。

ここでの簡単な解決策は、最初に図を1回描画し、次に軸のサイズを取得して、そこからポイントでマーカーサイズを計算することです。

import matplotlib.pyplot as plt
import numpy as np

vmin = 1
vmax = 11

x = np.random.randint(vmin, vmax, 5)
y = np.random.randint(vmin, vmax, 5)

fig, ax = plt.subplots(dpi=141)
for v in np.arange(vmin, vmax):
    ax.axvline(v - 0.5)
    ax.axvline(v + 0.5)
    ax.axhline(v - 0.5)
    ax.axhline(v + 0.5)

ax.set_xlim(vmin - 0.5, vmax + 0.5)
ax.set_ylim(vmin - 0.5, vmax + 0.5)

ax.set_aspect(1)
fig.canvas.draw()
s = ((ax.get_window_extent().width  / (vmax-vmin+1.) * 72./fig.dpi) ** 2)

ax.scatter(x, y, s = s, linewidth=0)

plt.show()

散乱のマーカー化がどのように使用されるかについての背景については、例えば、 この答え 。上記の解決策の欠点は、マーカーのサイズをプロットのサイズと状態に固定することです。軸の制限が変更されたり、プロットがズームされたりした場合、散布図のサイズが再び間違ってしまいます。

したがって、次のソリューションはより一般的です。これは少し複雑で、 データ単位で幅のある線をプロットする と同様に機能します。

import matplotlib.pyplot as plt
import numpy as np

vmin = 1
vmax = 32

x = np.random.randint(vmin, vmax, 5)
y = np.random.randint(vmin, vmax, 5)

fig, ax = plt.subplots()
for v in np.arange(vmin, vmax):
    ax.axvline(v - 0.5)
    ax.axvline(v + 0.5)
    ax.axhline(v - 0.5)
    ax.axhline(v + 0.5)

ax.set_xlim(vmin - 0.5, vmax + 0.5)
ax.set_ylim(vmin - 0.5, vmax + 0.5)

class scatter():
    def __init__(self,x,y,ax,size=1,**kwargs):
        self.n = len(x)
        self.ax = ax
        self.ax.figure.canvas.draw()
        self.size_data=size
        self.size = size
        self.sc = ax.scatter(x,y,s=self.size,**kwargs)
        self._resize()
        self.cid = ax.figure.canvas.mpl_connect('draw_event', self._resize)

    def _resize(self,event=None):
        ppd=72./self.ax.figure.dpi
        trans = self.ax.transData.transform
        s =  ((trans((1,self.size_data))-trans((0,0)))*ppd)[1]
        if s != self.size:
            self.sc.set_sizes(s**2*np.ones(self.n))
            self.size = s
            self._redraw_later()

    def _redraw_later(self):
        self.timer = self.ax.figure.canvas.new_timer(interval=10)
        self.timer.single_shot = True
        self.timer.add_callback(lambda : self.ax.figure.canvas.draw_idle())
        self.timer.start()


sc = scatter(x,y,ax, linewidth=0)

ax.set_aspect(1)
plt.show()

この問題 のため、タイマーを使用してキャンバスを再描画するようにコードを更新しました)