matplotlibでグラフを描く

# はじめに

データ解析とかをしていると、グラフ表現で可視化をすることはよくある。
Pythonではmatplotlibというライブラリを使うが、設定や書式など覚えきれずに度々調べてしまうので、よく使う機能・設定について備忘録的にまとめておく。
なので、「matplotlibとは？」や「pythonの使い方」などについての解説は特にしない。

# 基本的な描き方

## とりあえず描く

一番基本的なプロットをやってみる。

```python:単一データ
from matplotlib import pyplot as plt
from random import randint

# データの定義(サンプルなのでテキトー)
x = list(range(10))
y = [randint() for _ in x]

# グラフの描画
plt.plot(x, y)
plt.show()
```

出力↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291298/2d70b165-327f-899b-ad2b-34ea5968cebf.png)

とりあえず描画するだけならば非常にシンプルで、上記サンプルのように`matplotlib`ライブラリから`pyplot`モジュールをインポートし、リスト等で用意したデータを`plt.plot()`に渡すだけでよい。
`plt.show()`で出力例のようなウィンドウが開いて中にグラフが表示される。(注：サンプルのデータはランダムなのでグラフ自体は異なる)
ちなみに`pyplot`モジュールは`plt`という名称に置換するのが慣例らしい。

プロットしたいデータが複数ある場合は、`plt.plot()`を描画したいデータ分だけ重ねればよい。

```python:複数のデータ
from matplotlib import pyplot as plt
from random import randint

# データの定義(サンプルなのでテキトー)
x = list(range(10))
y1 = [randint(0, 100) for _ in x]
y2 = [randint(0, 100) for _ in x]

# グラフの描画
plt.plot(x, y1)
plt.plot(x, y2)
plt.show()
```

出力↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291298/c23a1c64-f41c-9b96-a672-9b19aa3fe61a.png)

こんな感じに勝手に色分けされて描画される。

## 複数のグラフを1枚に描く

異なるデータを描画する際、重ねるのではなく別々に描画したいこともある。

```python:複数グラフ
from matplotlib import pyplot as plt
from random import randint, random

# データの定義(サンプルなのでテキトー)
x = list(range(10))
y1 = [randint(0, 100) for _ in x]
y2 = [randint(0, 100) for _ in x]

# グラフの描画
fig = plt.figure()
ax = fig.add_subplot(2, 1, 1)
ax.plot(x, y1)
ax = fig.add_subplot(2, 1, 2)
ax.plot(x, y2)
plt.show()
```

出力↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291298/7cdb80bc-f8d0-30dc-4426-8264792ac68e.png)

少し複雑になった。
最初に、`fig = plt.figure()`でグラフのインスタンスを取得し、`ax = fig.add_subplot()`でプロットエリアのインスタンスを`ax`に取得している。`ax`に対して`plot`を実行することで異なるプロットエリアに描画している。
`add_subplot()`に渡す引数は左から、`縦のプロットエリア数`,`横のプロットエリア数`,`割り当てるプロットエリア番号`になっている。プロットエリア番号は左上から横⇒下の順で割りつく。

**2x2の場合の例**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291298/0fdbc094-3393-0b22-8539-31c34fc2963a.png)


なので、サンプルのサブプロット部分の指定を下記のようにすると、横並びにもできる。

