からっぽのしょこ

読んだら書く!書いたら読む!同じ事は二度調べ(たく)ない

quiver関数の矢印色の設定:3次元の場合【Matplotlib】

はじめに

 Matplotlibライブラリを利用して、矢印プロットを作成します。

【前の内容】

www.anarchive-beta.com

【目次】

quiver関数の矢印色の設定:3次元の場合

 Matplotlibライブラリのquiver関数で矢印を描画できます。全回は、3次元の場合の矢のサイズ・形状に関する引数を確認しました。今回は、矢の色に関する引数を確認します。

 利用するライブラリを読み込みます。

# 利用ライブラリ
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation


矢印の作図

 まずは、3次元の場合の矢印の座標・長さに関する引数を簡単に確認します。
 詳しくは「矢印プロットの作図【ゼロつく1のノート(Python)】 - からっぽのしょこ」を参照してください。

 また、複数の矢を個別に色付けする際の注意点を確認します。

 座標と移動量を指定して、矢印を描画します。

# 座標・移動量を指定
x, y, z = 0.0, 1.0, 2.0
u, v, w = 3.0, 4.0, 5.0

# 線色を指定
c = 'orange'

# ラベル用の文字列を作成
str_c = 'None' if c == None else "'" + c + "'"

# 3Dベクトルを作図
fig, ax = plt.subplots(figsize=(6, 6), facecolor='white', 
                       subplot_kw={'projection': '3d'})
ax.scatter(x, y, z, s=100, label='(X, Y, Z)') # 始点
ax.scatter(x+u, y+v, z+w, s=100, label='(X+U, Y+V, Z+W)') # 終点
ax.quiver(x, y, z, u, v, w, 
          color=c, 
          label='({}, {}, {}, {}, {}, {})'.format(x, y, z, u, v, w)) # 矢印
ax.set_xticks(ticks=np.arange(np.floor(x-abs(u))-1, np.ceil(x+abs(u))+2))
ax.set_yticks(ticks=np.arange(np.floor(y-abs(v))-1, np.ceil(y+abs(v))+2))
ax.set_zticks(ticks=np.arange(np.floor(z-abs(w))-1, np.ceil(z+abs(w))+2))
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
ax.set_title('color='+str_c, loc='left', fontsize=10)
fig.suptitle('Axes3D.quiver(X, Y, Z, U, V, W)', fontsize=15)
ax.legend()
ax.set_aspect('equal')
plt.show()

