【matplotlib】2次元ヒートマップを3次元で表示する方法

# 概要
matplotlibで作成した2次元ヒートマップをmatplotlibで**3次元で表示する方法**を紹介します。
また、番外編ですがPlotlyを使い、インタラクティブな3次元のグラフを作成する方法も紹介します。
Google Colabで作成したコードは、[こちら](https://colab.research.google.com/drive/1gNRJ0xXOJEvy8oB5KDzEZA6XlzocVxXA?usp=sharing)にあります。

# 手順
1. 各種インポートします。（各モジュールの実行時のversionは、Google Colab内で作成したコードにあります。）
1. 2次元ヒートマップに使う2次元配列を用意します。
1. 2次元配列を[imshow](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.imshow.html#matplotlib-pyplot-imshow)を使い、2次元ヒートマップで表示します。
1. matplotlibの[Axes3D](https://matplotlib.org/stable/api/_as_gen/mpl_toolkits.mplot3d.axes3d.Axes3D.html#mpl-toolkits-mplot3d-axes3d-axes3d)を使い、３次元で表示します。
1. （番外編）Plotlyを使い、matplotlibで作ったグラフをインタラクティブな3次元のグラフで表示します。

## 1.　各種インポート
```Python:各種インポート
import numpy as np
from matplotlib.image import imread
import matplotlib.pyplot as plt
import matplotlib.colors
from mpl_toolkits.mplot3d import Axes3D
import plotly.graph_objects as go
```
各モジュールの実行時のversionは、Google Colab内で作成したコード[こちら](https://colab.research.google.com/drive/1gNRJ0xXOJEvy8oB5KDzEZA6XlzocVxXA?usp=sharing)にあります。

## 2.　2次元ヒートマップに使う2次元配列を用意
適当なサンプルの2次元配列を用意します。

```Python:サンプルとして使う、２次元ヒートマップ用の2次元配列
def func(x, y):
    z = 0.01*(x*np.sin(0.1*x) + y*np.cos(0.1*y))
    return z

y_shape, x_shape = (50, 100)
image = np.empty((y_shape, x_shape))
for y in range(y_shape):
    for x in range(x_shape):
        image[y][x] = func(x, y)
```

## 3.　matplotlibのimshowを使い、2次元ヒートマップで表示
```Python:2次元ヒートマップで表示
fig = plt.figure(figsize=(8, 4))
ax = fig.add_subplot(111)
color = ax.imshow(image, cmap='CMRmap', origin='lower')
ax.set_xlabel('x')
ax.set_ylabel('y')
plt.colorbar(color, shrink=0.8, label='z')
plt.show()
```
出力結果
![Unknown-31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/91dfbbf5-ad68-efd7-9e2d-e37c1b690a06.png)




## 4.　matplotlibのAxes3Dで3次元で表示
x軸とy軸の縮尺を統一するために、共通の軸の範囲``ax.set_xlim(0, np.max(image.shape))``、
``ax.set_ylim(0, np.max(image.shape))``を使います。

```Python:2次元ヒートマップを3次元で表示
fig = plt.figure(figsize=(13, 10))
ax = fig.add_subplot(111, projection='3d')
# 格子点を生成
y, x = np.mgrid[:image.shape[0], :image.shape[1]]
color = ax.plot_surface(x, y, image, cmap='CMRmap',
                        edgecolor='k', rstride=5, cstride=5)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
ax.set_xlim(0, np.max(image.shape))
ax.set_ylim(0, np.max(image.shape))
cbar = plt.colorbar(color, shrink=0.6, label='z')
plt.show()
```
出力結果
![Unknown-32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/234f27e6-9482-1033-5bea-9a21b452eb5c.png)


### 角度を変えたい場合
[ax.view_init()](https://matplotlib.org/2.0.2/mpl_toolkits/mplot3d/api.html#mpl_toolkits.mplot3d.axes3d.Axes3D.view_init)を使うと、任意の角度で表示できます。

```Python:
fig = plt.figure(figsize=(13, 10))
# 3Dプロット
ax = fig.add_subplot(111, projection='3d')
y, x = np.mgrid[:image.shape[0], :image.shape[1]]
color = ax.plot_surface(x, y, image, cmap='CMRmap',
                        edgecolor='k', rstride=5, cstride=5)
ax.view_init(80, 270)  # 表示角度の追加, ax.view_init(縦方向の角度, 横方向の角度)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
ax.set_xlim(0, np.max(image.shape))
ax.set_ylim(0, np.max(image.shape))
cbar = plt.colorbar(color, shrink=0.6, label='z')
plt.show()
```
出力結果
![Unknown-33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/bf02576b-9c42-252e-c2fa-489462d32fe2.png)



## 5.　（番外編）Plotlyを使い、matplotlibで作ったグラフをインタラクティブな3次元のグラフで表示
```Python:Plotlyを使い、インタラクティブな3次元で表示
fig = go.Figure(go.Surface(
    x=np.arange(image.shape[1]),
    y=np.arange(image.shape[0]),
    z=image,
    opacity=1.0,  # 透明度（透明0-1不透明）
    colorbar=dict(title='z')  # colorbarのタイトル
    ))
fig.update_layout(
    scene=dict(
        xaxis=dict(nticks=10, range=[0,np.max(image.shape)],),  # x軸の範囲
        yaxis=dict(nticks=10, range=[0,np.max(image.shape)],),  # y軸の範囲
        ),
    width=700,  # インライン表示の横幅のエリア
    height=700,  # インライン表示の縦幅のエリア
    margin=dict(l=50, r=50, b=50, t=50)
    )
# 等高線の表示
'''パラメータ
show : グラフと面に常に等高線を表示するか
usecolormap : 等高線にカラーマップの色を表示するか
highlightcolor : colorの色
project_z : 面に等高線を投影するか
'''
fig.update_traces(contours_z=dict(show=True, usecolormap=True,
                                  highlightcolor='limegreen',
                                  project_z=True
                                  ))
fig.show()
```
出力結果
![plotly_image.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/02f3ef4d-6074-77ba-a63d-1b1faa670d16.gif)

**Google Colab内でもインタラクティブに動きます。**
# まとめ
matplotlibで作成した2次元ヒートマップを**3次元で表示する方法**を紹介しましたが、2次元ヒートマップの良さもあるので、状況によって使い分けましょう。
3次元は角度によって見えない領域があるので、番外編で紹介したPlotlyが3次元を表示するのに便利です。

# 参考資料
* [matplotlib mplot3d tutorial](https://matplotlib.org/2.0.2/mpl_toolkits/mplot3d/tutorial.html#mplot3d-tutorial)
* [Plotly 3D Surface Plots in Python](https://plotly.com/python/3d-surface-plots/)