```python:横並び
ax = fig.add_subplot(1, 2, 1)
ax.plot(x, y1)
ax = fig.add_subplot(1, 2, 2)
ax.plot(x, y2)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291298/f17d9f73-646b-12f1-f801-6f1ef2e7f40d.png)

ちなみに、サンプルでは`fig`、`ax`に明示的にプロットエリアのインスタンスを取得してからプロットしているが、下記のようにも書ける。

```python:少しラフな書き方
# グラフの描画
plt.subplot(2, 1, 1)
plt.plot(x, y1)
plt.subplot(2, 1, 2)
plt.plot(x, y2)
plt.show()
```
好みの問題かもしれないが、この記法で書いても劇的に短縮されるわけではないのでメリットは薄い。(気がする)

なお、`subplot`では強制的にグリッドレイアウトになるが、変則的なレイアウトにする手段もある。
ただし、大抵`subplot`で事足りる気がするのでこの記事には含まない。もし、必要になったら**[ここ](http://ailaby.com/matplotlib_fig/)**の記事が参考になる。

## よく使うグラフの種類

ここまでの例ではすべて折れ線グラフを使っていたが、いろいろな種類のグラフを書くことができる。

#### 折れ線グラフ:plot
`plt.plot(x, y)`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291298/5e4be013-c4fe-2f3e-f4e5-45553406e045.png)

#### 散布図:scatter
`plt.scatter(x, y)`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291298/4e3e2c4c-376c-8e86-be50-7e98065e6b2c.png)

#### 棒グラフ:bar
`plt.bar(x, y)`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291298/d41b1753-a918-1319-50bd-cb801fc4cb53.png)

#### ヒストグラム:hist
`plt.hist(y, 30)`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291298/d0b61fd4-9dae-f87b-164e-b5882592795c.png)

ヒストグラム以外の種類は呼び出す関数を変えるだけで、x,yの指定通り描画できる。散布図は`plot`関数で`linewidth`と`marker`オプションを設定することでも作成できるが、`scatter`関数の場合は、関数名変えるだけなのでこちらのほうが楽。(`scatter`は厳密にはバブルチャート作成用らしい)
ヒストグラムに関してはランダムに存在するデータを第一引数に渡し、第二引数で分割数を指定する。上記のサンプルでは正規分布の10000件のデータをyに入れて、適当に30分割して表示してみた。
他にも円グラフやヒートマップなど色々あるが、ここではよく使うものだけまとめておく。ほかのグラフは**[公式ドキュメント](https://matplotlib.org/gallery/index.html)**に沢山の例があるので、描きたいグラフに近いものを参照すればよい。

## グラフの保存

ここまでの例では`plt.show()`でグラフの表示をしてきた。これを使うと、pythonコード実行時にグラフ用のウィンドウが開いて中にグラフが表示される形になる。一応、このウィンドウから保存もできるが、連続で数枚のグラフを作る時や、画像サイズにこだわる時は一々表示はせずにグラフ画像の保存用関数`plt.savefig("ファイル名.png")`で保存できる。
ファイル形式は保存するファイル名末尾につけた形式で自動判別される。
![test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291298/97ea0c4b-102f-c5a1-4bf0-ae39b3b10450.png)
上の図は適当な散布図をpng形式で保存したもの。
ファイル名にはパスを指定するので、`img`ディレクトリ等を先に作って、その中に連続で画像を保存とかもできる。

```python:保存先ディレクトリ自動作成を含んだコード例
#!/usr/bin/env python
# coding: utf-8
from matplotlib import pyplot as plt
import numpy as np
from pathlib import WindowsPath as Path

# データの定義(サンプルなのでテキトー)
x = range(100)
y = np.random.normal(100, 20, 100)

# 画像の保存先ディレクトリを作成
img_path = Path("./img")
img_path.mkdir(exist_ok=True)

# グラフの描画
plt.figure(figsize=(4, 2))
plt.plot(x, y, lw=0, marker='.')
plt.savefig("./img/test.png")
```


## jupyterでの表示

jupyterのセルごとに実行結果をoutセルに表示できる。
`%matplotlib inline`とノートブックの先頭などに書いておくだけで、他は通常通り書けばよい。
※いつからか不明だが、何も書かなくてもインライン表示できました。とりあえず実行して、表示できないときは上記を書くと解決するかもしれません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291298/3ddefe07-c982-589a-2183-947b114b9d7f.png)


## markdown-preview-enhancedのコードチャンクでの表示

実験結果をレポートとしてまとめる時などに、markdown-preview-enhancedを使う場合、コードチャンクとして実行コードを埋め込むことができる。
通常、コードチャンクとしてpythonのコードを埋め込む時は、
> \`\`\`python {cmd}
> ~
> \`\`\`

のようにpython指定のコードブロックの後ろに`␣{cmd}`をつける。
このままでmatplotlibの描画コードを実行すると、`plt.show()`で別ウィンドウが開いてしまう。
こんな感じ↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291298/81871910-ee51-a186-510b-4da8ee465e25.png)

そこで、コードブロックの後ろを`␣{cmd matplotlib}`に変更すると、ドキュメント中に出力がインライン表示されるようになる。↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291298/d612e547-7c7e-345f-4a72-44b4874a1cdc.png)

`hide`も指定して、`␣{cmd hide matplotlib}`とすると、描画用のコードを隠してグラフだけ表示することもできる。

※コードチャンク自体**デフォルトではセキュリティ上off**になっているので、[公式ドキュメント](https://shd101wyy.github.io/markdown-preview-enhanced/#/code-chunk)の通り有効にしたときのみ使用できる。公式ドキュメントでも下記の注意書きがあるように、悪意あるコードを実行できてしまう危険があるので、機能の有効化は**自己責任**で。
>⚠️ Script execution is off by default and needs to be explicitly enabled in Atom >package / VSCode extension preferences
>
>Please use this feature with caution because it may put your security at risk! Your >machine can get hacked if someone makes you open a markdown with malicious code while >script execution is enabled.
>
>Option name: enableScriptExecution

# 見た目の調整

ここまではデータをプロットするだけだったが、レポートに載せる時などは当然体裁を整えたくなる。

## よく使うオプション

### 軸、目盛り関連

#### 軸範囲の調整:xlim, ylim
表示する軸範囲をを設定するオプション。
x軸は`plt.xlim(min, max)`、y軸は`plt.ylim(min, max)`にそれぞれ上下限値を指定する。
なお、`axes`を使う場合は`ax.set_xlim(min, max)`と`ax.set_ylim(min, max)`と若干名称が変化する。

例えば、下記コードは散布図を描画するが、基本0~100のデータ分布中に10000の値が紛れさせている。

```python:
#!/usr/bin/env python
# coding: utf-8
from matplotlib import pyplot as plt
import random

