こんな人にオススメ
pythonで2次元配列をマップ化(値の大きさで色が変わる表的な)を作成したい!
どうすればうまくできる?
ということで、今回はmatplotlib.pyplot
、plt
のplt.imshow
を使用して2次元配列からマップを作成しようと思う。マップというのはイメージで言えば以下のようなもの。この図では値をピンク色で示すように情報を付加している。
このような図を用いることでただの2次元配列がパッと見でその値の違いや傾向を知ることができる。また、執筆者が研究している天文分野ではこのような2次元データ(画像)からマップを作成し、どのような画像なのかを視覚的に捉えることが多い。
そのため、今回のplt.imshow
はとても重要な役割を果たしている(もちろんplt
以外にもマップを作成する手法はあるが、一番メジャーだと思うので今回はplt
)。
今回は4行9列のデータを主に使用するのでまばらになるが、データ数を増やすと例えば以下のようにキレイなマップを作成することができる。以下のデータは0
から9999
までのデータを100×100の2次元配列にしてマップ化したもの。もちろん連続した数のデータでなくてもいい。
plt
にはplt.imshow
とする方法とax.imshow
とする方法の2種類があるが、今回は後者のax.imshow
の方を採用する。こちらの方がコードを書く量は増えるが汎用性が高い。慣れていない人はとりあえずコピペして書き直していただければ良いだろう。
python環境は以下。
- Python 3.9.4
- numpy 1.20.3
- matplotlib 3.4.2
import
とかの下準備と使用するデータ
まずは必要なimport
関連やデータの作成。
使用するモジュールのimport
import sys import matplotlib.pyplot as plt import mpl_toolkits.axes_grid1 import matplotlib as mpl import numpy as np sys.path.append('../../') import plt_layout_template plt.ion()
今回は基本的にはmatplotlib
。その中でカラーバーなどをいじるので色々とimport
。plt_layout_template
はplt
のテンプレートを入れたpyファイルで、2つ上のディレクトリにいるので../../
で階層を登ってimport
している。内容については以下参照。
-
-
【pltテンプレート】matplotlib.pyplotのグラフ作成テンプレート
続きを見る
また、plt.ion()
でipython
をインタラクティブモードにした。これでグラフを表示している間も処理を行うことができる。
使用するデータ
np.random.seed(seed=1) arr = np.random.randint(1, 100, 36).astype("float") # print(arr) # # [38. 13. 73. 10. 76. 6. 80. 65. 17. 2. 77. 72. 7. 26. 51. 21. 19. 85. # # 12. 29. 30. 15. 51. 69. 88. 88. 95. 97. 87. 14. 10. 8. 64. 62. 23. 58.] arr2d = arr.reshape(4, -1) # print(arr2d) # # [[38. 13. 73. 10. 76. 6. 80. 65. 17.] # # [ 2. 77. 72. 7. 26. 51. 21. 19. 85.] # # [12. 29. 30. 15. 51. 69. 88. 88. 95.] # # [97. 87. 14. 10. 8. 64. 62. 23. 58.]] arr2d[0][3] = np.nan arr2d[1][2] = np.nan arr2d[3][4] = np.nan # print(arr2d) # # [[38. 13. 73. nan 76. 6. 80. 65. 17.] # # [ 2. 77. nan 7. 26. 51. 21. 19. 85.] # # [12. 29. 30. 15. 51. 69. 88. 88. 95.] # # [97. 87. 14. 10. nan 64. 62. 23. 58.]] # print(arr2d.shape) # # (4, 9)
今回使用するデータは乱数の4行9列の合計36データ。乱数はnp.random.seed(seed=1)
で1番の乱数に固定。そのうちの3データをNaN
に変更して、非数として扱うこととした。
非数を使用することでマップでのNaN
の挙動を確認する。
共通して使用する自作関数
def text(array: np.ndarray, ax, color: str): """dataの値をその配列のインデックスの位置で示す Parameters ---------- array : np.ndarray テキストとして示す値の配列 ax : matplotlib.axes._subplots.AxesSubplot 値を追加したい2次元マップのaxes color : str 追加したい値の文字の色 """ for num_r, row in enumerate(array): for num_c, value in enumerate(array[num_r]): ax.text(num_c, num_r, value, color=color, ha='center', va='center')
ここで、以降で共通して使用する関数について紹介・解説する。自作関数の名前はtext
関数で、役割としてはマップ上にその点の値を載せるというもの。
一番初めのマップ上にあったピンク色の文字はこの関数から作成したもの。入力した配列array
を行→列の順番でループさせ、インデックスの位置でarray
の値を出力する。enumerateについては以下参照。
-
-
【python&初級】のlistとかforとかifとかまとめ
続きを見る
色はcolor
変数で変更可能でdeeppink
を使用。また、デフォルトでは記載されるテキストが指定した位置の中心に来ないので、center
で横位置と縦位置をどちらも中心を基準にしている。
2次元配列をマップ化
ということでシンプルにマップを作成する。このマップが一番初めのマップを作成するためのコードだ。
全体コード
各部分の解説
fig, ax = plt.subplots()
fig
でプロット情報を、ax
で軸情報を作成している。
ax.tick_params(which='both', direction='out')
主目盛、副目盛の向きをどちらも外向きに設定。plt
のテンプレートでは内向きにしているので、このままではマップ目盛がかぶるので調整。
ax.set_title('imshow') ax.set_xlabel('x') ax.set_ylabel('y')
マップのタイトルと横・縦軸に書く言葉。
im = ax.imshow(data) fig.colorbar(im, label='colorbar')
マップとカラーバーの作成。カラーバーとは右にある棒のこと。どの値がどの色を示すのかを表す。
text(data, ax, 'deeppink')
先ほど紹介した自作関数。array
はdata
でどの軸にテキストを付加するかは今の軸であるax
。そして色は見やすいdeeppink
。
fig.savefig('imshow.png')
画像を保存。拡張子は今回は.png
とした。拡張子はつけなくてもデフォルトならpng
になる。デフォルトの拡張子は
plt.rcParams['savefig.format'] = '(拡張子の名前。pdfとか)'
で変更することができる。
ちなみに10,000データ使用したマップは以下。
マップの配色を変更
基本的なマップの作成方法がわかったので、次はマップの色を変更する。先程のマップでは紫から緑、そして黄色へと色が変わったが、この色を変更することができる。
全体コード
各部分の解説
基本的には先程のマップと同じだが、ax.imshow
の部分に色を示すcmap
引数を追加した。map_color
引数でマップの色の具合を文字で指定する。マップの色についてはmatplotlib
の「Choosing Colormaps in Matplotlib」という項目に記載されている。
im = ax.imshow(data, cmap=map_color)
マップの色の具合は多くあるが、今回はその中から7つをピックアップしてforで回しながら指定した。
color_lst = ['gray', 'jet', 'hsv', 'Pastel1', 'plasma', 'cool', 'bwr'] for color in color_lst: img_show_c( map_color=color )
カラーバーの最小・最大値の変更とNaN
の可視化
マップの作成と色の変更を行なった。同じ色関係で、色の最小値と最大値を変更するというものがある。そもそもカラーバーの最小値以下、最大値以上の値は全て同じ色で表現される。
ということは、カラーバーの最小・最大値を変更することで色合いを変更することができる。使用用途としては、極端に大きな(小さな)値を無視できるようにするなどである。
また、非数であるNaN
を別の色にすることで目立たせることもできる。これまではNaN
は白色で表されたが、これの色も変更可能。
全体コード
なお、current_cmap = mpl.cm.get_cmap(map_color) # .copy()
についてだが、どうやらこの方式は非推奨らしい。言われる通りcopy
をつけると警告は消えるが、その代わりにNaN
の色付けがなくなる。
# current_cmap = cm.get_cmap(map_color)にすると、以下の警告 # You are modifying the state of a globally registered colormap. This has been deprecated since 3.3 and in 3.6, you will not be able to modify a registered colormap in-place. To remove this warning, you can make a copy of the colormap first. cmap = mpl.cm.get_cmap("gray").copy() # current_cmap = mpl.cm.get_cmap(map_color).copy()にすると警告は消えるが # NaNの色付けがされなくなる
各部分の解説
既出の部分は省く。まずはカラーバーの最小・最大値を「(最小値)to(最大値)
」となるように文字列として加工。map(str, (vmin, vmax))
でtuple
にしたvmin
とvmax
をどちらもstrにしている。その後join
でstr
にしたvmin
, vmax
をto
で結合している。
# 「vmintovmax」となる文字列を作成 suffix = 'to'.join(map(str, (vmin, vmax)))
非数NaN
の色を変更するため、まずは現在のマップの色を取得。その後、非数(bad
)をnan_color
とした。二つ目の引数の1.
は透明度を示しており、1.
は薄くしないということ。nan_color
は紫系列のmediumslateblue
。
# 現在のマップの色を取得 current_cmap = mpl.cm.get_cmap(map_color) # .copy() # NaNの色をnan_colorに変更。1.は透明度 current_cmap.set_bad(nan_color, 1.)
origin
は(0, 0)
の座標をどこに置くかでlower
は左下。注意点は配列をprint
した時は左上が基準となること。この違いをわかっていないと配列の上下を見誤る。
カラーバーの最小値はvmin
、最大値はvmax
で指定する。
# マップの配色はgray、基準位置は左下、最小値はvmin、最大値はvmax im = ax.imshow(data, cmap=map_color, origin='lower', vmin=vmin, vmax=vmax)
また、カラーバーの目盛が内向きのままだったので外向きにも変更している。
colorbar = fig.colorbar(im, label='colorbar') colorbar.ax.tick_params(which='both', direction='out')
カラーバーをマップのサイズに合わせる
今回使用した配列は行数よりも列数の方が多い。したがって横長のマップになるが、カラーバーが上下にはみ出ているのがわかるだろう。縦長のマップならはみ出ないことも多いが、横長の場合は大抵はみ出る。
ということでここではカラーバーの長さをマップのサイズに合わせる。なお、行数の方が多い時はマップ右に、列数の方が多い時はマップ下にカラーバーを配置した。
全体コード
各部分の解説
行と列の数でカラーバーを縦置きか横置きかに変更。direction
がどこに置くかで、orientation
で垂直(vertical
)か水平(horizontal
)を選択。
# 行数が列数以上の時 if array.shape[0] >= array.shape[1]: direction = 'right' orientation = 'vertical' # 行数が列数未満の時 else: direction = 'bottom' orientation = 'horizontal'
カラーバーをマップのサイズに合わせる。divider
でどのaxes
のものかを指定し、cax
でカラーバーの情報を付加。第2引数の0.5
はカラーバーの厚さを示す。pad
はマップとカラーバーの間隔。
あとはこれらの情報をfig.colorbar
で設定。
# カラーバーの向きや位置を調整 divider = mpl_toolkits.axes_grid1.make_axes_locatable(ax) cax = divider.append_axes(direction, 0.5, pad=0.5) # 向きや位置を調整したカラーバーを追加 colorbar = fig.colorbar( im, cax=cax, orientation=orientation, label='colorbar', )
行数の方が多いデータ、列数の方が多いデータ
まずは列数の方が多いdata
から。この場合は列数の方が多いので、マップ下に水平にカラーバーを配置。
一方で行数の方が多いデータはdata2
として定義した。こちらは列数を4
として作成。この場合はカラーバーはマップ右側。
マップの軸目盛の値を変更
これまではマップの目盛は0
, 1
, 2
, ...
のように0
から1
刻みで作成した。ここではこの刻みを任意にする方法について解説する。ただし条件として以下を挙げる。
- 目盛にする数値は一定の周期(
10
,20
,30
とか。10
,20
,50
,60
とかはなし) - 数値を使用する(文字は次章で扱う)
全体コード
使用する軸目盛は
- 横軸:
10
から90
までを10
刻み - 縦軸:
100
から400
までを100
刻み
各部分の解説
まずは横軸、縦軸の最初と最後の数を取得。そして、刻み数を計算。正直刻みは一定なので刻み数については計算しなくていい。
start_x, end_x = xticks[0], xticks[-1] start_y, end_y = yticks[0], yticks[-1] xtick = (end_x - start_x) / (data.shape[1] - 1) ytick = (end_y - start_y) / (data.shape[0] - 1)
横軸と縦軸の表示上の最小・最大値を設定する。横軸では目盛の初めは10
にしたいので、表示上は$10 - 10 / 2 = 5$とする。縦軸も同様で、表示上の最小の数は50
とし、最大の数は450
としている。
# 横軸の表示上の最小値、最大値、縦軸の最小値、最大値を設定 extent = [ start_x - xtick / 2, end_x + xtick / 2, start_y - ytick / 2, end_y + ytick / 2 ]
目盛の値を変更した時、その比率を指定しておかないとマップの1データが長方形となってしまう。以下の図はaspect
を指定しなかった時のマップ。縦長になってしまっている。
# 縦横でデータ数が異なるとmapのpixelが歪になるのでアスペクトを整える aspect = abs(xtick / ytick)
作成した表示上の最小・最大値とaspect
をimshow
の引数に入れる。次章で解説する、軸で値を変更した場合だとマップ外の軸目盛の表示がされない。しかし、extent
を使用することでマップ外の軸目盛も表示される。
# マップの配色はgray、基準位置は左下、最小値は0、最大値は100 # extentを使用することでmap外の座標も自動で計算して表現される im = ax.imshow( data, cmap=map_color, vmin=0, vmax=100, extent=extent, aspect=aspect, )
しかし、軸目盛の値自体を変更しているので、テキストを付加するために作成した自作関数text
関数が使用できない。text
関数は0
から1
刻みだったからだ。ということで即席でfor
でtext
を追加。
# 座標の値が変わっているのでそれに応じてtextを付与する # text(data, ax, 'deeppink') for num_r, y in enumerate(yticks): for num_c, x in enumerate(xticks): value = data[::-1][num_r, num_c] ax.text(x, y, value, color='deeppink', ha='center', va='center')
指定する軸の並びを逆にする
軸目盛の値を指定できるので、当然配列を逆にもできる。
img_show_e( show_name='imshow_setxy_r', xticks=np.arange(10, 90 + 10, 10), yticks=np.arange(100, 400 + 100, 100)[::-1], map_color='gray', nan_color='mediumslateblue', )
元々の目盛の数と指定する目盛の数を合わせない
元々の目盛の数と指定する目盛の数を合わせない場合はどうなるか。単純にマップ上でズレが生じる。この例では横軸が9
データあるのに指定する配列を10
から80
の10
刻み、すなわち8
データにした時のマップだ。
軸がマップの1データごとの中心ではなくなり、ものすごく見にくくなっている。
img_show_e( show_name='imshow_set_xy_miss', xticks=np.arange(10, 90, 10), yticks=np.arange(100, 400 + 100, 100), map_color='gray', nan_color='mediumslateblue', )
マップの軸目盛を文字に変更
最後は軸を数値ではなく文字にするというもの。先ほどとは異なり文字なので表示軸の最小値や最大値という考え方がない。
したがって、文字を目盛にした以下の方法だと、例え数値にしたとしてもマップ外に出た際にはその部分の表記がなくなる。
全体コード
ここでは横軸は文字で指定、縦軸は数値のままにした。
各部分の解説
今回は文字列を使用するということで、extent
のように最小値などが設定できない。したがって、軸目盛を直接変更する方法にする。
まずはax.set_xticks
, ax.set_yticks
で軸目盛を作成、その後、ax.set_xticklabels
, ax.set_yticklabels
でその位置に軸目盛を付加する。
先にticks
を指定しておかないとなぜか横軸がB
から始まる。設定するとちゃんとA
から表示された。
# 軸目盛を打つ場所を決める ax.set_xticks(np.arange(len(xticks))) ax.set_yticks(np.arange(len(yticks))) # 軸目盛を設定 ax.set_xticklabels(xticks) ax.set_yticklabels(yticks)
一方で軸目盛を単純に上書きしているだけなので、マップ外の目盛は表示されない。
視覚的な表現が豊かに
今回はmatplotlib.pyplot
、plt
のimshow
で2次元配列をマップ化した。使用したデータは36データだったのでまばらだったが、今回のコードは応用が利くものだ。
天文データでよく使われるfits形式のデータや画像データを使用する際にも応用することができる。応用が利きやすいので各人で色々と改良をしていただければと思う。
関連記事
-
-
【plt&pandas】df.plot()でmatplotlibのグラフを作成
2022/8/19
こんな人にオススメ pandasのデータフ& ...
-
-
【plotly&pandas】df.plot()でPlotlyのグラフを作成
2022/8/19
こんな人にオススメ pnadasのデータフ& ...
-
-
【文字入力&グラフに反映】inputとtkinterでグラフに任意の文字を反映
2022/8/19
こんな人にオススメ グラフを作成 ...
-
-
【plt&マーカー一覧】matplotlibのマーカーをグラフ化
2022/8/19
こんな人にオススメ matplotlib.pyplot、pltのマ ...