quiver関数の座標と移動量の引数

 3次元ベクトルの場合は、第1・2・3引数に始点の座標 X, Y, Z、第4・5・6引数に移動量 U, V, W を指定します。終点の座標は X+U, Y+V, Z+W になります。
 length=1 (デフォルト)を指定すると、指定した座標の通りに描画されます。

 (ラベル用の文字列でごにょごにょやってるのは、文字列を表すためにクォーテーションマーク ' を前後に追加して、しかし None のときは追加しない拘りのためです。矢印の作図自体には不要な処理です。)

 複数の矢印に対して色付けします。

・作図コード(クリックで展開)

# 矢印の数を指定
n = 3

# 座標・移動量を指定
xs = np.linspace(start=0, stop=0, num=n)
ys = np.linspace(start=0, stop=3, num=n)
zs = np.arange(n)
us = np.linspace(start=3, stop=3, num=n)
vs = np.linspace(start=3, stop=3, num=n)
ws = np.linspace(start=3, stop=3, num=n)

# カラーマップを指定
cmap = 'tab10'

# グラデーション用の値を指定
cs = np.arange(n)

# RGBAデータに変換
cm = plt.colormaps.get_cmap(cmap)
CS = cm(cs)

# ラベル用の文字列を作成
str_cmap = 'None' if cmap == None else "'" + cmap + "'"

# グラフサイズを設定
x_min = np.floor(np.min([xs, xs+us]))
x_max =  np.ceil(np.max([xs, xs+us]))
y_min = np.floor(np.min([ys, ys+vs]))
y_max =  np.ceil(np.max([ys, ys+vs]))
z_min = np.floor(np.min([zs, zs+ws]))
z_max =  np.ceil(np.max([zs, zs+ws]))

# 3Dベクトルを作図
fig, ax = plt.subplots(figsize=(6, 6), facecolor='white', 
                       subplot_kw={'projection': '3d'})
ax.quiver(xs, ys, zs, us, vs, ws, color=CS) # 矢印
ax.set_xticks(ticks=np.arange(x_min, x_max+1))
ax.set_yticks(ticks=np.arange(y_min, y_max+1))
ax.set_zticks(ticks=np.arange(z_min, z_max+1))
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
ax.set_title('cmpa='+str_cmap, loc='left', fontsize=10)
fig.suptitle('Axes3D.quiver(color)', fontsize=15)
ax.set_aspect('equal')
plt.show()

意図しない配色の例

 矢印は、矢筒と矢頭の左右(上下?)の3本の線で構成されます。n本の矢印を描画する場合、「1番目の矢筒」の次に「2番目の矢筒」・・・「n番目の矢筒」と続き、その次に「1番目の矢頭の左右」「2番目の矢頭の左右」・・・「n番目の矢頭の左右」の順番に配色されます。そのため、3本の矢印に対して3つの色を指定すると意図しない配色になります。

 矢印の3倍の数の色を指定して、先ほどのコードで作図してみます。

・作図コード(クリックで展開)

# グラデーション用の値を指定
cs = np.arange(3 * n)

# RGBAデータに変換
CS = cm(cs)

複数データの場合のcolor引数の仕様

 全ての線が別の色になりました。

 では、矢印と同じ数の色を用意して、矢頭の分の色データを複製します。

・作図コード(クリックで展開)

# グラデーション用の値を指定
cs = np.arange(n)
print(cs)

# RGBAデータに変換
CS = cm(cs)
print(CS.round(2))
print(CS.shape)

# RGBAデータを複製
CS = np.vstack([CS, np.repeat(CS, repeats=2, axis=0)])
print(CS.round(2))
print(CS.shape)
[0 1 2]
[[0.12 0.47 0.71 1.  ]
 [1.   0.5  0.05 1.  ]
 [0.17 0.63 0.17 1.  ]]
(3, 4)
[[0.12 0.47 0.71 1.  ]
 [1.   0.5  0.05 1.  ]
 [0.17 0.63 0.17 1.  ]
 [0.12 0.47 0.71 1.  ]
 [0.12 0.47 0.71 1.  ]
 [1.   0.5  0.05 1.  ]
 [1.   0.5  0.05 1.  ]
 [0.17 0.63 0.17 1.  ]
 [0.17 0.63 0.17 1.  ]]
(9, 4)

 RGBAデータは、赤・緑・青・透過度の4列の配列です。
 矢印ごとのRGBAデータを np.repeat() で行(矢)ごとに複製して、元の配列と np.vstack() で結合します。

 先ほどコードで作図します。

意図した配色の例

 矢頭分の色データを複製することで、矢印ごとに配色できました。カラー名やカラーコードを指定する場合も同様に作図します。

色に関する引数

 次からは、矢印の色に関する引数を確認していきます。

cmap引数

 cmap 引数で矢の色のグラデーションを設定できます。

 引数の値を変更した矢印を並べて描画します。

・作図コード(クリックで展開)

# 矢印の数を指定
n = 20

# カラーマップを指定
cmap = 'viridis'

# x軸方向の移動量を指定
u = 3.0

# 座標・移動量を作成
os = np.repeat(0, repeats=n)
zs = np.arange(n)
us = np.repeat(u, repeats=n)

# グラデーション用の値を指定
cs = zs**2

# ラベル用の文字列を作成
str_cmap = 'None' if cmap == None else "'" + cmap + "'"

# 3Dベクトルを作図
fig, ax = plt.subplots(figsize=(8, 8), facecolor='white', 
                       subplot_kw={'projection': '3d'})
quiv = ax.quiver(os, os, zs, us, os, os, 
                 cmap=cmap, linewidth=2) # 矢印
quiv.set_array(np.hstack([cs, np.repeat(cs, 2)])) # グラデーション用の値
for i in range(n):
    ax.text(0, 0, i, s='color='+str(cs[i])+' ', 
            size=10, ha='right', va='center') # 引数ラベル
ax.set_xticks(ticks=np.arange(u+1))
ax.set_yticks(ticks=[0])
ax.set_zticks(ticks=np.arange(n))
ax.set_xlim(-4, np.ceil(u))
ax.set_ylim(-1, 1)
ax.set_zlim(-1, n)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
ax.set_title('cmap='+str_cmap, loc='left', fontsize=10)
fig.suptitle('Axes3D.quiver(cmap)', fontsize=15)
fig.colorbar(quiv)
ax.set_aspect('equal')
ax.view_init(elev=30, azim=300) # xz面
plt.show()

quiver関数のグラデーションの引数

 cmap 引数にカラーマップ名を指定して、矢印プロットのオブジェクトの set_array() メソッドにグラデーション用の値を指定します。カラーマップのデフォルトは viridis です。
 使用できるカラーマップについては「matplotlibドキュメント:カラーマップ 」を参照してください。

 あるいは、次の方法でも作図できます。

・作図コード(クリックで展開)

# RGBAデータに変換
cm = plt.colormaps.get_cmap(cmap)
CS = cm(cs)

# 3Dベクトルを作図
fig, ax = plt.subplots(figsize=(8, 8), facecolor='white', 
                       subplot_kw={'projection': '3d'})
quiv = ax.quiver(os, os, zs, us, os, os, 
                 color=np.vstack([CS, np.repeat(CS, 2, axis=0)]), linewidth=2) # 矢印
for i in range(n):
    ax.text(0, 0, i, s='color=' + str(cs[i]) + ' ', 
            size=10, ha='right', va='center') # 引数ラベル
ax.set_xticks(ticks=np.arange(u+1))
ax.set_yticks(ticks=[0])
ax.set_zticks(ticks=np.arange(n))
ax.set_xlim(-4, np.ceil(u))
ax.set_ylim(-1, 1)
ax.set_zlim(-1, n)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
ax.set_title('cmap='+str_cmap, loc='left', fontsize=10)
fig.suptitle('Axes3D.quiver(cmap)', fontsize=15)
ax.set_aspect('equal')
ax.view_init(elev=30, azim=300) # xz面
plt.show()

quiver関数のグラデーションの引数

 pyplot.colormaps.get_cmap() にカラーマップ名を指定して、カラーマップのオブジェクトを作成します。指定した値をカラーマップに応じたRGBAデータに変換します。
 矢印ごとに色データを直接指定しているのでカラーバーを表示できません。(データと連動していないカラーバーを表示して、目盛の範囲などを調整すれば疑似的に再現できそうですが、よく分かりませんでした。)

 引数の値を変更した矢印のアニメーションを作成します。

・作図コード(クリックで展開)

# フレーム数を指定
n = 101

# グラデーション用の値の範囲を指定
cs = np.linspace(start=-10, stop=10, num=n)

# カラーマップを指定
cmap = 'hsv'

# x軸方向の移動量を指定
u = 3.0

# 座標・移動量を作成
os = np.repeat(0, repeats=n)
us = np.repeat(u, repeats=n)

# ラベル用の文字列を作成
str_cmap = 'None' if cmap == None else "'" + cmap + "'"

# グラフオブジェクトを初期化
fig, ax = plt.subplots(figsize=(6, 4), facecolor='white', 
                       subplot_kw={'projection': '3d'})
fig.suptitle('Axes3D.quiver(cmap)', fontsize=15)

# カラーバー描画用のダミーを作成
tmp = ax.quiver(os, os, os, us, os, os, 
                cmap=cmap, linewidth=2) # 矢印
tmp.set_array(np.hstack([cs, np.repeat(cs, 2)])) # グラデーション用の値
fig.colorbar(tmp)

# 作図処理を関数として定義
def update(i):
    
    # 前フレームのグラフを初期化
    plt.cla()
    
    # 3Dベクトルを作図
    quiv = ax.quiver(os[i], os[i], os[i], us[i], os[i], os[i], 
                     cmap=cmap, clim=(cs.min(), cs.max())) # 矢印
    quiv.set_array(np.repeat(cs[i], repeats=3)) # グラデーション用の値
    ax.text(0, 0, 0, s='color={: .1f}'.format(cs[i])+' ', 
            size=10, ha='right', va='center') # 引数ラベル
    ax.set_xticks(ticks=np.arange(u+1))
    ax.set_yticks(ticks=[0])
    ax.set_zticks(ticks=[0])
    ax.set_xlim(-3, np.ceil(u))
    ax.set_ylim(-1, 1)
    ax.set_zlim(-1, 1)
    ax.set_xlabel('x')
    ax.set_ylabel('y')
    ax.set_zlabel('z')
    ax.set_title('cmap='+str_cmap, loc='left', fontsize=10)
    ax.set_aspect('equal')
    ax.view_init(elev=0, azim=270) # xz面

# gif画像を作成
ani = FuncAnimation(fig=fig, func=update, frames=n, interval=100)

# gif画像を保存
ani.save(filename='3d_quiver_cmap.gif')

 作図処理をupdate()として定義して、FuncAnimation()でgif画像を作成します。

quiver関数のグラデーションの引数


color引数

 color 引数または edgecolor 引数で矢印の色を設定できます。

 引数の値を変更した矢印を並べて描画します。

・作図コード(クリックで展開)

# 色を指定
cs = np.array(
    ['purple', 'green', 'pink', 'yellow', 'lightblue', 
    'red', 'lightgreen', 'white', '#F8C46E', '#DAC4AA']
)

# 矢印の数を設定
n = len(cs)

# x軸方向の移動量を指定
u = 3.0

# 座標・移動量を作成
os = np.repeat(0, repeats=n)
zs = np.arange(n)
us = np.repeat(u, repeats=n)

# 2Dベクトルを作図
fig, ax = plt.subplots(figsize=(6, 6), facecolor='white', 
                       subplot_kw={'projection': '3d'})
ax.quiver(os, os, zs, us, os, os, 
          edgecolor=np.hstack([cs, np.repeat(cs, 2)]), linewidth=2) # 矢印
for i in range(n):
    str_c = 'None' if cs[i] == None else "'" + cs[i] + "'"
    ax.text(0, 0, i, s='color='+str_c+' ', 
            size=10, ha='right', va='center') # 引数ラベル
ax.set_xticks(ticks=np.arange(u+1))
ax.set_yticks(ticks=[0])
ax.set_zticks(ticks=np.arange(n))
ax.set_xlim(-4, np.ceil(u))
ax.set_ylim(-1, 1)
ax.set_zlim(-1, n)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
fig.suptitle('Axes3D.quiver(color)', fontsize=15)
ax.set_aspect('equal')
ax.view_init(elev=30, azim=300) # xz面
plt.show()

quiver関数の矢の色の引数

 color 引数または edgecolor や省略形の ec 引数は、矢の線の色です。色名やカラーコード、RGB値を指定できます。デフォルトや None を指定した場合はMatplotlibで良く使われるデフォルト色( C0 )になります。

勾配の可視化

 最後は、矢印によって関数の勾配のグラフを作成します。
 詳しくは「矢印プロットの作図【ゼロつく1のノート(Python)】 - からっぽのしょこ」を参照してください。

 関数と勾配を計算して、シンプルにベクトル場を作成します。

・作図コード(クリックで展開)

# 1軸の矢印の数を指定
n = 21

# x・y軸の座標を指定
x = np.linspace(start=-10, stop=10, num=n)
y = np.linspace(start=-10, stop=10, num=n)

# 格子点を作成
X, Y = np.meshgrid(x, y)

# z軸の座標を計算
Z = X**2 + Y**2

# 勾配を計算
dX = 2 * X
dY = 2 * Y

# z軸方向の移動量を計算
W = np.sqrt(dX**2 + dY**2) + 1e-7

# カラーマップを指定
cmap = 'jet'

# RGBAデータに変換
cm = plt.colormaps.get_cmap(cmap)
C = cm(W.flatten())

# 3D勾配を作図
fig, ax = plt.subplots(figsize=(8, 8), facecolor='white', 
                       subplot_kw={'projection': '3d'})
ax.plot_wireframe(X, Y, Z, alpha=0.5, label='f(x, y)') # 関数
quiv = ax.quiver(X, Y, Z, -dX/W, -dY/W, -W, 
                 cmap=cmap, pivot='tail', length=0.5, arrow_length_ratio=0.2) # 勾配
quiv.set_array(np.hstack([W.flatten(), np.repeat(W.flatten(), 2)])) # グラデーション用の値
ax.set_xlim(X.min(), X.max())
ax.set_ylim(Y.min(), Y.max())
ax.set_zlim(Z.min(), Z.max()*1.1)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
ax.set_title('$z = x^2 + y^2$', loc='left', fontsize=10)
fig.suptitle('gradient', fontsize=15)
fig.colorbar(quiv)
#ax.view_init(elev=0, azim=300) # 表示角度
plt.show()

ベクトル場による勾配のグラフ

 この例では、2乗和の勾配を可視化します。
 各座標における勾配の大きさを矢印の色で表します。また、矢印と矢頭のサイズも対応します。

 グラフを回転したアニメーションで確認します。

・作図コード(クリックで展開)

# 水平方向の角度を作成
h_vals = np.arange(start=0, stop=360, step=5.0)

# フレーム数を設定
frame_num = len(h_vals)

# グラフオブジェクトを初期化
fig, ax = plt.subplots(figsize=(8, 8), facecolor='white', 
                       subplot_kw={'projection': '3d'})
fig.suptitle('gradient', fontsize=15)

# カラーバー表示用のダミー
tmp = ax.quiver(X, Y, Z, -dX/W, -dY/W, -W, 
                 cmap=cmap, pivot='tail', length=0.5, arrow_length_ratio=0.2) # 勾配
tmp.set_array(np.hstack([W.flatten(), np.repeat(W.flatten(), 2)])) # グラデーション用の値
#fig.colorbar(tmp)

# 作図処理を関数として定義
def update(j):
    
    # 前フレームのグラフを初期化
    plt.cla()
    
    # 3D勾配を作図
    #ax.plot_wireframe(X, Y, Z, alpha=0.5, label='f(x, y)') # 関数
    quiv = ax.quiver(X, Y, Z, -dX/W, -dY/W, -W, 
                     cmap=cmap, pivot='tail', length=0.5, arrow_length_ratio=0.2) # 勾配
    quiv.set_array(np.hstack([W.flatten(), np.repeat(W.flatten(), 2)])) # グラデーション用の値
    ax.set_xlim(X.min(), X.max())
    ax.set_ylim(Y.min(), Y.max())
    ax.set_zlim(Z.min(), Z.max()*1.1)
    ax.set_xlabel('x')
    ax.set_ylabel('y')
    ax.set_zlabel('z')
    ax.set_title('$z = x^2 + y^2$', loc='left', fontsize=10)
    ax.view_init(elev=20, azim=h_vals[j]) # 表示角度

# gif画像を作成
ani = FuncAnimation(fig=fig, func=update, frames=frame_num, interval=100)

# gif画像を保存
ani.save(filename='3d_quiver_gradient1.gif')

ベクトル場による勾配のグラフ


 続いて、矢頭のサイズを固定して作図します。

・作図コード(クリックで展開)

# 1軸の矢印の数を指定
n = 21

# x・y軸の座標を指定
x = np.linspace(start=-10, stop=10, num=n)
y = np.linspace(start=-10, stop=10, num=n)

# 格子点を作成
X, Y = np.meshgrid(x, y)
xs = X.flatten()
ys = Y.flatten()

# z軸の座標を計算
zs = xs**2 + ys**2

# 勾配を計算
dxs = 2 * xs
dys = 2 * ys

# z軸の座標を計算
ws = np.sqrt(dxs**2 + dys**2) + 1e-7

# 矢頭サイズを指定
hl = 5.0

# 矢頭サイズ比を作成
alrs = hl / np.linalg.norm(np.vstack([dxs/ws, dys/ws, ws]).T, axis=1)

# カラーマップを指定
cmap = 'jet'

# 3D勾配を作図
fig, ax = plt.subplots(figsize=(8, 8), facecolor='white', 
                       subplot_kw={'projection': '3d'})
ax.plot_wireframe(X, Y, zs.reshape(X.shape), alpha=0.5, label='f(x, y)') # 関数
for i in range(n**2):
    quiv = ax.quiver(xs[i], ys[i], zs[i], -dxs[i]/ws[i], -dys[i]/ws[i], -ws[i], 
                     cmap=cmap, clim=(ws.min(), ws.max()), 
                     pivot='tail', length=0.5, arrow_length_ratio=alrs[i]) # 勾配
    quiv.set_array(np.hstack([ws[i], np.repeat(ws[i], 2)])) # グラデーション用の値
ax.set_xlim(xs.min(), xs.max())
ax.set_ylim(ys.min(), ys.max())
ax.set_zlim(zs.min(), zs.max()*1.1)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
ax.set_title('$z = x^2 + y^2$', loc='left', fontsize=10)
fig.suptitle('gradient', fontsize=15)
fig.colorbar(quiv)
#ax.view_init(elev=0, azim=300) # 表示角度
plt.show()

ベクトル場による勾配のグラフ

 勾配に連動しないように矢頭のサイズを固定しています(が、矢印の角度(捻り具合?)によってサイズが違ってみえますね)。矢印の色と長さが勾配の大きさに対応します。
 矢印を1つずつ描画するので処理に時間がかかります(私のマシンだと...)。

 グラフを回転したアニメーションで確認します。

・作図コード(クリックで展開)

# 水平方向の角度を作成
h_vals = np.arange(start=0, stop=360, step=5.0)

# フレーム数を設定
frame_num = len(h_vals)

# グラフオブジェクトを初期化
fig, ax = plt.subplots(figsize=(8, 8), facecolor='white', 
                       subplot_kw={'projection': '3d'})
fig.suptitle('gradient', fontsize=15)

# カラーバー表示用のダミー
tmp = ax.quiver(xs, ys, zs, -dxs/ws, -dys/ws, -ws, 
                cmap=cmap, pivot='tail', length=1, arrow_length_ratio=0.3) # 勾配
tmp.set_array(np.hstack([ws, np.repeat(ws, 2)])) # グラデーション用の値
#fig.colorbar(tmp)

# 作図処理を関数として定義
def update(j):
    
    # 前フレームのグラフを初期化
    plt.cla()
    
    # 3D勾配を作図
    #ax.plot_wireframe(X, Y, zs.reshape(X.shape), alpha=0.5, label='f(x, y)') # 関数
    for i in range(n**2):
        quiv = ax.quiver(xs[i], ys[i], zs[i], -dxs[i]/ws[i], -dys[i]/ws[i], -ws[i], 
                         cmap=cmap, clim=(ws.min(), ws.max()), 
                         pivot='tail', length=0.5, arrow_length_ratio=alrs[i]) # 勾配
        quiv.set_array(np.hstack([ws[i], np.repeat(ws[i], 2)])) # グラデーション用の値
    ax.set_xlim(xs.min(), xs.max())
    ax.set_ylim(ys.min(), ys.max())
    ax.set_zlim(zs.min(), zs.max()*1.1)
    ax.set_xlabel('x')
    ax.set_ylabel('y')
    ax.set_zlabel('z')
    ax.set_title('$z = x^2 + y^2$', loc='left', fontsize=10)
    ax.view_init(elev=20, azim=h_vals[j]) # 表示角度

# gif画像を作成
ani = FuncAnimation(fig=fig, func=update, frames=frame_num, interval=100)

# gif画像を保存
ani.save(filename='3d_quiver_gradient2.gif')

ベクトル場による勾配のグラフ


 この記事では、矢印の色に関する引数を確認しました。

参考リンク

おわりに

 線を一本ずつ色付けしてくれなくていいのにと思うのですが、内部での処理の問題なのか、それとも何か良いことがあるのでしょうか。グラデーションの方でも、配列の渡し方だったり、vmin, vmax 引数ではなく clim 引数だったり、次に使う時には忘れているであろうことがいくつかあったのでメモとして書き残しておきます。
 勾配の図はおまけです。ここまでの記事同様に、利用例的なものを入れたかっただけです。