# データの定義(サンプルなのでテキトー)
x = range(100)
y = [random.randint(0, 100) for _ in range(40)]
y.append(10000)
y.extend([random.randint(0, 100) for _ in range(59)])

# グラフの描画
plt.scatter(x, y)
plt.show()
```
これを描画するとこうなる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291298/db26d314-7830-25eb-e242-adcb367aa462.png)

こんな外れデータを無視して分布が集中しているところを見たくなったりするときに、データから外れ値を除く処理を掛けなくても、y軸の描画範囲を絞るだけで見れるようになる。

```python:y軸描画範囲を0~120に制限
#!/usr/bin/env python
# coding: utf-8
from matplotlib import pyplot as plt
import random

# データの定義(サンプルなのでテキトー)
x = range(100)
y = [random.randint(0, 100) for _ in range(40)]
y.append(10000)
y.extend([random.randint(0, 100) for _ in range(59)])

# グラフの描画
plt.scatter(x, y)
plt.ylim(0, 120)
plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291298/13318319-6210-aacb-4def-7b5eeb4c2846.png)

#### 数値でない軸目盛りを使う
x軸には時折数値でない値を使いたいこともある。

##### 文字列をそのまま軸目盛りに使いたい場合
項目ごとに正規化した値を比較するようなグラフとかの場合は、x軸に項目名の文字列リストを指定するだけで自動的に項目名を軸にしてくれる。

```python:例
#!/usr/bin/env python
# coding: utf-8
from matplotlib import pyplot as plt
import random

# データの定義(サンプルなのでテキトー)
x = ['a', 'b', 'c', 'd', "hoge"]
y = [random.randint(0, 100) for _ in x]

# グラフの描画
plt.bar(x, y)
plt.show()
```
出力↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291298/fb1f0411-4e53-914f-137d-3943ea06d18e.png)


##### プロットは値そのままで、描画上文字列に置換したい場合
任意の文字列を目盛りに振りたい時は、x軸は`plt.xticks(x, ticks)`、y軸は`plt.yticks(y, ticks)`を使う。`x`,`y`は目盛り値のリスト、`ticks`には目盛りに対応するラベル(文字列とか)のリストを渡す。`ticks`を省略すると目盛りの値がそのままラベルになる。
なお、`axes`に設定する場合は`set_xticks()`,`set_yticks()`になる。

```python:x軸の値ラベルを適当な文字列で置換する例
#!/usr/bin/env python
# coding: utf-8
from matplotlib import pyplot as plt
import random

# データの定義(サンプルなのでテキトー)
x = range(10)
xticks = ["id{}".format(x) for x in random.sample(range(100), len(x))]
y = [random.randint(0, 100) for _ in x]

# グラフの描画
plt.plot(x, y)
plt.xticks(x, xticks)
plt.show()
```
出力↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291298/e23539d2-19cc-c4bd-b2fb-5190c9d39f43.png)

