カテゴリー

plt

【python&imshow】plt.imshowで2次元配列をマップ化

2021年6月15日

こんな人にオススメ

pythonで2次元配列をマップ化(値の大きさで色が変わる表的な)を作成したい!

どうすればうまくできる?

ということで、今回はmatplotlib.pyplotpltplt.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

運営者のメガネです。YouTubeTwitterInstagramも運営中。自己紹介お問い合わせページあります。

運営者メガネ

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。その中でカラーバーなどをいじるので色々とimportplt_layout_templatepltのテンプレートを入れたpyファイルで、2つ上のディレクトリにいるので../../で階層を登ってimportしている。内容については以下参照。

plt.rcParamsでデフォルトを変更した後のグラフ
【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')

先ほど紹介した自作関数。arraydataでどの軸にテキストを付加するかは今の軸である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にしたvminvmaxをどちらもstrにしている。その後joinstrにしたvmin, vmaxtoで結合している。

# 「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)

作成した表示上の最小・最大値とaspectimshowの引数に入れる。次章で解説する、軸で値を変更した場合だとマップ外の軸目盛の表示がされない。しかし、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刻みだったからだ。ということで即席でfortextを追加。

# 座標の値が変わっているのでそれに応じて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から8010刻み、すなわち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.pyplotpltimshowで2次元配列をマップ化した。使用したデータは36データだったのでまばらだったが、今回のコードは応用が利くものだ。

天文データでよく使われるfits形式のデータや画像データを使用する際にも応用することができる。応用が利きやすいので各人で色々と改良をしていただければと思う。

関連記事

plt

【plt&pandas】df.plot()でmatplotlibのグラフを作成

2022/8/19

こんな人にオススメ pandasのデータフ& ...

Pythonでの比較

【plotly&pandas】df.plot()でPlotlyのグラフを作成

2022/8/19

こんな人にオススメ pnadasのデータフ& ...

Python基礎

【文字入力&グラフに反映】inputとtkinterでグラフに任意の文字を反映

2022/8/19

こんな人にオススメ グラフを作成 ...

plt

【plt&マーカー一覧】matplotlibのマーカーをグラフ化

2022/8/19

こんな人にオススメ matplotlib.pyplot、pltのマ ...

ガジェット

2023/9/18

【デスクツアー2022下半期】モノは少なく、でも効率的に Desk Updating #0

今回はガジェットブロガーなのにデスク環境を構築していない執筆者の ...

ライフハック

2023/9/16

【Audible vs YouTube Premium】耳で聴く音声学習コンテンツを比較

ワイヤレスイヤホンが普及し耳で学習することへのハードルが格段に下 ...

完全ワイヤレスイヤホン(TWS)

2023/9/18

【SENNHEISER MOMENTUM True Wireless 3レビュー】全てが整ったイヤホン

今回は高音質・高機能なSENNHEISERのフラグシップ完全ワイヤレスイヤホン「SENNH ...

ライフハック

2023/3/11

【YouTube Premiumとは】メリットしかないから全員入れ

今回はYouTube Premiumを実際に使ってみてどうなのか、どんなメリット/デメリット ...

マウス

2023/8/17

【Logicool MX ERGOレビュー】疲れない作業効率重視トラックボールマウス

こんな人におすすめ トラックボールマウスの王道Logicool MX ERGOが気になるけどऩ ...

ベストバイ

2023/9/18

【ベストバイ2022】今年買って良かったモノのトップ10

2022年ベストバイ この1年を振り返って執筆者は何を買ったのか。ガジェッ& ...

スマホ

2023/1/15

【楽天モバイル×povo2.0の併用】月1,000円の保険付きデュアルSIM運用

こんな人におすすめ 楽天モバイルとpovo2.0のデュアルSIM運用って実際のとこ ...

マウス

2023/9/16

【Logicool MX ERGO vs MX Master 3】ERGOをメインにした決定的な理由

こんな疑問・お悩みを持っている人におすすめ 執筆者はLogicoolのハイエンӠ ...

macOSアプリケーション

2022/9/30

【Chrome拡張機能】便利で効率的に作業できるおすすめの拡張機能を18個紹介する

こんな人におすすめ Chromeの拡張機能を入れたいけど、調べても同じような ...

macOSアプリケーション

2023/5/3

【Automator活用術】Macで生産性を上げる作業の自動化術

今回はMacに標準でインストールされているアプリ「Automator」を使ってできる ...

Pythonを学びたいけど独学できる時間なんてない人へのすゝめ

執筆者は大学の研究室・大学院にて独学でPythonを習得した。

でも社会人になったら独学で行うには時間も体力もなくて大変だ。

時間がない社会人だからこそプロの教えを乞うのが効率的。

ここでは色んなタイプに合ったプログラミングスクールの紹介をする。

  • この記事を書いた人

メガネ

Webエンジニア駆け出し。独学のPythonで天文学系の大学院を修了。常時金欠のガジェット好きでM2 Pro MacBook Pro(30万円) x Galaxy S22 Ultra(17万円)使いの狂人。自己紹介と半生→変わって楽しいの繰り返しレビュー依頼など→お問い合わせ運営者情報、TwitterX@m_ten_pa、 YouTube@megatenpa、 Threads@megatenpa

-plt
-, , ,