matplotlibでのプロットの基本

matplotlibを使ってプロットする時、プロットするたびに関数を調べることが多く、効率が悪いので、よく使うものについて情報まとめておく。

# matplotlib, pyplot, pylabの関係性
まず、「各々の言葉が何を指すのか」から。

- Matplotlib: データビジュアライゼーションパッケージの全体を指す。
- pyplot: matplotlibパッケージ内のモジュールを指す。欲しいプロットを作るために暗黙的かつ自動的に図形や軸を作成するインターフェース。基本的にはこのモジュール越しにmatplotlibの機能を活用する。以下のようにインポートして置くのが一般的。

```py
import matplotlib.pyplot as plt
```


- pylab: pyplotとnumpyモジュールを一括インポートするものであるが、現在では特にJupyter notebookやipythonカーネルを使っている場合に奨励されないので、注意すること。

# グラフの構成要素

![スクリーンショット 2018-01-02 20.50.29.png](https://qiita-image-store.s3.amazonaws.com/0/157720/a9fb1d28-643c-ee23-50d7-f49ec2ef99f9.png)

※上図は下記サイトより抜粋
https://www.datacamp.com/community/tutorials/matplotlib-tutorial-python

- figure: グラフや文字など、全てが描かれるウインドウ(またはページ)を指す。全ての要素の最上位レベルのコンポーネント
- Axes: plot()やscatter()でデータがプロットされる領域を指す。tickやlabelなどの要素が関連付けれらている。fugureとAxesが別れて定義されているため、一つのfigure上に複数のAxes(グラフ)を表示することができる。

# クイックにデータをプロットする
FigureやAxesをインスタンス化せずに、クイックにデータの中身を可視化したい場合に便利な使い方である。この状態でカスタマイズのコードを書くと、変更は現在表示されているfigureとAxesを念頭に置いて変更される。

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

# データ生成
x = np.linspace(0, 10, 100)
y = x + np.random.randn(100) 

# プロット
plt.plot(x, y, label="test")

# 凡例の表示
plt.legend()

# プロット表示(設定の反映)
plt.show()
```

実行結果: 
![Unknown.png](https://qiita-image-store.s3.amazonaws.com/0/157720/87fa7020-11eb-607f-2457-7b0ae20e0e0c.png)


# インスタンスを明言してプロットする
1. figureインスタンスを生成する。
2. figureインスタンスのadd_subplot()メソッドでaxesを生成
figure.add_subplot()は、描画するグラフの位置を指定する値を指定する。   
※ここでは(111)すなわち「1行1列の1つ目」の位置を指定する。
3. Axesオブジェクトのplot()メソッドで指定データをプロットする。

インスタンスを明言するとき、figure()のfigsize引数にサイズのタプル(インチ単位)を渡すことで、グラフの大きさを変えられる。

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

# データ生成
x = np.linspace(0, 10, 100)
y = x + np.random.randn(100) 

# Figureの初期化
fig = plt.figure(figsize=(12, 8)) #...1

# Figure内にAxesを追加()
ax = fig.add_subplot(111) #...2
ax.plot(x, y, label="test") #...3

# 凡例の表示
plt.legend()

# プロット表示(設定の反映)
plt.show()
```

実行結果: 
![Unknown-1.png](https://qiita-image-store.s3.amazonaws.com/0/157720/afc6a27d-03f4-7516-3ac0-25cc6aaf53fd.png)


ちなみに、figure.add_subplot()ではなく、figure.add_axes()を使ってプロットすることもできる。
add_axes()は引数に、グラフ座標の左下の位置情報(x, y)と幅と高さのリスト(以下の例だと[0,0,1,1])を渡す。

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

# データ生成
x = np.linspace(0, 10, 100)
y = x + np.random.randn(100) 

# Figureの初期化
fig = plt.figure(figsize=(12, 8))

# Figure内にAxesを追加
ax = fig.add_axes([0,0,1,1])
ax.plot(x, y, label="test")

# 凡例の表示
plt.legend()

# プロット表示(設定の反映)
plt.show()
```

fig.add_axes()とfig.add_subplot()は、どちらもAxesオブジェクトを返す。違いは、軸が追加される時のメカニズムである。

- figure.add_axes()は上記の通り、左下のx座標、y座標、幅、高さのリストを渡す。返されるAxesオブジェクトは絶対座標に配置される。
- figure.add_subplot()はAxesオブジェクトを特定の位置に置かない代わりにサブプロットグリッドに従って配置する。

ほとんどの場合、add_subplot()を使用して軸を作成する。配置が重要な場合のみ、add_axes()を使用する。

# 複数のグラフ(Axes)を一つのページ(figure)にプロットする
## 方針1: figureオブジェクト内にadd_subplot()メソッドで追加する。
```Python
import matplotlib.pyplot as plt
import numpy as np

# データ生成
x = np.linspace(0, 10, 100)
y1 = np.sin(x)
y2 = np.cos(x)

# プロット領域(Figure, Axes)の初期化
fig = plt.figure(figsize=(12, 8))
ax1 = fig.add_subplot(131)
ax2 = fig.add_subplot(132)
ax3 = fig.add_subplot(133)

# 棒グラフの作成
ax1.bar([1,2,3],[3,4,5])
ax2.barh([0.5,1,2.5],[0,1,2])
ax3.scatter(y1, y2)

# 水平線、垂直線を入れる
ax3.axhline(0.45)
ax3.axvline(0.65)

plt.show()
```

## 方針2: figureオブジェクトではなく、subplotsオブジェクトを生成する。
plt.subplots()は、figureオブジェクトのインスタンスと、指定した個数のAxesオブジェクトのインスタンスを返す。
グラフの大きさを変えたい時は、subplots()にfigsize引数を指定すれば良い。

```python
import matplotlib.pyplot as plt
# プロット領域の初期化(今回は1行2列の配列)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 8))

# データのプロット
ax1.bar([1,2,3], [3,4,5])
ax2.barh([0.5, 1, 2.5], [0,1,2])

plt.show()
```

# Axesオブジェクトへのプロットメソッド
よく使うやつだけまとめる。Axesの部分をpltに書き換えると、インスタンスを明言せずにプロットをクイックに描ける。
(ex: plt.plot()など)

| 記法 | グラフの種類 | ドキュメンテーション |
|:-----------:|:------------:|:------------:|
| Axes.plot() | 折れ線グラフ | [Axes.plot()][1] |
| Axes.scatter() | 散布図 | [Axes.scatter()][2] |
| Axes.bar() | 縦棒グラフ | [Axes.bar()][3] |
| Axes.barh() | 横棒グラフ | [Axes.barh()][4] |
| Axes.hist() | ヒストグラム | [Axes.hist()][5] |
| Axes.boxplot() | ボックスプロット | [Axes.boxplot()][6] |
| Axes.violinplot() | バイオリンプロット | [Axes.violinplot()][7] |
| Axes.contour() | コンタープロット | [Axes.contour()][8] |
| Axes.pcolor() | 擬似カラープロット(ヒートマップ) | [Axes.pcolor()][12] | 
| Axes.imshow() | 画像 | [Axes.imshow()][9] |
| Axes.axhline() | 水平線 | [Axes.axhline()][10] |
| Axes.axvline() | 垂直線 | [Axes.axvline()][11] |

[1]:https://matplotlib.org/api/_as_gen/matplotlib.axes.Axes.plot.html#matplotlib.axes.Axes.plot
[2]:https://matplotlib.org/api/_as_gen/matplotlib.axes.Axes.scatter.html#matplotlib.axes.Axes.scatter
[3]:https://matplotlib.org/api/_as_gen/matplotlib.axes.Axes.bar.html#matplotlib.axes.Axes.bar
[4]:https://matplotlib.org/api/_as_gen/matplotlib.axes.Axes.barh.html#matplotlib.axes.Axes.barh
[5]:https://matplotlib.org/api/_as_gen/matplotlib.axes.Axes.hist.html#matplotlib.axes.Axes.hist
[6]:https://matplotlib.org/api/_as_gen/matplotlib.axes.Axes.boxplot.html#matplotlib.axes.Axes.boxplot
[7]:https://matplotlib.org/api/_as_gen/matplotlib.axes.Axes.violinplot.html#matplotlib.axes.Axes.violinplot
[8]:https://matplotlib.org/api/_as_gen/matplotlib.axes.Axes.contour.html#matplotlib.axes.Axes.contour
[9]:https://matplotlib.org/api/_as_gen/matplotlib.axes.Axes.imshow.html#matplotlib.axes.Axes.imshow
[10]:https://matplotlib.org/api/_as_gen/matplotlib.axes.Axes.axhline.html#matplotlib.axes.Axes.axhline
[11]:https://matplotlib.org/api/_as_gen/matplotlib.axes.Axes.axvline.html#matplotlib.axes.Axes.axvline
[12]:https://matplotlib.org/devdocs/api/_as_gen/matplotlib.axes.Axes.pcolor.html

また、グラフの細かな設定をするためにメソッドもよく使うやつをまとめる。

| 記法 | 操作の内容 |
|:-----------:|:------------:|
| Axes.set_title() | タイトルを設定 |
| Axes.set_xlabel()| x軸の名前を設定 |
| Axes.set_ylabel()| y軸の名前を設定 |
| Axes.set_xlim() | x軸の範囲を指定 |
| Axes.set_ylim() | y軸の範囲を指定 |
| Axes.legend() | 凡例を表示 |
| Axes.grid() | グリッド線を表示 |

Axesオブジェクトの詳細は以下を参照すること。
https://matplotlib.org/api/axes_api.html

# その他の細かなTips
## プロットの色指定
各種プロットメソッドは引数にcolor(c)という引数を持っているが、色の指定方法はいくつかやり方がある。代表的な手法だけまとめる。

- 0~1の範囲のfloat値で表現されたRGBを指定するタプル(ex. (0,0,1)は青)

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

x = np.linspace(0, 100, 101)
y = np.random.randn(101)

plt.plot(x, y, color=(0,0,1))

plt.show()
```

- 16進数表記のカラーコードを指定する文字列 (ex. "#0F0F0F")

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

x = np.linspace(0, 100, 101)
y = np.random.randn(101)

plt.plot(x, y, color="#0F0F0F")

plt.show()
```

- グレーレベルを0~1のfloat値で指定した文字列 (ex. "0.5")

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

x = np.linspace(0, 100, 101)
y = np.random.randn(101)

plt.plot(x, y, color="0.5")

plt.show()
```

- "b"(青), "g"(緑), "r"(赤), "c"(シアン), "m"(マゼンダ), "y"(イエロー), "k"(黒), "w"(白)で指定する

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

x = np.linspace(0, 100, 101)
y = np.random.randn(101)

plt.plot(x, y, color="g")

plt.show()
```

色に関する詳細は下記参照
https://matplotlib.org/api/colors_api.html

## カラーマップについて
ヒートマップや散布図でカラーマップを使う時、matplotlibが定義しているカラーバリエーションを使うことができる。

詳細は以下を参照
https://matplotlib.org/tutorials/colors/colormaps.html

## プロットの線種指定
linestyle(ls)引数で指定する。linewidth引数で太さも変更できる。
(scatterプロットの場合、linewidthで指定するのはマーカーの輪郭線の太さになることに注意)

| 記法 | 線種 |
|:-----------:|:------------:|
| ls=":" | 点線 |
| ls="-." | 一点鎖線 |
| ls="--" | 破線 |
| ls="-" | 実線 |

詳細は下記参照
https://matplotlib.org/gallery/lines_bars_and_markers/line_styles_reference.html

## プロットのマーカー指定
marker引数で指定する。大きさはmarkersize引数で指定する。scatterプロットはs引数でデータ点のサイズを指定する。ここでもよく使いそうなものだけまとめる。

| 記法 | マーカー | 記法 | マーカー |
|:-----------:|:------------:|:-----------:|:------------:|
| marker="." | 点 | marker="*" | 星 |
| marker="," | ピクセル | marker="1" | Y |
| marker="o" | 丸 | marker="2" | Y(上下反転) |
| marker="v" | 下三角形 | marker="3" | Y(90度時計回り) |
| marker="^" | 三角形 | marker="4" | Y(90度反時計周り) |
| marker="<" | 左三角形 | marker="+" | + |
| marker=">" | 右三角形 | marker="x" | x |
| marker="s" | 四角形 | marker="X" | x(filled) |
| marker="p" | 五角形 | marker="D" | ひし形 |
| marker="h" | 六角形 | marker="d" | 細いひし形 |
| marker="8" | 八角形 | marker="" | マーカー無し |

詳細は下記参照
https://matplotlib.org/api/markers_api.html


## Axesの削除
subplotの中から不必要なものを**figure.delaxes()**メソッドで削除できる。

```python
import matplotlib.pyplot as plt

# figureの初期化
fig = plt.figure()
ax1 = fig.add_subplot(131)
ax2 = fig.add_subplot(132)
ax3 = fig.add_subplot(133)

# データのプロット
ax1.bar([1,2,3],[3,4,5])
ax2.barh([0.5,1,2.5],[0,1,2])
ax2.axhline(0.45)
ax1.axvline(0.65)
ax3.scatter(np.linspace(0, 1, 5), np.linspace(0, 5, 5))

# ax3を削除する
fig.delaxes(ax3)
plt.show()
```

## 凡例をプロットの外に置きたい。
- Axes.legend()メソッドのlocation(loc)引数に"center left"や"right right"などのように指定する
- bbox_to_anchor引数を追加して、凡例を配置する座標を含むタプルを渡す。
ex) ax.legend(bbox_to_anchor = (1.1、1.05)): 凡例は作図領域の右上隅に置かれる。

## 詰め込みすぎて、複数のグラフが重なり合ってしまうのを解消したい
plt.show()の前に**plt.tight_layout()**を入れる。

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

# データ生成
x = np.linspace(0, 10, 100)
y1 = np.sin(x)
y2 = np.cos(x)

# プロット領域(Figure, Axes)の初期化
fig = plt.figure()
ax1 = fig.add_subplot(131)
ax2 = fig.add_subplot(132)
ax3 = fig.add_subplot(133)

# 棒グラフの作成
ax1.bar([1,2,3],[3,4,5])
ax2.barh([0.5,1,2.5],[0,1,2])
ax3.scatter(y1, y2)

# 水平線、垂直線を入れる
ax3.axhline(0.45)
ax3.axvline(0.65)

# 3つのaxesを並べると、縦軸の数字が隣のグラフと重なってしまう。。
plt.tight_layout()
plt.show()
```

## 作ったグラフを保存したい
### 方針1: png形式で保存する。
**plt.savefig()**メソッドを使う

```python
import matplotlib.pyplot as plt

# figureの初期化
fig = plt.figure()
ax1 = fig.add_subplot(131)
ax2 = fig.add_subplot(132)
ax3 = fig.add_subplot(133)

# データのプロット
ax1.bar([1,2,3],[3,4,5])
ax2.barh([0.5,1,2.5],[0,1,2])
ax2.axhline(0.45)
ax1.axvline(0.65)
ax3.scatter(np.linspace(0, 1, 5), np.linspace(0, 5, 5))

plt.tight_layout()
plt.show()

# figureの保存
plt.savefig("foo.png")
```

※ subplot単位で保存はできない。あくまでfigure全体を保存する。

plt.savefig()クラスの詳細は以下を参照
https://matplotlib.org/devdocs/api/_as_gen/matplotlib.pyplot.savefig.html

### 方針2: pdf形式で保存したい。
**PdfPages**(matplotlibが持っているpdfバックエンド)を使う。

```python
# PdfPagesのインポート
from matplotlib.backends.backend_pdf import PdfPages
import matplotlib.pyplot as plt

# figureの初期化
fig = plt.figure()
ax1 = fig.add_subplot(131)
ax2 = fig.add_subplot(132)
ax3 = fig.add_subplot(133)

# データのプロット
ax1.bar([1,2,3],[3,4,5])
ax2.barh([0.5,1,2.5],[0,1,2])
ax2.axhline(0.45)
ax1.axvline(0.65)
ax3.scatter(np.linspace(0, 1, 5), np.linspace(0, 5, 5))

# pdfファイルの初期化
pp = PdfPages('multipage.pdf')

# figureをセーブする
pp.savefig()

# pdfファイルをクローズする。
pp.close()
```

## プロットのクロージング
GUI環境でシャットダウンしているとあまり気にならないが、ポリシーがあるようなので一応まとめる。

- **plt.cla()**: Axesをクリア
- **plt.clf()**: figureをクリア
- **plt.close()**: プロットを表示するためにポップアップしたウィンドウをクローズ


# その他参考サイト
本家のギャラリー:
https://matplotlib.org/gallery.html
ソースコード付きなので、気になるやつをコピペしていじるだけでイケてるグラフを作れる。