また、`xticks`,`yticks`は軸の目盛りを調整するものになるので、デフォルトの目盛り間隔が広すぎる/狭すぎるといったときに、任意の間隔に調整するのにも使う。

```python:目盛り間隔による違い
#!/usr/bin/env python
# coding: utf-8
from matplotlib import pyplot as plt
import random

# データの定義(サンプルなのでテキトー)
x = range(10)
xticks = ["id{}".format(x) for x in random.sample(range(100), len(x))]
y = [random.randint(0, 100) for _ in x]

# グラフの描画
fig = plt.figure()
ax = fig.add_subplot(3, 1, 1)
ax.plot(x, y)
ax.set_title("自動")
ax = fig.add_subplot(3, 1, 2)
ax.plot(x, y)
ax.set_xticks(x)
ax.set_title("全部")
ax = fig.add_subplot(3, 1, 3)
ax.plot(x, y)
ax.set_xticks([i for i in x[::3]])
ax.set_title("3つ飛ばし")
plt.tight_layout()
plt.show()
```
出力↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291298/027e9f38-ffca-c1f4-5116-8ccac2a63a18.png)



##### 軸目盛を表示したくない場合
`plt.xticks([])`のように空のリストを渡せば軸目盛を消せる。
なお、`axes`に設定する場合は`set_xticks()`,`set_yticks()`になる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291298/9cdc481f-43e0-4490-1754-60bb0e7892d8.png)


#### グリッドの表示/非表示
グラフによってはグリッド線のガイドがあると見やすくなるものがある。グリッドは`plt.grid()`で表示できる。

```python:グリッド線表示例
#!/usr/bin/env python
# coding: utf-8
from matplotlib import pyplot as plt
import random

# データの定義(サンプルなのでテキトー)
x = range(10)
y = [random.randint(0, 100) for _ in x]

# グラフの描画
plt.plot(x, y)
plt.grid()
plt.show()
```
出力↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291298/4349e543-f28c-4d18-2fa1-9dffa9e939e9.png)

オプション引数で`axis=`に`"x"`や`"y"`を指定すると、指定した側だけグリッド線を表示できる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291298/e2fe8348-b3a1-233d-0e73-a9cdbd316936.png)


### データの表示関連

#### 線の幅を調整:linewidth
折れ線で接続する線の幅を調整するオプション。
`plt.plot(x, y, linewidth=xx)`または、`plt.plot(x, y, lw=xx)`と指定する。(xxは任意の値)

表示例↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291298/583fb3b0-cd51-5f8a-8198-6d2462327503.png)

#### 線の種類:linestyle
折れ線で接続する線の種類を調整するオプション。
`plt.plot(x, y, linestyle=xx)`または、`plt.plot(x, y, ls=xx)`と指定する。(xxは任意の指定子)

|指定子|種類|
|:--|:--|
|"-"|実線|
|"--"|破線|
|"-."|一点鎖線|
|":"|点線|

表示例↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291298/5c3ccfec-ed63-fc5d-a5c3-75b02f48c318.png)

#### 点の種類:marker
プロットする点の種類を調整するオプション。
`plt.plot(x, y, marker=xx)`と指定する(xxは任意の指定子)
指定子は大量にあるが、主に使うものをピックアップしたのが下表。全種確認したい時は[公式ドキュメント](https://matplotlib.org/3.1.0/api/markers_api.html#module-matplotlib.markers)を参照。

|指定子|種類|
|:--|:--|
|"o"|丸|
|"."|点|
|"s"|四角|
|"D"|ダイヤ|
|"+"|プラス|

表示例↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291298/e95f894d-c39d-69cf-28b8-aebeb73e5b5b.png)

#### 線、点の色:color
描画する線及び点の色を調整するオプション。
`plt.plot(x, y, color=xx)`または`plt.plot(x, y, c=xx)`と指定する(xxは任意の色名)
指定する色名の内、下表の色は短縮指定子を使える。
下表に無い色でもかなりの色がプリセットとして色名指定できるようになっている。詳しくは[公式ドキュメント](https://matplotlib.org/3.1.0/gallery/color/named_colors.html)参照。

|指定子|色|フルネーム|
|:--|:--|:--|
|'r'|赤|"red"|
|'g'|緑|"green"|
|'b'|青|"blue"|
|'c'|シアン|"cyan"|
|'m'|マゼンタ|"magenta"|
|'y'|黄|"yellow"|
|'k'|黒|"black"|
|'w'|白|"white"|

表示例↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291298/349448f8-ccb2-5407-6eef-913f6d5bab35.png)

