はじめに
ヒートマップの任意の場所に斜線を描画します。Matplotlibライブラリを利用したヒートマップの作図については「pcolorによるヒートマップの作成 - からっぽのしょこ」を参照してください。
斜線入りのヒートマップの作成
matplotlib
ライブラリのAxes.pcolor()
を用いて、任意の場所に斜線を入れたヒートマップを作図します。
利用するライブラリを読み込みます。
# 利用するモジュール import matplotlib.pyplot as plt from matplotlib.animation import FuncAnimation import numpy as np
まずは、行数R
と列数C
を指定して、作図に利用する配列x
を作成します。
# 行数と列数を指定 R = 5 C = 7 # 配列を作成 x = np.arange(R * C).reshape([R, C]) print(x) print(x.shape)
[[ 0 1 2 3 4 5 6]
[ 7 8 9 10 11 12 13]
[14 15 16 17 18 19 20]
[21 22 23 24 25 26 27]
[28 29 30 31 32 33 34]]
(5, 7)
この例では、グラデーションが分かりやすいように、値が1
ずつ大きくなる2次元配列を作成します。
R
とC
も作図に使います。
Axes.pcolor()
のhatch
引数を指定すると斜線を入れられます。
# ヒートマップを作成 fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(8, 6)) # 図の設定 ax.pcolor(x, hatch='x', edgecolor='blue') # 斜線入りヒートマップ ax.set_xticks(np.arange(C) + 0.5) # x軸目盛の位置 ax.set_xticklabels(np.arange(C)) # x軸目盛のラベル ax.set_yticks(np.arange(R) + 0.5) # y軸目盛の位置 ax.set_yticklabels(np.arange(R)) # y軸目盛のラベル ax.invert_yaxis() # y軸を反転 ax.set_aspect('equal', adjustable='box') # アスペクト比 plt.show() # 図を表示
ここでの目的は、一部のタイルのみに斜線を入れることです。
・配列のマスキング
斜線の描画用に、任意の要素をマスクした配列を作成します。
表示しない要素をマスクした配列x_mask
を作成します。
# 表示するインデックスを指定 r = 2 c = 2 # 元の配列と同じ形状の配列を作成 x_mask = np.zeros_like(x) # マスクしない要素を指定 x_mask[r, c] = 1 #x_mask[r, :] = 1 #x_mask[:, c] = 1 # 指定した要素以外をマスク x_mask = np.ma.masked_where(x_mask != 1, x) print(x_mask)
[[-- -- -- -- -- -- --] [-- -- -- -- -- -- --] [-- -- 16 -- -- -- --] [-- -- -- -- -- -- --] [-- -- -- -- -- -- --]]
r
行c
列目の要素以外がマスクされました。
マスクについては、NumPyの公式ドキュメント「The numpy.ma module — NumPy v1.23 Manual」を参照してください。masked_***()
というマスク用の関数は他にも実装されています。用途に応じて使い分けてください。
マスクされていない要素のみ描画されます。
# ヒートマップを作成 fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(8, 6)) # 図の設定 ax.pcolor(x_mask) # ヒートマップ ax.set_xticks(np.arange(C) + 0.5) # x軸目盛の位置 ax.set_xticklabels(np.arange(C)) # x軸目盛のラベル ax.set_yticks(np.arange(R) + 0.5) # y軸目盛の位置 ax.set_yticklabels(np.arange(R)) # y軸目盛のラベル ax.invert_yaxis() # y軸を反転 ax.set_aspect('equal', adjustable='box') # アスペクト比 plt.show() # 図を表示
・斜線入りヒートマップの作図
任意の場所に斜線を描画します。
元の配列x
とhatch用の配列x_mask
のヒートマップを重ねて作図します。
# ヒートマップを作成 fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(8, 6)) # 図の設定 ax.pcolor(x) # ヒートマップ ax.pcolor(x_mask, hatch='x', edgecolor='blue', linewidth=2) # 斜線入りヒートマップ ax.set_xticks(np.arange(C) + 0.5) # x軸目盛の位置 ax.set_xticklabels(np.arange(C)) # x軸目盛のラベル ax.set_yticks(np.arange(R) + 0.5) # y軸目盛の位置 ax.set_yticklabels(np.arange(R)) # y軸目盛のラベル ax.invert_yaxis() # y軸を反転 ax.set_aspect('equal', adjustable='box') # アスペクト比 plt.show() # 図を表示
x_mask
のヒートマップに関して、x_mask
の最小値と最大値から色付けが決まるため、x
とx_mask
で色が対応していません。
そこで、x_mask
のグラデーションの最小値と最大値をx
に対応させます。
# ヒートマップを作成 fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(8, 6)) # 図の設定 ax.pcolor(x) # ヒートマップ ax.pcolor(x_mask, hatch='x', edgecolor='blue', linewidth=2, vmin=np.min(x), vmax=np.max(x)) # 斜線入りヒートマップ ax.set_xticks(np.arange(C) + 0.5) # x軸目盛の位置 ax.set_xticklabels(np.arange(C)) # x軸目盛のラベル ax.set_yticks(np.arange(R) + 0.5) # y軸目盛の位置 ax.set_yticklabels(np.arange(R)) # y軸目盛のラベル ax.invert_yaxis() # y軸を反転 ax.set_aspect('equal', adjustable='box') # アスペクト比 plt.show() # 図を表示
vmin, vmax
引数でグラデーションの最小値と最大値を設定します。
以上で任意の場所に斜線を入れられました。
続いて、条件に応じて斜線を入れます。
値をランダムに生成して、指定した値より大きい場合だけマスクします。
# 値をランダムに生成 x = np.random.rand(R, C) print(np.round(x, 1)) # 0.5より大きい要素をマスク x_mask = np.ma.masked_where(x > 0.5, x) print(np.round(x_mask, 1))
[[0.5 0.1 0.9 0.7 0.4 0.2 0.1]
[0.3 0.5 1. 0.6 0.5 0.7 0.8]
[0.7 0.2 0.1 0.4 0.5 0.4 0. ]
[0. 0.7 0.9 0.1 0.2 0.8 0.1]
[0.2 0.9 0.5 0.4 0.7 0.5 0.1]]
[[0.5 0.1 -- -- 0.4 0.2 0.1]
[0.3 -- -- -- -- -- --]
[-- 0.2 0.1 0.4 -- 0.4 0.0]
[0.0 -- -- 0.1 0.2 -- 0.1]
[0.2 -- 0.5 0.4 -- 0.5 0.1]]
0
から1
の一様乱数に従い配列x
の値を生成します。
0.5
より大きい要素をマスクした配列x_mask
を作成します。
マスクしていない要素(0.5
以下の要素)に斜線を入れます。
# ヒートマップを作成 fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(9, 6)) # 図の設定 ax.pcolor(x) # ヒートマップ c = ax.pcolor(x_mask, hatch='x', edgecolor='blue', vmin=np.min(x), vmax=np.max(x)) # 斜線入りヒートマップ ax.set_xticks(np.arange(C) + 0.5) # x軸目盛の位置 ax.set_xticklabels(np.arange(C)) # x軸目盛のラベル ax.set_yticks(np.arange(R) + 0.5) # y軸目盛の位置 ax.set_yticklabels(np.arange(R)) # y軸目盛のラベル ax.invert_yaxis() # y軸を反転 ax.set_aspect('equal', adjustable='box') # アスペクト比 fig.colorbar(c, ax=ax) # カラーバー plt.show() # 図を表示
マスクしていない要素に斜線が入ります。つまり、np.ma.masked_where()
の条件を満たす要素がそのまま表示されます。
・hatchの設定
hatch
引数に指定できる記号と模様を確認します。
全てのパターンを描画してみます。
・作図コード(クリックで展開)
# hatch用の記号を設定 pattern_lt = ['/', '\\', '|', '-', '+', 'x', 'o', 'O', '.', '*'] # 行数と列数を指定 R, C = 5, 7 # 配列を作成 a = np.ones((R, C)) # ヒートマップを作成 fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(10, 4)) # 図の設定 #ax.pcolor(x, cmap='Blues') # ヒートマップ # 記号ごとに斜線を描画 for i in range(len(pattern_lt)): # マスクした配列を作成 a_mask = np.zeros_like(a) a_mask[i // C, i % C] = 1 a_mask = np.ma.masked_where(a_mask != 1, a) # 斜線を描画 ax.pcolor(a_mask, cmap='Blues', hatch=pattern_lt[i], edgecolor='turquoise') # 斜線入りヒートマップ ax.set_xticks(np.arange(C) + 0.5) # x軸目盛の位置 ax.set_xticklabels(np.arange(C)) # x軸目盛のラベル ax.set_yticks(np.arange(R) + 0.5) # y軸目盛の位置 ax.set_yticklabels(np.arange(R)) # y軸目盛のラベル ax.set_title('hatch=' + str(pattern_lt)) # タイトル ax.invert_yaxis() # y軸を反転 ax.set_aspect('equal', adjustable='box') # アスペクト比 plt.show() # 図を表示
\
が2つになっているのはエスケープキーのためです。
hatch
に指定できる記号(模様)については、Matplotlibの公式ドキュメント「matplotlib.collections — Matplotlib 3.6.0 documentation」を参照してください。
記号の数を増やすと、描画される斜線等の密度が高くなります。
・アニメーションの作成
斜線入りのタイルの位置を変更するアニメーションを作成します。
斜線入りのタイルを1つずつ移動します。
・作図コード(クリックで展開)
# 行数と列数を指定 R, C = 5, 7 # 配列を作成 x = np.arange(R * C).reshape([R, C]) # hatch用の記号を設定 pattern_lt = ['/', '\\', '|', '-', '+', 'x', 'o', 'O', '.', '*'] # 図の設定 fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(8, 6)) # 作図処理を関数として定義 def update(i): # 前フレームのグラフを初期化 plt.cla() # i番目以外の要素をマスクした配列を作成 x_mask = np.zeros_like(x) x_mask[i // C, i % C] = 1 x_mask = np.ma.masked_where(x_mask != 1, x) # i回目のヒートマップを作成 ax.pcolor(x) # ヒートマップ ax.pcolor(x_mask, hatch=pattern_lt[i % len(pattern_lt)], edgecolor='blue', linewidth=2, vmin=np.min(x), vmax=np.max(x)) # 斜線入りヒートマップ ax.set_xticks(np.arange(C) + 0.5) # x軸目盛の位置 ax.set_xticklabels(np.arange(C)) # x軸目盛のラベル ax.set_yticks(np.arange(R) + 0.5) # y軸目盛の位置 ax.set_yticklabels(np.arange(R)) # y軸目盛のラベル ax.set_title('i=' + str(i) + ', hatch=' + pattern_lt[i % len(pattern_lt)]) # タイトル ax.invert_yaxis() # y軸を反転 ax.set_aspect('equal', adjustable='box') # アスペクト比 # gif画像を作成 hatch_anime = FuncAnimation(fig, update, frames=R * C, interval=200) # gif画像を保存 hatch_anime.save('hatch.gif')
pattern_lt[i % len(pattern_lt)]
の部分は普通に記号を1つ指定してください。
斜線を入れる条件を0.1
ずつ大きくします。
・作図コード(クリックで展開)
# 行数と列数を指定 R, C = 5, 7 # 値をランダムに生成 x = np.random.rand(R * C).reshape([R, C]) # 図の設定 fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(9, 6)) # フラグをFalseに設定 cbar_flg = False # 作図処理を関数として定義 def update(i): global cbar_flg # 前フレームのグラフを初期化 plt.cla() # 0.i以上の要素をマスクした配列を作成 x_mask = np.ma.masked_where(x >= i * 0.1, x) # i回目のヒートマップを作成 c = ax.pcolor(x) # ヒートマップ ax.pcolor(x_mask, hatch='x', edgecolor='blue', vmin=np.min(x), vmax=np.max(x)) # 斜線入りヒートマップ ax.set_xticks(np.arange(C) + 0.5) # x軸目盛の位置 ax.set_xticklabels(np.arange(C)) # x軸目盛のラベル ax.set_yticks(np.arange(R) + 0.5) # y軸目盛の位置 ax.set_yticklabels(np.arange(R)) # y軸目盛のラベル ax.set_title('i=' + str(i) + ', x<' + str(np.round(i * 0.1, 1))) # タイトル ax.invert_yaxis() # y軸を反転 ax.set_aspect('equal', adjustable='box') # アスペクト比 # 初回のみカラーバーを実行 if not cbar_flg: cbar_flg = True # フラグをTrueに変更 fig.colorbar(c, ax=ax) # カラーバー # gif画像を作成 hatch_anime = FuncAnimation(fig, update, frames=11, interval=500) # gif画像を保存 hatch_anime.save('random.gif')
カラーバーは初期化されないようなので(?)、フレームの回数分表示されてしまいます。
そこで、cbar_flg
を切り替えて初回のみfig.colorbar()
を実行します。gloval
って何?これがないとカラーバーが2つ表示されるのはなぜ?。
おわりに
思いの外苦労したので、メモしておきます。
ところで、hatchってなんて訳すの?斜線?陰影?個人的には網掛けと呼びたいんだけど、meshとどう違うんだ。
先日公開されたカバーが最高なのでみんな聴いて。