[matplotlib] 科学論文向けグラフ作成サンプル関数

# 0. はじめに
本記事はpythonのmatplotlibを用いたグラフ作成の汎用関数とその簡単な解説になります．
汎用性を高めるため，凡例の増加等には**なるべく繰り返し関数で対応**するような工夫をしています．また，データ処理で使いがちなpandas.dataframeを使用しているので，計算などを行った後のデータにそのまま使用しやすいです．

筆者が必要になった形式のグラフスタイルを記載しています．（定期的に追記します）
本記事はパラメータを多く降った結果や，グラフを並べて見ることを想定しており，性質的に理工学者の研究発表・論文投稿向けとなっています．

# 1. 準備

## 1.1. 環境構築
2020/04/04現在最新のpython3-Anaconda環境を想定しています．
pythonの導入方法はネットでいくらでも出てきますが，ポチポチするだけで済むので，[Anaconda](https://www.anaconda.com/distribution/)がおすすめ．pathは通すようにチェックつけましょう．

エディタは[Visual Studio Code](https://azure.microsoft.com/ja-jp/products/visual-studio-code/)を使用しています．以前はPyCharmを使用していましたが，他の言語と同じエディタを使いたくなったので，変えました．自動整形コマンドが便利です．(Shift + Alt + F)
参考：[VS codeでpython3の実行(windows10)](https://qiita.com/aki34/items/6d5e4f5d6036b9bcd63e)

## 1.2. パッケージのインポート
必要なものをimportします．matplotlib以外にはnumpyとpandasをとりあえず使います．matplotlibはpyplotしか使ってないですね

```python
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
```

## 1.3. サンプルデータの用意
本記事で使用するためのサンプルデータを用意します．
dataframeのほうが，一気に表にできるのでデータとして扱う際には簡単ですが，実際にグラフにする際は，リストのほうが細かい設定ができるので，逐一リストに戻す場合もあります．
dataframeの扱いやファイルの読み書きはまたどこかでまとめます．

とりあえず以下ではリストを作った後dfを生成しておきました．
csvやexcel出力時に縦じゃないと見づらいので，行と列を変換してます．


```python
A = np.linspace(0, 1)
B = [x**2 for x in A]
C = [np.cos(2*np.pi*x) for x in A]
S = [np.sin(2*np.pi*x) for x in A]
E = np.random.rand(len(A))/20+0.1
df = pd.DataFrame([A, B, C, S, E], index=[
                  "x", "squarex", "cosx", "sinx", "error"]).T
```
## 1.4. グラフスタイルの調整
論文ぽい見た目にする設定をします．```rcParams```でデフォルト値を変更する事ができるので以下のように変えてます．
特にフォント・目盛り・凡例の枠とかを変えるとかなりそれっぽいです．
フォントは，英語の図ならとりあえずTimes New Romanにしとけばいいでしょう．

以下は上で用意したサンプルデータを，dataframeの確認がてら適当にplotしたものです．```df.plot()```でindexをx軸にした複数凡例のグラフになります．驚くほど簡単ですが，調整が少ししんどくなるので以降は使いません．

```python
plt.rcParams['font.family'] = 'Times New Roman'
plt.rcParams['font.size'] = 10  # 適当に必要なサイズに
plt.rcParams['xtick.direction'] = 'in'  # in or out
plt.rcParams['ytick.direction'] = 'in'
plt.rcParams['axes.xmargin'] = 0.01
plt.rcParams['axes.ymargin'] = 0.01
plt.rcParams["legend.fancybox"] = False  # 丸角OFF
plt.rcParams["legend.framealpha"] = 1  # 透明度の指定、0で塗りつぶしなし
plt.rcParams["legend.edgecolor"] = 'black'  # edgeの色を変更

df = df.set_index("x")
df.plot()
plt.legend()
plt.show()
```
出力結果は以下になります．
![Figure_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613158/a9f152d3-936c-6b62-e285-a8af2224dd97.png)

## 1.5. LaTeX記法について
python内では```r'$~~$'```で簡単にLaTeX記法で数式を書くことができます（~~に記載する）．グラフで表示する際には，数式を積極的に書くというよりは，ラベルで上付き，下付き，ギリシャ文字を使用したいときによくお世話になる気がします．記法については[公式](https://matplotlib.org/tutorials/text/mathtext.html)を参照で．
数式フォントのデフォルトはdejavusansですが，流石にsans serif体は気持ち悪いので，stixに以下のように変更推奨です．

```python
plt.rcParams["mathtext.fontset"] = "stix" # stixフォントにする
```

注意点として，普通に記載したままだと，数式は細字（というか標準の太さ）で表記されます．matplotlibはすべてデフォルトが太字なので，デザイン的にはちょっと気持ち悪いことになります．気になる場合は[こちら](http://spiky-ball.blogspot.com/2019/07/matplotlib.html)などで頑張ってすべて細字にするのもありかもしれません．

最後に，数式を文字的に使いたい場合は，デフォルトが細字イタリック体だと面倒かもしれないので，以下のようにすることで，普通のTextと同じような状態でかけます．

```python
plt.rcParams['mathtext.default']= 'default' # defaultテキストにする
```

なおここまでの設定の状態では上付き文字が上過ぎ下付き文字が下過ぎるので，修正したほうがいいと思います．
参考：[matplotlibで上付き文字が上過ぎ、下付き文字が下過ぎる問題の解決](https://qiita.com/ogose/items/d110aa090102079fe73f)

## 1.6. 注意事項
以降本記事内ではオブジェクト指向インターフェースでグラフを書きます．ax.~~とかいうやつです．理屈はきちんと解説されている方のを見てください．
参考：[早く知っておきたかったmatplotlibの基礎知識、あるいは見た目の調整が捗るArtistの話](https://qiita.com/skotaro/items/08dc0b8c5704c94eafb9)

# 2. 複数凡例（1軸）
## 2.1. 概要
* 1つのy軸に対して複数の凡例を与えます．for文により凡例は自動で追加されます．
* y軸ラベルにはすべての値を表記をカンマ区切りで表記するようにしています．
* 引数はdataframeとそのラベルで，y軸のラベルは配列で与えてください．
* 色や線のリスト```c_list, l_list```はお好みで変更してください．
* ```subplots_adjust(top=0.95, right=0.95)```とすると，無駄な余白ができて紙面ぎりぎりになったり図が潰たりしません．（```tight_layout()```でいいという節はあります．）

## 2.2. 関数

```python:multiLegend
def multiLegend(df, x, y_list):
    c_list = ["k", "r", "b", "g", "c", "m", "y"]
    l_list = ["-","--","-.","."]
    fig, ax = plt.subplots(figsize=(5, 5))
    plt.subplots_adjust(top=0.95, right=0.95)
    for i in range(len(y_list)):
        y = y_list[i]
        ax.plot(df[x], df[y], linestyle=l_list[i], color=c_list[i], label=y)
    yLabel = ', '.join(y_list)
    ax.set_ylabel(yLabel)
    ax.set_xlabel(x)
    plt.legend()
    plt.show()
    return
```

## 2.3. 実行結果

```python
multiLegned(df, "x", ["squarex", "cosx", "sinx"])
```

![Figure_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613158/e3b5f669-8e60-69cf-9e2e-6081e01fc1c7.png)

# 3. 複数凡例（2軸）
## 3.1. 概要
* 2つのy軸に対して複数の凡例を与えます．for文により凡例は自動で追加されます．
* 引数はdataframeとそのラベルで，y軸のラベルは1軸目，2軸目それぞれ配列で与えてください．凡例がそれぞれ右と左に分かれるようにしているのがミソです．
* 色や線のリスト```c_list, l_list```はお好みで変更してください．
* 一応```y2_list```を入れないことでこの関数のまま1軸として使うこともできるようになってます．表示の仕方色々模索するときには便利かと思います．

## 3.2. 関数

```python:multiLegend2
def multiLegend2(df, x, y1_list, y2_list=None):
    # y2_listを入れなければ2軸にならない
    c_list = ["k", "r", "b", "g", "c", "m", "y"]
    l_list = ["-", "--", "-.", "."]
    fig, ax1 = plt.subplots(figsize=(5.5, 5))
    j = 0
    for y in y1_list:
        ax1.plot(df[x], df[y], linestyle=l_list[j],
                      color=c_list[j], label=y)
        j += 1
    ax1.legend(loc='lower left')
    ax1.set_xlabel(x)
    ax1.set_ylabel(', '.join(y1_list))
    if len(y2_list) != None:
        ax2 = ax1.twinx()
        for y in y2_list:
            ax2.plot(df[x], df[y], linestyle=l_list[j],
                         color=c_list[j], label=y)
            j += 1
        ax2.legend(loc='upper right')
        ax2.set_ylabel(', '.join(y2_list))
    plt.tight_layout()
    plt.show()
    return
```

## 3.3. 実行結果

```python
multiLegend2(df, "x", ["squarex", "cosx"], ["sinx"])
```
![Figure_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613158/dec40f52-6b36-e8c1-e0d9-fa4c806e28ac.png)

# 4. 多軸グラフ
## 4.1. 概要
* 軸数を自動で調整して多軸出せるグラフです．for文により自動的に右側に軸を追加していきます．
* ラベルの数に応じて右の余白を大きくしたり軸位置を調整しています．必要に応じて実数字を変更してください．
* 引数はdataframeとそのラベルで，y軸のラベルは配列で与えてください．
* 色や線のリスト```c_list, l_list```はお好みで変更してください．

## 4.2. 関数
```python:multiAxes
def multiAxes(df, x, y_list):
    c_list = ["k", "r", "b", "g", "c", "m", "y"]
    l_list = ["-","--","-.","."]
    fig, ax0 = plt.subplots(figsize=(6, 5))
    plt.subplots_adjust(top=0.95, right=0.95-(len(y_list)-1)*0.1) #ずれたらここ調整
    axes = [ax0]  # 変数分の軸の数を作る
    p_list = []  # 変数分のplotの入れ物
    for i in range(len(y_list)):
        y = y_list[i]
        if i != 0:
            axes.append(ax0.twinx())
            axes[i].spines["right"].set_position(("axes", 1+(i-1)*0.2)) #ずれたらここ調整
        p, = axes[i].plot(df[x], df[y], linestyle=l_list[i], color=c_list[i], label=y)
        p_list.append(p)
        axes[i].set_ylabel(y_list[i], color=c_list[i])
        axes[i].yaxis.label.set_color(c_list[i])
        axes[i].spines['right'].set_color(c_list[i])
        axes[i].tick_params(axis='y', colors=c_list[i])
    axes[0].set_xlabel(x)
    plt.legend(p_list,y_list)
    plt.show()
    return
```

## 4.3. 実行結果

```python
multiAxes(df, "x", ["squarex", "cosx", "sinx"])
```
![Figure_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613158/085d4066-2ba1-f9e3-1548-112bede9018e.png)

# 5. 複数subplot
## 5.1. 概要
* 複数のサブプロットを繰り返し関数により自動で作成します．
* 今回の例は縦に一列並べた場合です．横一列の場合は```plt.subplots```の中身をちょこっといじって，y軸の表示を最後だけにしてやればいいでしょう．
* 並べるからにはおそらく並べた方向の軸は共通にすると思われるので，```sharex=all```としています．
* 色等はやはりお好みで調整してください．（表示領域自体が違うので本来色などの変更必要は論文等では無いと思いますが）

## 5.2. 関数

```python:multiPlots
def multiPlots(df, x, y_list):
    c_list = ["k", "r", "b", "g", "c", "m", "y"]
    l_list = ["-","--","-.","."]
    fig, axes = plt.subplots(len(y_list), 1, sharex="all", figsize=(4, 2*len(y_list)))
    for i in range(len(y_list)):
        y = y_list[i]
        axes[i].plot(df[x], df[y], linestyle=l_list[i], color=c_list[i], label=y)
        axes[i].set_ylabel(y_list[i], color=c_list[i])
        axes[i].yaxis.label.set_color(c_list[i])
        axes[i].spines['left'].set_color(c_list[i])
        axes[i].tick_params(axis='y', colors=c_list[i])
        if i == len(y_list)-1:
            axes[i].set_xlabel(x)
    plt.tight_layout()
    plt.show()
    return
```

## 5.3. 実行結果

```python
multiPlots(df, "x", ["squarex", "cosx", "sinx"])
```

![Figure_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613158/7ad95b35-4609-6c4e-0ecb-b9c3a15ab8b2.png)

# 6. 複数サンプル
## 6.1. 概要
* 複数のデータ（ファイル）から一枚のグラフを作りたいときも結構あるかと思います．
* ここまでよりも簡単といえば簡単ですが，2軸まで対応するために少し調整しました．1軸で良ければy2に値を入れなければいいです．
* 現状ラベルは数字が入っているだけなので，実用時はラベルの```datai```の部分をうまいこといじってもらったほうがいいかと思います．

## 6.2. 関数

```python:multiData
def multiData(df_list, x, y1, y2=None):
    l_list = ["-", "--", "-.", "."]
    fig, ax1 = plt.subplots(figsize=(5.25, 5))
    p_list = []  # 変数分のplotの入れ物
    labels = []
    for i in range(len(df_list)):
        df = df_list[i]
        p, = ax1.plot(df[x], df[y1], linestyle=l_list[i], color="k")
        labels.append(y1+'-data'+str(i+1))
        p_list.append(p)
    ax1.set_xlabel(x)
    ax1.set_ylabel(y1)
    if y2 != None:
        ax2 = ax1.twinx()
        for i in range(len(df_list)):
            df = df_list[i]
            p, = ax2.plot(df[x], df[y2], linestyle=l_list[i], color="b")
            labels.append(y2+'-data'+str(i+1))
            p_list.append(p)
        ax2.set_ylabel(y2, color = "b")
        ax2.yaxis.label.set_color("b")
        ax2.spines['right'].set_color("b")
        ax2.tick_params(axis='y', colors="b")
    plt.legend(p_list, labels)
    plt.tight_layout()
    plt.show()
    return
```

## 6.3. 実行結果

```python
A_2 = np.linspace(1, 2)
B_2 = [x**2 for x in A_2]
C_2 = [np.cos(2*np.pi*x) for x in A_2]
df_2 = pd.DataFrame([A_2, B_2, C_2], index=["x", "squarex", "cosx"]).T
A_3 = np.linspace(2, 3)
B_3 = [x**2 for x in A_3]
C_3 = [np.cos(2*np.pi*x) for x in A_3]
df_3 = pd.DataFrame([A_3, B_3, C_3], index=["x", "squarex", "cosx"]).T

multiData([df, df_2, df_3], "x", "squarex", "cosx")
```
![Figure_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613158/9bd7ebfd-2314-b0f7-679b-a843ec1e34e1.png)

# 7. エラー付き1軸
## 7.1. 概要
* 連続した値に対してあるエラーが乗っている場合に，それを値の周りに薄い色の幅をつけて示す関数です．
* 今回はエラーが上側下側同じであるとしているので，上下異なる場合は調整してください．
* 1つのy軸に対して複数の凡例，そのエラーを与えます．for文により凡例は自動で追加されます．
* ```y_error_list```にはyに対応したエラーの大きさを入れます．（今回は適当なランダム関数です．）
* y軸ラベルにはすべての値を表記をカンマ区切りで表記するようにしています．
* 引数はdataframeとそのラベルで，y，yのエラーはラベルの配列で与えてください．
* 色や線のリスト```c_list, l_list```はお好みで変更してください．

## 7.2. 関数

```python:multiLegend_wError
def multiLegend_wError(df, x, y_list, y_error_list):
    c_list = ["k", "r", "b", "g", "c", "m", "y"]
    l_list = ["-", "--", "-.", "."]
    fig, ax = plt.subplots(figsize=(5, 5))
    plt.subplots_adjust(top=0.95, right=0.95)
    for i in range(len(y_list)):
        y = y_list[i]
        y_error = y_error_list[i]
        ax.plot(df[x], df[y], linestyle=l_list[i], color=c_list[i], label=y)
        ax.fill_between(df[x], df[y]+df[y_error], df[y]-df[y_error], facecolor=c_list[i], edgecolor=None, alpha=0.3)
    yLabel = ', '.join(y_list)
    ax.set_ylabel(yLabel)
    ax.set_xlabel(x)
    plt.legend()
    plt.show()
    return
```

## 7.3. 実行結果

```python
multiLegend_wError(df, "x", ["squarex", "cosx", "sinx"], ["error", "error", "error"])
```
![Figure_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613158/fdb252a7-254e-171d-a706-0b211d149ee3.png)

# 8. 2Dカラーマップ
## 8.1. 概要
* 2変数関数の大きさをカラーで表すグラフです．
* 変数1を```x```，変数2を```y```とし，自分で定義した関数を```func```としています．
* 今回は2変数関数としてxとyの積を取る関数を用意しておきました．
* dataframeの行と列のインデックスを用いて表す方法もありますが，今回は各列を要素とする形をとっています．
* 分布のカラーマップは今回は```"jet"```を利用していますが，お好み[こちら](https://matplotlib.org/3.1.0/tutorials/colors/colormaps.html)でから選んでください．

## 8.2. 関数

```python:colormapPlot

def colormapPlot(df, x, y, func):
    x_list = df[x].values.tolist()
    y_list = df[y].values.tolist()
    X, Y = np.meshgrid(x_list, y_list)
    Z = func(X, Y)
    fig = plt.figure(figsize=(6, 5))
    ax = fig.add_subplot(111)
    c = ax.pcolor(X, Y, Z, cmap='jet')
    ax.set_xlabel(x)
    ax.set_ylabel(y)
    cc = fig.colorbar(c, ax=ax, orientation="vertical")
    cc.set_label("Function")
    plt.tight_layout()
    plt.show()
    return
```

## 8.3. 実行結果

```python
def multiply(x1, x2):
    return x1*x2

colormapPlot(df, "sinx", "cosx", multiply)
```

![Figure_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613158/e96507bf-09d6-fecf-edba-0b8f16126cd1.png)


# 9. 立体カラーマップ

## 9.1. 概要
* 2変数関数の大きさをカラーと高さで表すグラフです．（上記の3D版）
* 変数1を```x```，変数2を```y```とし，自分で定義した関数を```func```としています．
* 今回は2変数関数としてxとyの積を取る関数を用意しておきました．
* 3Dで表すためのツールとして新たに```mpl_toolkits.mplot3d```をインポートする必要があります．
* dataframeのみで立体表記は難しかったので，リスト，numpy配列を経由しています．
* 分布のカラーマップは今回は```"jet"```を利用していますが，お好み[こちら](https://matplotlib.org/3.1.0/tutorials/colors/colormaps.html)でから選んでください．

## 9.2. 関数

```python:cubicPlot

from mpl_toolkits.mplot3d import Axes3D

def cubicPlot(df, x, y, func):
    x_list = df[x].values.tolist()
    y_list = df[y].values.tolist()
    X, Y = np.meshgrid(x_list, y_list)
    Z = func(X, Y)
    fig = plt.figure(figsize=(6.25, 5))
    ax = fig.add_subplot(111, projection='3d')
    surf = ax.plot_surface(X, Y, Z, cmap='jet', linewidth=0)
    ax.set_xlabel(x)
    ax.set_ylabel(y)
    ax.set_zlabel("Function")
    fig.colorbar(surf)
    plt.tight_layout()
    plt.show()
    return
```

## 9.3. 実行結果

```python
def multiply(x1, x2):
    return x1*x2

cubicPlot(df, "sinx", "cosx", multiply)
```
![Figure_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613158/f56c670b-99d3-1f7e-526a-5f0f6bfc4c5f.png)

# 10. おわりに
本記事ではmatplotlibを使って筆者が実際に使用した科学技術論文用グラフ出力関数をまとめました．
実際に投稿版を作る際には軸ラベルをそれぞれ手打ちで作成したりしますが，それもリストを作ってfor文内で読み込むようしています．今回は見づらくなるのを避けてdataframeのラベル名で表示しました．

定期的に更新するのでよろしくどうぞ．