【matplotlib】 グラフの任意の位置に、複数の単位の軸を表示する方法

# 概要
matplotlibの[secondary_xaxis](https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.secondary_xaxis.html#matplotlib-axes-axes-secondary-xaxis)、[secondary_yaxis](https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.secondary_yaxis.html#matplotlib-axes-axes-secondary-yaxis)を使い、**グラフの任意の位置に複数の単位の軸を表示する方法**を紹介します。
Google Colabで作成したコードは、[こちら](https://colab.research.google.com/drive/13anFd6V1RZ8F4m5ZOql2h-7owNabCnzk?usp=sharing)にあります。

:::note warn
注意
matplotlib.axes.Axes.secondary_xaxis、matplotlib.axes.Axes.secondary_yaxisは、matplotlibのversion3.1では、バグがある可能性があります。
バグの事例の1つに、https://github.com/matplotlib/matplotlib/issues/15621があります。
:::

# 項目
1. [x軸に別の軸を表示する例](#1x軸に別の軸を表示する例)
1. [y軸に別の軸を表示する例](#2y軸に別の軸を表示する例)
1. [対数軸を表示する例](#3対数軸を表示する例)
1. [任意の位置に複数の単位の軸を表示する例](#4任意の位置に複数の単位の軸を表示する例)



## 1.　x軸に別の軸を表示する例
上のx軸には[ラジアン](https://ja.wikipedia.org/wiki/ラジアン)を、下のx軸には角度の単位としての[度](https://ja.wikipedia.org/wiki/度_(角度))を表示する例を紹介します。

```Python:x軸に別の軸を表示する例
import matplotlib.pyplot as plt
import numpy as np

fig = plt.figure()
ax1 = fig.add_subplot(111)
x = np.arange(0, 360, 1)
y = np.sin(2 * x * np.pi / 180)
ax1.plot(x, y)
ax1.set_xlabel('angle [degrees]')
ax1.set_ylabel('signal')


def deg2rad(x):
    return x * np.pi / 180


def rad2deg(x):
    return x * 180 / np.pi


ax2 = ax1.secondary_xaxis('top', functions=(deg2rad, rad2deg))
ax2.set_xlabel('angle [rad]')
plt.show()
```
* コードの説明
``ax1.secondary_xaxis('top', functions=(deg2rad, rad2deg))``で、軸の上（``top``）に軸を置き、``ax1``オブジェクトのx軸に用いた**座標情報の配列**を``deg2rad``関数の引数に渡します。（``deg2rad``関数は、角度の度をラジアンに変換する関数です。）
その``deg2rad``関数の戻り値を``rad2deg``関数の引数に渡します。（``rad2deg``関数は、ラジアンを角度の度に変換する関数です。）
その``rad2deg``の戻り値を``ax1``と同じ縮尺にします。そして、軸の上（``top``）の目盛に、``deg2rad``関数の値が表示されます。
つまり、基本的に``functions``の2つ目の引数は、**``ax1``オブジェクトのx軸と同じ単位にするための関数**を置く使い方が良いと思います。

* 出力結果
![Unknown-43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/24f94f92-2064-361d-0879-5d69d5a8a28c.png)

## 2.　y軸に別の軸を表示する例
左のy軸にはメートルを、右のy軸にはセンチメートルを表示する例を紹介します。

```Python:y軸に別の軸を表示する例
import matplotlib.pyplot as plt
import numpy as np

fig = plt.figure()
ax1 = fig.add_subplot(111)
x = np.arange(0, 360, 1)
y = np.sin(2 * x * np.pi / 180)
ax1.plot(x, y)
ax1.set_xlabel('angle [degrees]')
ax1.set_ylabel('signal [m]')


def m2cm(x):
    return x * 100


def cm2m(x):
    return x / 100


ax2 = ax1.secondary_yaxis('right', functions=(m2cm, cm2m))
ax2.set_ylabel('signal [cm]')
plt.show()
```
* コードの説明
[x軸に別の軸を表示する例](#1x軸に別の軸を表示する例)と基本的に同じです。``ax1.secondary_yaxis``は、``'right'``で場所を指定しています。

* 出力結果<br>
![Unknown-44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/3a8f90e5-82a9-cea4-a96a-2dc558a2c95b.png)


## 3.　対数軸を表示する例
上のx軸には[周期](https://ja.wikipedia.org/wiki/周期)を、下のx軸には[周波数](https://ja.wikipedia.org/wiki/周波数)を対数軸で表示する例を紹介します。

```Python:対数軸を表示する例
import matplotlib.pyplot as plt
import numpy as np

fig = plt.figure()
ax1 = fig.add_subplot(111)
x = np.arange(0, 360, 1)
y = np.arange(0, 360, 1)
ax1.loglog(x, y)
ax1.set_xlabel('frequency [Hz]')
ax1.set_ylabel('signal')


def invert(x):
    # 1/x関数（0割対策でx=0のとき、1/x=infを返す）
    x = np.array(x).astype(float)
    near_zero = np.isclose(x, 0)
    x[near_zero] = np.inf
    x[~near_zero] = 1 / x[~near_zero]
    return x


ax2 = ax1.secondary_xaxis('top', functions=(invert, invert))
ax2.set_xlabel('Period [s]')
plt.show()
```

* コードの説明<br>
``ax1.loglog(x, y)``で、x軸、y軸を対数軸にします。
``ax1.secondary_xaxis('top', functions=(invert, invert))``で、軸の上（``top``）に軸を置き、``ax1``オブジェクトのx軸に用いた**座標情報の配列**を``invert``関数の引数に渡します。（``invert``関数は逆数を返す関数です。）<br>
``functions``の1つ目と2つ目の引数を同じ``invert``関数にすることで逆数の逆数となり、**``ax1``オブジェクトのx軸と同じ単位**にすることができます。<br><br>
``invert``関数の中身は簡単な例で説明します。
``x = np.array([0, 0.000000001, 1, 2, 3])``が、どのように処理されるかを見ていきます。<br>
``near_zero = np.isclose(x, 0)``で、近似的に``0``の値を``True``、それ以外は``False``を返します。<br>
``x[near_zero] = np.inf``で、``True``の配列に``inf``（無限）を入れます。<br>
``x[~near_zero] = 1 / x[~near_zero]``で、``True``の配列に逆数を入れます。（``~near_zero``は、配列の``True``と``False``を入れ替えるため、**``inf``ではない配列が逆数を入れる対象**となります。）<br>

```Python:invert関数の中身の説明
x = np.array([0, 0.000000001, 1, 2, 3])
x = np.array(x).astype(float)
near_zero = np.isclose(x, 0)
x[near_zero] = np.inf
x[~near_zero] = 1 / x[~near_zero]

print('near_zero=', near_zero)
print('~near_zero=', ~near_zero)
print('x =', x)
print('x[near_zero] =', x[near_zero])
print('x[~near_zero] =', x[~near_zero])

'''出力結果
near_zero= [True True False False False]
~near_zero= [False False True True True]
x = [inf inf 1. 0.5 0.33333333]
x[near_zero] = [inf inf]
x[~near_zero] = [1. 0.5 0.33333333]
'''
```

* 出力結果<br>
![Unknown-46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/1de9766c-8c3b-1aa7-a428-a0a7d0c824fe.png)





## 4.　任意の位置に複数の単位の軸を表示する例
y軸にキロメートル、メートル、センチメートル、ミリメートルの単位の軸を表示する例を紹介します。

```Python:任意の位置に複数の単位の軸を表示する例
import matplotlib.pyplot as plt
import numpy as np

fig = plt.figure()
ax1 = fig.add_subplot(111)
x = np.arange(0, 360, 1)
y = np.sin(2 * x * np.pi / 180)
ax1.plot(x, y)
ax1.set_xlabel('angle [degrees]')
ax1.set_ylabel('signal [m]')

'''mとcmを変換する関数'''
def m2cm(x):
    return x * 100


def cm2m(x):
    return x / 100


'''mとmmを変換する関数'''
def m2mm(x):
    return x * 1000


def mm2m(x):
    return x / 1000


'''mとkmを変換する関数'''
def m2km(x):
    return x / 1000


def km2m(x):
    return x * 1000

# 2軸目
ax2 = ax1.secondary_yaxis('right', functions=(m2cm, cm2m))
ax2.set_ylabel('signal [cm]')

# 3軸目
ax3 = ax1.secondary_yaxis(1.2, functions=(m2mm, mm2m))
ax3.set_ylabel('signal [mm]')

# 4軸目
ax4 = ax1.secondary_yaxis(-0.2, functions=(m2km, km2m))
ax4.set_ylabel('signal [km]')

plt.show()
```
* コードの説明
``secondary_yaxis``の1つ目引数は、数値も扱うことができ、**数値で軸の場所を指定すること**ができます。数値0がプロットエリアの左側（``left``）、1がプロットエリアの右側（``right``）となります。``secondary_xaxis``も同様に数値で軸の場所を指定でき、数値0がプロットエリアの下側（``bottom``）、1がプロットエリアの上側（``top``）となります。

* 出力結果
![Unknown-45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/82673f0a-3303-3aea-6d33-ac3a62b80697.png)


# まとめ
グラフの任意の位置に複数の単位の軸を表示する方法を紹介しました。
使う機会は少ないと思いますが、誰かの役に立てれば幸いです。

# 参考資料
* [matplotlib Secondary Axis](https://matplotlib.org/stable/gallery/subplots_axes_and_figures/secondary_axis.html#secondary-axis)
* [matplotlib.axes.Axes.secondary_xaxis](https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.secondary_xaxis.html#matplotlib-axes-axes-secondary-xaxis)
* [matplotlib.axes.Axes.secondary_yaxis](https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.secondary_yaxis.html#matplotlib-axes-axes-secondary-yaxis)