【necoplot】長文matplotlibを少し簡潔にできるライブラリ



# 記事の背景
`necoplot`というライブラリを作ったのでその紹介記事です｡
matplotlibをちょっとだけ簡潔に書くことができます｡

Githubにも公開しています｡ -> [Github necoplot](https://github.com/guneco/necoplot)

# この記事の想定読者
- Pythonでmatplotlibを使う
- matplotlibの作図をもう少し簡潔にやりたい
- 人がつくったライブラリの話に興味がある(※)

※ 筆者は独学の非エンジニアです｡
「そういう人でもこういうのつくれるんだ〜」
くらいに思っていただければ幸いです｡

# 結論
- ものぐさな筆者が自分のためにつくりました
- 引数をわりと適当に入れてもわりと大丈夫なようにつくりました
- matplotlibの引数やメソッドはだいたいそのまま使えるはずです

# ライブラリの特徴とメリット
- wiht文を使って一部のコード記述を簡略化してます
- 図の設定を引数を介して行うことで､コードの行数を削減してます
- デフォルト値を設定でき､引数入力の手間を省けるようにしています

# インストール
`pip install necoplot`でインストールできます｡
(作りたてなので今後挙動の変更がいろいろ生じるかもしれませんm(_ _)m)

# 使用例
## 下準備

`import necoplot as neco`としておきます｡[^2]
[^2]:nekoではなくnecoなのは､`nekoplot`が先に存在していて､名前が被ったためです｡

```python
import necoplot as neco
import numpy as np

xx = np.linspace(-5,5,20)
yy = xx*xx
```
以降xの2乗のグラフを題材にしていきます｡

## 最もシンプルな例
`with neco.plot() as ax:`という形式で使います｡[^1]
まずは引数を何も入れないプレーンな状態から｡

```python
# Basic
with neco.plot() as ax:
    ax.plot(xx, yy)
```
![example01_basic](https://user-images.githubusercontent.com/104950574/167246388-d9b5fe6b-dd30-4609-9ded-e96fa6016959.jpeg)

簡素な図ですが､2行で作図できました｡
処理としては､with文に入る時にfigureを作って､出る時にplt.show()しています｡

## figureの設定
いわゆる`fig = plt.figure()`の部分です｡
necoplotでは`neco.plot()`の引数に設定する値を投入できます｡

画像の縦横比や色などを変更してみます｡

```python
# Config figiure
with neco.plot(figsize=(4,4), dpi=80, facecolor='silver') as ax:
    ax.plot(xx, yy)
```
![example02_config_figure](https://user-images.githubusercontent.com/104950574/167246391-5f91a775-a8d6-48b6-bfee-7304efe7076f.jpeg)

地味な図ではありますが､こちらも2行で作図できました｡

## axの設定1: figureの設定と一緒に
figureの次はax(図)の設定をします｡
axもfigureと同じように`neco.plot()`の引数として設定することができます｡

本来は別々に扱う引数ですが､ちょっとした設定で行追加するのが面倒だったので､
figureと同じところから引数を代入できるようにしています｡

今度はfigureの設定と同時にaxの設定もします｡
xの範囲を(-5〜0)の範囲に限定した図をつくります｡

```python
# Config ax by plot() 
with neco.plot(figsize=(6,4), xlim=(-5,0)) as ax:
    ax.plot(xx, yy) 
```
![example03_config_by_plot](https://user-images.githubusercontent.com/104950574/167246392-efc17842-a9ad-4fe9-9823-a3ce0c32281a.jpeg)

裏側では投入された引数を選り分けて､figure用とax用に割り振ったりしています｡

#### 余談: figureとax
figureとaxの違いは､matplotlibの少しややこしいところです｡

筆者の雑な理解でいうと､たとえるならfigureは画板です｡
その上に具体的な図(ここではax)が載るイメージです｡

よって基本的にfigureは1つですが､axは複数使うことができます｡
実際にこのあと複数のaxを使うケースも見ていきます｡

## axの設定2: 関数を使って設定する
先ほどはfigureと同じ`neco.plot()`からaxの設定を行いましたが､
今度は`neco.config_ax()`を使ってaxの設定を行います｡

`neco.plot()`だと少し煩雑になる場合には､こちらのほうがスッキリします｡
この場合は､`neco.config_ax()`の結果を`neco.plot()`に渡してあげます｡

ここではxの範囲の指定に加えて､タイトルをつけて､xのスケールを対数スケールに変更しています｡
(図の設定に深い意味はありません)

```python
# Config ax by using config_ax()
ax0 = neco.config_ax(xlim=(1,5), title='title', xscale='log')

with neco.plot(ax0, figsize=(6,4)) as ax:
    ax.plot(xx, yy)
```
![example04_config_ax](https://user-images.githubusercontent.com/104950574/167246394-13d89094-f43f-4d66-8adf-f8b59a3fb4ca.jpeg)

実は､ここでの`ax0`は関数なので､with文の中でそのまま使うことはできません｡

`neco.config_ax()`は関数をつくる関数ということになります｡
with文に入る前はax(図)を貼るfigure(画板)が準備できないので､こういう仕様になりました｡

## axの設定3: axに直接設定を加える

`with neco.plot() as ax:`のaxは､matplotlibのAxesクラスです｡

よって､matplotlibのaxでやっていたことはだいたい再現できるはずです｡
(例: ax.plot(), ax.bar(), ax.set_xlim()…etc)

この場合もaxを使って追加の設定をいろいろしています｡

```python
# Config ax directry
with neco.plot() as ax:
    ax.plot(xx, yy, label='x squared')
    ax.legend()
    ax.hlines(y=25, xmin=-5, xmax=5)
```
![example05_config_directry](https://user-images.githubusercontent.com/104950574/167246396-d5fefe64-1db5-4252-8ab0-1d119f77a113.jpeg)

なんとなくドラえもんのポケットみたいですね｡

## 図の保存
図の保存には`neco.save()`を使います｡
`show`オプションをFalseにすると､図の描写はせずに画像の保存だけ実行してくれます｡

```python
# Save figure
with neco.plot() as ax:
    ax.plot(xx, yy)
    neco.save('sample.png', show=False)
```
`show=False`なので画像も省略します｡

## 複数のax(図)を1つのfigure(画板)に描写する
複数の図を描写する際には`neco.mplot()`を使います｡
multiplotの略でmplotです｡[^3]
[^3]:短いほうが書きやすいしかわいいので､可読性を少し犠牲にしました｡

複数の図を描写するので､画板のどこに配置するかの「番地」を指定する必要があります｡

最初の引数の`121`や`122`がそれにあたります｡
この3桁で(行数,列数,順番)が表現されています｡

121は1行2列の1番目､122は1行2列の2番目を指定しています｡

```python
# Plot multiple with mplot()
ax0 = neco.config_ax(121, xlim=(-5, 0),title='Left side')
ax1 = neco.config_ax(122, xlim=(0, 5), title='Right side', yticks=[])

with neco.mplot([ax0, ax1]) as p:
    p.axes[0].plot(xx, yy)
    p.axes[1].plot(xx, yy)
```
![exmaple08](https://user-images.githubusercontent.com/104950574/167278508-0a7483d3-08f7-495f-9c02-9a689a546dde.jpeg)

見た目をスッキリさせるために､2つの図の間のy軸の目盛りを省略してみました｡
この3桁の記法で最大9つの図を1枚の画板に収録できます｡

## よく使う引数をデフォルト値に設定する
いくつか図を作っていると､複数の図で共通して使う引数が出てくることがあります｡
毎回同じ引数を書くのがめんどうなので､デフォルト値の引数を設定できるようにしました｡

あくまでデフォルト値を設定しているだけなので､
個別に他の値を代入すればそちらが優先して反映されます｡

以下の図は直接`neco.plot()`に何も引数を与えなくても､
`neco.config_user_parameters()`で設定したタイトルが反映されている例です｡

```python
# Config default values
neco.config_user_parameters(title='New default title!')

with neco.plot() as ax: # 引数を設定していない
    ax.plot(xx, yy)
```
![example07_config_params](https://user-images.githubusercontent.com/104950574/167246398-33484f92-f70b-4629-b8cd-86854ed1a2c3.jpeg)


タイトルを設定していなくても､新しい画像にタイトルがつきました｡
デフォルト値の再設定にはデコレータなどを使っています｡


## 設定をリセットする
設定したデフォルト値をすべて取り消したい場合は､`neco.reset()`を使います｡

```python
# Reset config
neco.reset()
```
ちなみに､猫リセットといえば…[^4]
[^4]:お気づきの方もいるかもですが､「ずっと真夜中でいいのに｡」の『猫リセット』をモチーフ(？)にしています｡
筆者はずとまよのファンなので､この関数を実装することをモチベーションに開発がんばりました｡
[ずっと真夜中でいいのに。『猫リセット』MV (ZUTOMAYO - Neko Reset)](https://youtu.be/Sfz5TpCRSiI)

## もう少し手の込んだ例
最後に､matplotlib公式を参考にもう少し手の込んだ作図をしてみます｡[^5]
[^5]:https://matplotlib.org/3.5.0/gallery

### グルーブごとの棒グラフ
#### データの準備
```python
import numpy as np

title = 'Scores by group and gender'
labels = ['G1', 'G2', 'G3', 'G4', 'G5']
ylabel = 'Scores'

x = np.arange(len(labels))
men_means = [20, 34, 30, 35, 27]
women_means = [25, 32, 34, 20, 25]
width = 0.35
```
#### 作図
```python
ax0 = neco.config_ax(title=title, ylabel=ylabel, xticks=x, xticklabels=labels)

with neco.plot(ax0) as ax:
    ax.bar(x - width/2, men_means, width, label='Men')
    ax.bar(x + width/2, women_means, width, label='Women')
    ax.legend()
```
![example09_grouped_bars](https://user-images.githubusercontent.com/104950574/167284557-513bd344-a385-484e-86f6-387fbb518675.jpeg)

### 散布図と棒グラフ
#### データの準備
```python
np.random.seed(0)

x0, y0 = np.random.normal(size=(2, 200))

x1 = np.arange(5)
y11, y12 = np.random.randint(1, 25, size=(2, 5))
width = 0.25
```
#### 作図
```python
ax0 = neco.config_ax(121)
ax1 = neco.config_ax(122, xticks=range(5), xticklabels=['a', 'b', 'c', 'd', 'e'])

with neco.mplot([ax0, ax1]) as p:
    p.axes[0].plot(x0, y0, 'o')
    p.axes[1].bar(x1, y11, width)
    p.axes[1].bar(x1+width, y12, width)
```

![example10_scatter_and_bars](https://user-images.githubusercontent.com/104950574/167284560-215b46eb-db96-4928-9eb0-a756e6cdb597.jpeg)

ずっと放物線のグラフばっかり紹介していましたが､
それ以外のグラフもきちんと描けることが確認できました｡

# 雑感
- エンジニアじゃなくても､その気があればライブラリをつくって公開できることがわかった
- 自分でライブラリを作るとPythonの色んな仕様を調べる機会になるので､よい勉強になった

# まとめ
- `necoplot`を使って(比較的)簡潔に作図できるようになった！
- ライブラリを自分でつくると色々勉強になる！

[^1]:コンテキストマネージャーを使う方法は[あきとしのスクラップノート](https://akitoshiblogsite.com/)さんの以下の記事を参考にさせていただきました｡
[[python] context manger を使ってmatplotlibの図を大量生産する](https://akitoshiblogsite.com/context-manager-matplotlib/)
あきとしさんの作成されたライブラリ`contextplt`は以下のリンクをご参照ください｡
https://toshiakiasakura.github.io/contextplt/index.html