6
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Matplotlibのscatterで半径1のマーカーサイズの円を描く

Last updated at Posted at 2019-10-27

Pythonで使えるグラフ描写パッケージのMatplotlibには散布図を描写するためのmatplotlib.pyplot.scatterメソッドがある。各点のマーカーサイズを指定することができるがポイント単位での指定のため、グラフの軸の実スケールと対応させるようにする。数個の円を描くのであればmatplotlib.patches.Circleクラスでradiusを指定して使えばいいが、数百数千の円と軸のスケールを合わせたいときはscatterの方が使いやすいのでこのようなことをやっている。

方法

コードにコメントを入れつつ説明。

import matplotlib.pyplot as plt
import numpy as np

ppi=72 # points per inche 

# 枠の範囲指定
xmin,xmax=-2.0, 2.0
ymin,ymax=-2.0, 2.0

x,y=0.0, 0.0 # 中心座標
size=1.0     # 半径

# dpiは任意の値を設定できる。デフォルトは100
fig = plt.figure(figsize=(8,8), dpi=100.0)
ax  = fig.add_axes((0.1,  0.1, 0.8, 0.8))

ax.set_aspect("equal") # アスペクト比を等しくする
                       # 今は枠のサイズ設定で最初から等しいのであってもなくてもよい

# 描写範囲の長さを取得(dpi単位)
# x軸をベースに計算しているがy軸でも同じ。アスペクト比が違えばおかしくなる
ax_length=ax.bbox.get_points()[1][0]-ax.bbox.get_points()[0][0]

# dpi単位の長さをポイント単位に変換(dpiからインチ単位にし、インチからポイント単位に変換)
ax_point = ax_length*ppi/fig.dpi

# x軸の実スケールをポイント単位に変換
xsize=xmax-xmin
fact=ax_point/xsize

# scatterのマーカーサイズは直径のポイントの二乗を描くため、実スケールの半径をポイントに変換し直径にしておく
size*=2*fact

# 二乗にして与える 
ax.scatter(x,y,s=size**2, linewidths=0)

ax.set_xlim(xmin,xmax)
ax.set_ylim(ymin,ymax)

ax.set_xticks(np.linspace(ax.get_xlim()[0], ax.get_xlim()[1], 5))
ax.set_yticks(np.linspace(ax.get_ylim()[0], ax.get_ylim()[1], 5))

ax.grid(which='both', axis='both')

plt.show()

axesクラスを使わない場合は描写範囲の長さの取得はこのようにすればいい。

gca=fig.gca()
ax_length=gca.bbox.get_points()[1][0]-gca.bbox.get_points()[0][0]
ax_point = ax_length*ppi/fig.dpi

コメントでもらったが変換係数を決める部分は以下のようにしてもいい。座標とピクセルを対応付するために先にxlim,ylimを指定してあげる必要がある。

ax.set_xlim(xmin,xmax)
ax.set_ylim(ymin,ymax)

# 実スケールの座標[1,0]でのピクセルを取得し、長さ1に対するポイント数を決める。
tf = ax.transData.transform([1, 0]) * (ppi / fig.dpi)
fact = tf[0] - tf[1]

上記を実行するとこのような半径1の図ができる。

ポイントは

  • scatterのマーカーサイズはポイントサイズの二乗で指定される。
    • ボックスサイズの情報を取得するときは基本的にはインチやdpi単位なので頑張ってポイント(ppi)単位にする。
    • dpiはインチあたりのドット数でmatplotlib.pyplot.figureを呼び出すときに任意の値を指定できる。デフォルトは100
    • ppiはインチあたりのポイント数で72でmatplotlibでは固定。
  • 半径ではなく直径を与える。
  • アスペクト比は等しい必要がある。
    • 楕円などを描きたかったらmatplotlib.patches.Circleクラスを使う。
  • 後述しているがlinewidthsは0を指定したほうがいい。

scatterの引数に配列を与えると複数の任意の半径でマーカーが描ける。

マーカーの枠線の取り扱い

import matplotlib.pyplot as plt
import numpy as np

ppi=72 # points per inche

xmin,xmax=0.0, 2.0
ymin,ymax=0.4, 1.6

x=np.array( [0.5, 1.5] )
y=np.array( [1.0, 1.0] )

size=np.array([0.5, 0.5])  # radius

fig = plt.figure(figsize=(8,8), dpi=100.0)
ax  = fig.add_axes((0.1,  0.1, 0.8, 0.8))
ax.set_aspect("equal")

ax_length=ax.bbox.get_points()[1][0]-ax.bbox.get_points()[0][0]
ax_point = ax_length*ppi/fig.dpi
xsize=xmax-xmin
fact=ax_point/xsize
size*=2*fact

ax.scatter(x[0],y[0],s=size[0]**2, linewidths=0)
ax.scatter(x[1],y[1],s=size[1]**2, edgecolors="black", linewidths=(size[1]/10))

ax.set_xlim(xmin,xmax)
ax.set_ylim(ymin,ymax)

ax.set_xticks(np.linspace(ax.get_xlim()[0], ax.get_xlim()[1], 21))
ax.set_yticks(np.linspace(ax.get_ylim()[0], ax.get_ylim()[1], 13))

ax.grid(which='both', axis='both')

plt.show()

上記のコードよりlinewidthsを指定すると円のエッジを中心とした(二乗していない)ポイントサイズの太さのエッジラインを描くことができる。上記は(二乗していない)マーカーサイズ(ポイント単位)の1/10を指定しているのでエッジを中心に太さ0.1のエッジラインになっている。デフォルトではlinewidthNoneなので指定しないでいいと思ったが明示的に0をしていしないと若干ズレるような気がする。

マーカーの種類による振る舞い

import matplotlib.pyplot as plt
import numpy as np

ppi=72 # points per inche

xmin,xmax=0.0, 2.0
ymin,ymax=0.0, 2.0

x=np.array( [0.5, 0.5, 1.5, 1.5] )
y=np.array( [0.5, 1.5, 0.5, 1.5] )

size=np.array([0.5, 0.5, 0.5, 0.5])  # radius

fig = plt.figure(figsize=(8,8), dpi=100.0)
ax  = fig.add_axes((0.1,  0.1, 0.8, 0.8))

ax_length=ax.bbox.get_points()[1][0]-ax.bbox.get_points()[0][0]
ax_point = ax_length*ppi/fig.dpi 
xsize=xmax-xmin
fact=ax_point/xsize
size*=2*fact

ax.scatter(x[0],y[0],s=size[0]**2, marker='^')
ax.scatter(x[1],y[1],s=size[1]**2, marker='x')
ax.scatter(x[2],y[2],s=size[2]**2, marker='+')
ax.scatter(x[3],y[3],s=size[3]**2, marker='*')

ax.set_xlim(xmin,xmax)
ax.set_ylim(ymin,ymax)

ax.grid(which='both', axis='both')

plt.show()

適当に選んだマーカーでは半径0.5を指定するとこのようになる。

参考

6
6
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?