necoplot; 少しだけmatplotlibを簡潔に書けるラッパー

27367 ワード

記事の背景

necoplotというライブラリを作ったのでそのことについて記事にします。
matplotlibを少しだけ簡潔に書くことができます。

Githubでも公開しています。 -> Github necoplot

この記事の想定読者

  • Pythonでmatplotlibを使う
  • matplotlibの作図をもう少し簡潔にやりたい
  • 人がつくったライブラリの話に興味がある(※)

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

結論

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

ライブラリの特徴とメリット

  • wiht文を使って一部のコード記述を簡略化してます
  • 図の設定を引数を介して行うことで、コードの行数を削減してます
  • デフォルト値を設定でき、引数入力の手間を省けるようにしています

インストール

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

使用例

下準備

import necoplot as necoとしておきます。[1]

import necoplot as neco
import numpy as np

xx = np.linspace(-5,5,20)
yy = xx*xx

以降xの2乗のグラフを題材にしていきます。

最もシンプルな例

with neco.plot() as ax:という形式で使います。[2]
まずは引数を何も入れないプレーンな状態から。

# Basic
with neco.plot() as ax:
    ax.plot(xx, yy)

example01_basic

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

figureの設定

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

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

# Config figiure
with neco.plot(figsize=(4,4), dpi=80, facecolor='silver') as ax:
    ax.plot(xx, yy)

example02_config_figure

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

axの設定1: figureの設定と一緒に

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

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

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

# Config ax by plot() 
with neco.plot(figsize=(6,4), xlim=(-5,0)) as ax:
    ax.plot(xx, yy) 

example03_config_by_plot

裏側では投入された引数を選り分けて、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のスケールを対数スケールに変更しています。
(図の設定に深い意味はありません)

# 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

実は、ここでの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を使って追加の設定をいろいろしています。

# 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

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

図の保存

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

# 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]

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

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

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

# 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

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

よく使う引数をデフォルト値に設定する

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

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

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

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

with neco.plot() as ax: # 引数を設定していない
    ax.plot(xx, yy)

example07_config_params

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

設定をリセットする

設定したデフォルト値をすべて取り消したい場合は、neco.reset()を使います。

# Reset config
neco.reset()

ちなみに、猫リセットといえば…[4]

もう少し手の込んだ例

最後に、matplotlib公式を参考にもう少し手の込んだ作図をしてみます。[5]

グルーブごとの棒グラフ

データの準備

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

作図

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

散布図と棒グラフ

データの準備

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

作図

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

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

雑感

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

まとめ

  • necoplotを使って(比較的)簡潔に作図できるようになった!
  • ライブラリを自分でつくると色々勉強になる!
脚注
  1. nekoではなくnecoなのは、nekoplotが先に存在していて、名前が被ったためです。 ↩︎

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

  3. 短いほうが書きやすいしかわいいので、可読性を少し犠牲にしました。 ↩︎

  4. お気づきの方もいるかもですが、「ずっと真夜中でいいのに。」の『猫リセット』をモチーフ(?)にしています。
    筆者はずとまよのファンなので、この関数を実装することをモチベーションに開発がんばりました。
    ずっと真夜中でいいのに。『猫リセット』MV (ZUTOMAYO - Neko Reset) ↩︎

  5. https://matplotlib.org/3.5.0/gallery ↩︎