はじめに
matplotlibには2つの書き方があります。1つがpyplotモジュールを使った書き方(MATLABの書き方をベースにしているらしい)で、もう一つがfig, ax = plt.subplots()などの後にax.plotなどを使う書き方です。(参考: The Lifecycle of a Plot)
どっちの書き方でも描ければ良いといえば良いのですが、細かい調整をしようとするとオブジェクト指向の書き方を身に付けた方が良いらしいです。
…という話を↓の記事で読んだ記憶はあるのですが、それ以来特に気にせず使っており、結局全然オブジェクト指向でのmatplotlibの書き方分かっていない(そもそもmatplotlib使って可視化を行うのが苦手)ので、これを機にちょっとでも克服しよう、というのが今日の記事の目標です。
早く知っておきたかったmatplotlibの基礎知識、あるいは見た目の調整が捗るArtistの話
オブジェクト指向な書き方
Artistとは
参考: Artist tutorial
matplotlibの考え方としては、図として書かれている線やら点やらラベルは全てArtistと呼ばれるオブジェクトです。Artistが自身のプロパティを持っており、それらを変更してあげることで見え方を変えてあげることができます。
原理的には、真っ白なキャンバスを用意してこれらのArtistをぺたぺた貼っていけば良いのですが、これだけ色々な要素があると扱いが大変です。(グラフが1つならまだしも、複数作りたいとなった時に管理が大変そうですよね)
実際には、matplotlibではこれらの表示される側のArtistをprimitiveと呼び、これらのオブジェクトを表示する側のArtistをcontainersと呼びます。このcontainersに属するArtistは複数あるのですが、それらがいい感じに階層構造になっていて、担当範囲をいい感じに受け持って(後で紹介するAxesオブジェクトの負担が重すぎる気もしますが)、図全体をいい感じに構成してくれています。
という訳で、matplotlibで「どのように表示するのか」を理解するには、表示する側のArtistであるcontainersを理解できれば良さそう、ということが分かりました。次からはcontainersについてもう少し詳細に見ていきます。
containersの階層構造
containersにはFigure, Axes, Axis, Tickの4つがあり、これらがこの順序で階層構造になっています。イメージとしては下の図が分かりやすいです(なぜかTickが存在しませんが)
各containerの役割は、
- Figure: キャンバス
- Axes: データをプロットするところ
- Axis: 目盛のラベルと線、グリッド、軸のラベルを扱う
- Tick: 目盛のlineとかtextのprimitiveを持っているところ(正直Axisとの違いがあんまり分かっていないのですが、AxisはTickにアクセスして目盛などの操作を行えるが、実際の値を持っているのがTickというイメージだと思います)
Figureは複数のAxesを持つことができ、Axesはx, y軸(3次元の場合はz軸も)のAxisを持つことができ、Axisは複数のtickを持つことができる、という感じです。
言葉だけでは分かりづらいと思うので、上の階層から実際に触っていくことで、containersへの理解を深めていきます。
Figure
figureはplt.figure()によって作ることができます。
fig = plt.figure() # an empty figure with no Axes
この時点ではAxesも何もないので、真っ白なキャンバスが用意されただけの状態です。
実際これで実行しても、Axesが0のFigureがあるだけですよ、と表示されて何も映りません
試しにAxesをこのFigureに追加して見ましょう。既に作られたFigureにはadd_subplotメソッドでaxesオブジェクトを追加します。
とりあえず四角と目盛りがつきました。FigureとAxesは階層構造になっているはずなので、FigureはどこかでAxesをプロパティとして持っています。Figureにはaxesと言うプロパティがあり、ここからAxesの存在を確かめられます。
※:AxesSubplotはAxesのsubclass
FigureはAxesを複数持てることを確かめてみましょう。fig.axesは↑の通りリストになっていて、今の状態だとリストの長さは1でした
試しにaxesを1個増やしてみます。そうすると、fig.axesにaxesが2つできていることを確認できました。
このように、FigureがaxesというプロパティでAxesを持っているんだなーということが理解できました。
以下のような操作は全くやる必要がないのですが、FigureはAxes以外にもlinesといったプロパティを持っています。これを使えば、Axesを使わなくても線を表示することができます(その場合、Axesを使っていないので、当然Axesがあることで表示される四角いエリアは表示されません)
Figureが複数のプロパティを持っており、我々は上のようにFigureのAxes以外のプロパティを直接いじることは滅多にやらず、Figureのプロパティの1つであるAxesの中身をいじることで図を可視化しているんだな、ということが理解できれば問題ないかなと思います。
(ちなみにFigureは以下のようなプロパティを持っています)
Axes
Axesが図を書く際にほとんどのArtistを扱うオブジェクトで、AxesがAxes自身に多くのArtistを格納しますし、Axesが持つhelper methodでArtistを追加、更新する役割を果たしており、matplotlibの中核の機能を担っています。
とはいえ、FigureがAxesをプロパティに持っていたように、Axesが他のArtistを持っている構造は変わりません。ここでは例としてAxesがax.linesで線の情報を持っていることを確認してみましょう。
ax.plotで線を引くと、ax.linesというリストのプロパティに1つの線が含まれていることが分かります。
中身を見てみると、ax.linesは1つの線を持っており、それはmatplotlib.lines.Line2Dというオブジェクトになっています。このように、FigureがAxesが複数持つように、AxesはLine2DというArtistを複数持てる、ということが分かりました。こうやってAxesは自身のプロパティに他の色んなArtistをリスト形式なりなんなりで格納していて、図が作られている、ということになります。
下は一例ですが、Axesがどんなprimitiveを作成するhelper methodを持ち、それがどこに格納されているかを整理した表になります。これをみることで、自分がやっている処理は、あるprimitiveを対応するプロパティに追加、更新、削除している処理なんだなーと理解することができます。
Axis, Tick
Axisは目盛りやグリッド線を格納しており、例えば目盛りにはget_ticklocsメソッドで確認できます(説明していませんでしたが、Axesはxaxis, yaxisでaxisにアクセスできます)
ラベルを確認したいときはget_ticklabelsメソッドを使います。
ticksオブジェクトを取得するには、get_major_ticks, get_minor_ticksメソッドを使います(majorとminorが何なのか分かっていません)
Axisは上記含め、以下のようなaccessor methodを持っています
tickはlineとかlabelの実体を持っているだけです。以下のようなプロパティを持ちます。
ここまでで、containersの各要素の説明は以上になります。
おわりに
matplotlibのオブジェクト指向の考え方について、表示する側のArtistであるcontainersを中心に全体像について説明してきました。正直、これが分かったからといって劇的にmatplotlibが書けるようになる訳ではないので無駄といえば無駄なんですが、調べてみたかったし何となく分かったことへの達成感があるので良しとします。
もしかしたら、「これはこのcontainerのこのprimitiveをいじってんだろうなー」という感覚を持っていることで、自分のやっていることが何なのか理解できて助けになる場面があるかもしれません。もしそんなことがあったらこの記事にいいねして頂ければと思います。