### その他

#### 凡例の表示
プロットしたデータの凡例を表示する機能。

表示例↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291298/b2b2cb5a-4d8b-6fd5-2a9a-2dd9fb59e07d.png)

```python:表示例のコード
#!/usr/bin/env python
# coding: utf-8
from matplotlib import pyplot as plt
import random


# データの定義(サンプルなのでテキトー)
x = range(10)

fig, ax = plt.subplots(1, 1)
for i in range(3):
    lower = 100 * i
    upper = lower + 100
    y = [random.randint(lower, upper) for _ in x]
    ax.scatter(x, y, label="random:{}-{}".format(lower, upper))
plt.legend()
plt.tight_layout()
plt.show()
```

なお、凡例の表示位置を`plt.legend(loc=xx)`で指定できる。(xxは任意のパターンの指定子)
なお、`axes`の場合も同じメソッド名でよい。
指定パターンは下表の通り。

|指定子|位置|
|:--|:--|
|"best"|自動(標準)|
|"upper left"|左上|
|"center left"|左中央|
|"lower left"|左下|
|"upper center"|中央上|
|"center"|中央|
|"lower center"|中央下|
|"upper right"|右上|
|"center right"|右中央|
|"lower right"|右下|

表示例↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291298/440d8209-c5b5-a524-156a-390a080da917.png)

```python:表示例のコード
#!/usr/bin/env python
# coding: utf-8
from matplotlib import pyplot as plt
import random


# データの定義(サンプルなのでテキトー)
x = range(10)

# グラフの描画
fig = plt.figure()
locs = [
    "{} {}".format(a, b)
    for b in ["left", "center", "right"]
    for a in ["upper", "center", "lower"]
    if not (a == "center" and b == "center")
]
locs.insert(len(locs) // 2, "center")
for j, loc in enumerate(locs, 1):
    for i in range(3):
        lower = 100 * i
        upper = lower + 100
        y = [random.randint(lower, upper) for _ in x]
        ax = fig.add_subplot(3, 3, j)
        ax.scatter(x, y, label="{}-{}".format(lower, upper))
        ax.set_title("\"{}\"".format(loc))
        ax.legend(loc=loc)
plt.tight_layout()
plt.show()
```

#### グラフタイトルの表示
グラフに任意のタイトルを書く機能。
`plt.title(xx)`で指定する。(xxは表示したい任意の文字列)なお、`axes`の場合は`ax.set_title(xx)`となる。

表示例↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291298/e66cf814-d74e-fadf-5286-dd2a2f0f7604.png)

```python:表示例のコード
#!/usr/bin/env python
# coding: utf-8
from matplotlib import pyplot as plt
import random


# データの定義(サンプルなのでテキトー)
x = range(10)
y = [random.randint(0, 100) for _ in x]

# グラフの描画
plt.plot(x, y)
plt.title("タイトル")
plt.show()
```

#### 任意のテキストの表示
`axes`内の任意座標を指定して、指定したテキストを描画する機能。
`plt.text(x, y, s)`で指定する。

表示例↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291298/1545509b-fec4-a6ff-242a-1c1f56362871.png)

```python:表示例のコード
#!/usr/bin/env python
# coding: utf-8
from matplotlib import pyplot as plt
import random


# データの定義(サンプルなのでテキトー)
x = range(10)
y = [random.randint(0, 100) for _ in x]

# グラフの描画
plt.plot(x, y)
plt.text(5, 50, "真ん中")
plt.grid()
plt.show()
```

# 終わりに

普段よく使う機能をまとめてみたが、かなりのボリュームになってしまった…
`matplotlib`にはまだまだ使ったことない機能も沢山あるが、次はアニメーションをまとめたいと思います。

# 参考

- [matplotlib 公式ドキュメント](https://matplotlib.org/3.1.0/index.html)
- [matplotlib入門-りんごがでている](http://bicycle1885.hatenablog.com/entry/2014/02/14/023734)
- [matplotlib基礎 | figureやaxesでのグラフのレイアウト](https://qiita.com/tsuruokax/items/90167693f142ebb55a7d)
- [matplotlib の figure（図） と axes（座標軸）－python](http://ailaby.com/matplotlib_fig/)