まあ比較と題しておきながらPyQtGraph側は公式のサンプルコードを眺めてもらいますが.
環境
- Mac mini(M1) Big Sur
- Python 3.9.6(arm64)
- Qt6, PyQtGraph 0.12.2
- matplotlib 3.4.2
背景と目標
計算が正常かをグラフにして確認したかったのですが,matplotlibの描画時間が本体の計算時間の10倍(体感)でやってられませんでした.そこで計算とその確認に時間を集中できるように高速(らしい)PyQtGraphに乗り換えた・・・・・・いのですが,読みにくいことで定評のあるmatplotlibの公式ドキュメントよりもPyQtGraphの方が役に立たなそう(サンプル見て覚えろなスタイル)だったので,まずはそのサンプルをmatplotlibで描き直してmatplotlib→PyQtGraphの対応関係を確認してみました.
その前に
対応関係だけ確認したい場合はこの章は無視して構いません.PyQtGraphにたどり着くまでに調べたことを簡素にまとめておきます.
pythonで使える描画ツール
-
matplotlib:
pythonにおけるデファクトスタンダード.オブジェクト指向な書き方とアーティストなどを知らないと(知ってても)微調整で発狂することでお馴染み. -
seaborn:
深いこと考えずにデータ丸投げしてもいい感じに描画してくれる凄いやつ.実体はmatplotlibをラップしてるだけなのでやっぱり発狂要因.matplotlibを使ってるのと同じで遅い.とりあえずpairplotするとコーヒーブレークの時間が作れる. -
plotly:
インタラクティブにグラフを操作できることが売り?本当に売りたいのはdashというソフトらしい.plotlyだけなら無料かつローカルに閉じた環境で使える.seabornに勝るとも劣らずいい感じにしてくれる.フォント入れなくても日本語が表示できる.軽くはない. -
VisPy:
PyQtGraphの作者も参加してるプロジェクト.OpenGL経由でGPUを使うので速い(らしい).PyQt 5 が推奨.appleがOpenGLからMetalに移行してしまい,私の環境では期待できないので試してません. -
PyQtGraph:
今回の真打.サンプルだけでも軽いのがよく分かった期待してるやつ.公式ドキュメントにすらグラフギャラリーがなく,インストール時に付属しているサンプル集を実行して確かめろなスパルタ主義.
PyQtGraphのインストール
結論は「公式HPちゃんと読め」なのですが,私は読み落としてハマったので一応・・・
#PyQtGraphは(たぶん)Qtを使って動くので必須.pipじゃ入りません.
brew install qt
そしたらshellを再起動して
pip install PyQt6 pyqtgraph
以上,QtはインストールするPyQtのバージョンに合わせましょう.私の環境ではQt5入れてもPyQt5は入りませんでした.PyQtGraphにPyQtが必要で,PyQtにはQtが必要なのかな?
PyQtGraphのサンプルコードをmatplolibで書き直す
公式ドキュメントの通りに次を実行してサンプル集を起動します.
import pyqtgraph.examples
pyqtgraph.examples.run()
すると新しいウィンドウが現れます.左側の列にBasic Plottingという項目があり,それを選択すると3*3個のグラフを詰め込んでしまった,初学者に厳し目なサンプルコードが右側に表示されます.さらにウィンドウ左下のRun Exampleを押すとやっとコードと対応したグラフが見られる仕掛けです.(インストール前に確認したい).
今回は↑こいつをmatplotlibで再現してPyQtGraphの構文を把握しようというわけです.こいつ自体は実は公式HPのトップに載ってたりします(コードはない).
import numpy as np
np.random.seed(seed=38038)
from matplotlib import pyplot as plt
fig = plt.figure()
fig.set_size_inches(1000/96, 600/96)
fig.suptitle("pyqtgraph example: Plotting")
ax1 = fig.add_subplot(3, 3, 1, title="Basic array plotting").plot(np.random.normal(size=100))
ax2 = fig.add_subplot(3, 3, 2, title="Multiple curves")
ax2.plot(np.random.normal(size=100), color=(255/255,0,0), label="Red curve")
ax2.plot(np.random.normal(size=110)+5, c=(0,255/255,0), label="Green curve")
ax2.plot(np.random.normal(size=120)+10, c=(0,0,255/255), label="Blue curve")
ax3 = fig.add_subplot(3, 3, 3, title="Drawing with points")
ax3.plot(np.random.normal(size=100), c=(200/255,200/255,200/255), markerfacecolor=(255,0,0), markeredgecolor="w")
ax4 = fig.add_subplot(3, 3, 4, title="Parametric, grid enabled")
x = np.cos(np.linspace(0, 2*np.pi, 1000))
y = np.sin(np.linspace(0, 4*np.pi, 1000))
ax4.plot(x, y)
ax4.grid(b=True, axis="both")
ax5 = fig.add_subplot(3, 3, 5, title="Scatter plot, axis labels, log scale")
x = np.random.normal(size=1000) * 1e-5
y = x*1000 + 0.005 * np.random.normal(size=1000)
y -= y.min()-1.0
mask = x > 1e-15
x = x[mask]
y = y[mask]
ax5.plot(x, y, linestyle="", marker="^", markeredgecolor=(0,0,0), markersize=10, markerfacecolor=(100/255, 100/255, 255/255, 50/255))
ax5.set_ylabel("Y Axis (A)")
ax5.set_xlabel("Y Axis (s)")
ax5.set(xscale="log", yscale="linear")
ax6 = fig.add_subplot(3, 3, 6, title="Updating plot")
ax7 = fig.add_subplot(3, 3, 7, title="Filled plot, axis disabled")
y = np.sin(np.linspace(0, 10, 1000)) + np.random.normal(size=1000, scale=0.1)
ax7.fill_between(np.linspace(0, 10, 1000), y, [-0.3]*1000, facecolor=(50/255,50/255,200/255,100/255))
#ax7.get_xaxis().remove()
fig.savefig("BasicPlotting_matplotlib.png")
やたら長大ですがこれはPyQtGraphのサンプルコードと行を揃えながら,私がわからないアニメーション(ax6)やmaplotlibに存在しないインタラクティブなグラフ(ax8, 9)などを省略したためです.左右でPyQtGraph版とmatplotlib版を並べて勉強しましょう.並べて眺めて思いましたが対応さえ把握できればmatplotlibからPyQtGraphへの移行はそんなに苦労しなさそう?
ちなみにmatplotlibの出力は・・・
上下でタイトルと軸が被ってしまいました.PyQtGraphではこれを調整していそうな直接的な記述は見当たりません.あとax3でmarkerを赤にしたつもりでしたが見えませんね.そしてアニメーションなかったりグラフ2つ省いてるのにPyQtGraphより遅かったです(体感).
またmatplotlib版では最後にsavefigしましたが,PyQtGraphではグラフを右クリックすることで,今回は9個あるグラフから1つまたは全部を選択してpng, tiff, jpg, svg等で保存できます.アニメーションとしての保存方法はまだ分かりません.
結論
確かに速い(体感)しmatplotlibと構文が似てる気がするから,これからはPyQtGraphを習得していきたい.