これはなんの記事?
GRというグラフ描画ライブラリのRubyバインディングの記事です。
Rubyだってグラフを描きたいのです!
こんにちは。気がつくとRubyのコードをこちょこちょ書いているkojix2と申します。
Rubyでグラフを描きたいって思ったことはありませんか? もちろんRubyにもグラフを描くツールはいくつかあります。たとえば
- NArrayの作者の田中さんが作っているnumo-gnuplot
- Jupyter-labで動かすiruby-plotly
- Ankaneファミリーのchartkick
- かつて一世を風靡し作者が忽然と姿を消したNyaplot
- Seabornのようなフロントエンドを目指すcharty
- daruと一緒に使うdaru-view
どれも良いツールではあるのですが、一長一短で私は満足できませんでした。
2019年の初め、rubyplotの開発が発表されました。これはRubyにふさわしいAPIをもつオブジェクト指向のグラフ描出用ライブラリということで、非常に期待していました。しかし開発者のみなさん忙しくてなかなか時間が取れないみたいで、開発は停滞。完成しなそうな雰囲気が漂ってきました。思えばRubyにふさわしいグラフ描画のためのAPIを一から考えて実装するのは簡単な仕事ではなかったのだと思います。
ここ数ヶ月間、Ankaneさんが次々と機械学習のライブラリを発表しています。どうしてそんなことができるのかなと思って、プロジェクトの中身を観察してみると、ruby-ffiを利用していました。ffiを使えばC言語に詳しくなくてもバインディングを作成できるのではないかと思い、GR.rbの開発をはじめました。ネーミングは最初はffi-grだったのですが、文字が丸っこくて可愛いという理由でGR.rbを採用することにしました。
(GRではアニメーションの描出や、3Dの描出もできます! こういうのRubyではあんまり見ないでしょ?)
(扉絵とアニメーションを描出するコードは全てExampleの中にあります。)
インストール
GRの最新のリリース版をダウンロードして解凍し、適当なディレクトリに配置します。
環境変数GRDIRを設定して、GR.rbにgrの場所を教えます。
export GRDIR=/your/path/to/gr
非公式ですがMacではHomebrewを用いてインストールすることもできます。(一部正常に動作しない場合があります。正常に動作しない理由はバージョンによってさまざまなのですが、GKS_WSTYPE="gksqt"
など、環境変数でワークステーションを指定することによって改善する場合もあります。)
brew install libgr
Gemをインストールします。GR.rbというプロジェクト名なのですが、gemの名前はruby-grになっていますのでご注意ください。
gem install ruby-gr
WIndowsでは、gem install ruby-gr
とするだけで自動的にMinGWからgrがダウンロードされてすぐに利用できるようになります。(しかし、こちらも非公式リリースなのでバージョンにより不具合が出たりします。)
簡単なつかい方
irbやpryに下のスクリプトをコピペしてみてください。GKSウィンドウが立ち上がります。
ファイルから実行する場合は、一瞬だけウィンドウが表示されて、スクリプトが実行し終わると瞬時に消えてしまいます。一番最後の行にgets
やsleep 10
を入れてRubyのスクリプトが終了しないようにしてください。
線グラフ
require 'gr/plot'
x = [0, 0.2, 0.4, 0.6, 0.8, 1.0]
y = [0.3, 0.5, 0.4, 0.2, 0.6, 0.7]
GR.plot(x, y)
ヒストグラム
実行には histogram gem が必要です。
require 'numo/narray'
require 'gr/plot'
data = Numo::DFloat.new(10_000).rand_norm
GR.histogram(data)
塗りつぶした2次元等高線図
require 'numo/narray'
require 'gr/plot'
include Numo
x = 8 * DFloat.new(100).rand - 4
y = 8 * DFloat.new(100).rand - 4
z = NMath.sin(x) + NMath.cos(y)
GR.contourf(x, y, z)
タイトル、軸ラベルなど
require 'rdatasets'
require 'gr/plot'
passenger = RDatasets.datasets.AirPassengers
time = passenger.at(0).to_a
value = passenger.at(1).to_a
opts = { title: 'Air Passenger numbers from 1949 to 1961',
ylabel: "Passenger numbers (1000's)",
xlabel: 'Date'
}
GR.plot(time, value, opts)
より詳しい使い方を知りたい場合は、Wikiをご覧ください。スクリーンショットに取り上げられているグラフはいずれもExampleに入っています。APIは今後も大きく変更になる可能性があります。
主なモジュールとクラス
GRとGR3という2つのモジュールから、grの関数を呼び出して使います。これらは細かくカスタマイズされたグラフの描出には便利です。けれども多くのケースではもっと簡単にグラフを描きたいでしょう。その場合はgr/plot
を使います。require 'gr/plot'
をすると、GRモジュールに簡単にグラフを描出するメソッドが追加されます。下にGRモジュール
、GR3モジュール
、Plotクラス
について簡単に説明します。
-
Plotクラス
- 普段はこれを使う
- `require 'gr/plot'
- GRを上書き・置換して簡単にグラフを書けるメソッドを追加する。
-
plot
step
scatter
stem
histogram
contour
contourf
hexbin
heatmap
nonuniformheatmap
wireframe
surface
plot3
scatter3
imshow
isosurface
polar
polarhist
polarheatmap
trisurf
tricont
shade
volume
(未実装あり) - Example その 1
- Example その 2
- 完成度はまだ未熟、大きな変更も入る
- GR.jlに準拠。オブジェクト指向よりも関数的に。
-
GRモジュール
-
GR3モジュール
Jupyterで使う
Jupyter + IRubyでも動作します。
そもそもGRってなに?
GRはユーリヒ総合研究機構のJosef Heinenさん達が開発しているグラフ描画ライブラリです。GRはJuliaではPlotのデフォルトのバックエンドに採用されてます。PythonやR言語など特定の言語に依存しないグラフ描画ライブラリとしてはかなり有名な方じゃないかなと思います。Githubのコントリビューションを見ると、8年前から継続的な開発が続けられているのがわかります。
工夫したところ
Ankaneさんのプロジェクトのディレクトリ構成を参考にしました。ありがとうAnkaneさん。私がいろいろここに書くよりも、Ankaneさんのリポジトリを見るのが勉強になります。例えばlightgbmはコードの量も多くなくてオススメです。
具体的にはFFIモジュールを作って、そこにattach_function
でCの関数をRubyのメソッドとして登録します。Rubyといえばテストですが、Ankaneレポジトリを見ればffiを使うプロジェクトでtravis-ci
等をどう設定すればいいかわかると思います。
私が工夫したところは、ブロック構文と継承を使って、RubyのArrayとpointerの相互変換を自動化したところです。(私はこれまでblock構文がよくわかっていなくて、はじめて満足がいく方法でブロックを使うことができた気がします。しかし、ひょっとするとこの書き方は、第三者が見た時にどうやって動作しているのかわかりにくくて、メンテナンス上はあまりよろしくないかもしれません。)
def inqtext(x, y, string)
inquiry [{ double: 4 }, { double: 4 }] do |tbx, tby|
super(x, y, string, tbx, tby)
end
end
(同じメソッドがpython-grだとこうなる。Rubyの方がスッキリして見える気がする。)
def inqtext(x, y, string):
tbx = (c_double * 4)()
tby = (c_double * 4)()
__gr.gr_inqtext(c_double(x), c_double(y), char(string), tbx, tby)
return [[tbx[0], tbx[1], tbx[2], tbx[3]],
[tby[0], tby[1], tby[2], tby[3]]]
それから、プログラミングに限った話ではありませんが、わからないことはいろいろと質問してみることも大事だなと感じました。英語版のstackoverflowやGithubのissue欄を使えば日本人だけでなく、外国の方に質問することができます。みらい翻訳
で英文をこしらえてThanks
と書くのを忘れなければ、たいていの場合親切に教えてくれます。
ffiでRubyからC言語の関数を呼ぶ
ruby-ffiは非常に良くできていて、ほとんどの局面でうまく動作します。とくに今のruby-ffiはRubyInstaller2の方がメンテナンスしているので、Windowsの対応は抜群です。私はruby-ffiのことは結構好きです。けれども、ちょっと調べるとruby-ffiはかつてプロジェクトが死にかけた過去があるようです。ruby-ffiのリポジトリを眺めると、いくつかC言語のソースコードが含まれているのがわかります。私はC言語わかりませんが、Rubyの仕様が大きく変更になった時にruby-ffiを追従させていくのは結構な労力がいるんじゃないかなと推測します。
ruby-ffiからfiddleへの移行について
Rubyからffiを使用するライブラリはruby-ffiのほかに、fiddleがあります。こちらはRubyチームが管理しているので、Rubyの仕様が変更になっても安定して更新されると期待されます。けれどもfiddleは仕様がシンプルすぎて、ユーザーが使うのは正直しんどくて、普及しないのも仕方ないという感じもします。
今回はfiddleへの移行にあたりfiddleyというモジュールを使うことにしました。fiddleyはfiddleを使ってruby-ffiと同じ使用感を実現するツールです。でもfiddleyは未完成なので、実際にfiddleyを使うときは自分でいくつかコードを追加・改変していく必要がありました。
そのほか、Windowsでfiddleを使う際に、パスを通すだけでは共有ライブラリ(dll)が読み込まれません。実はRubyInstaller.add_dll_directory
を呼ばなければならないという注意点があります。ruby-ffiではこのへんは自動でうまくやってくれているようです。詳しくは下記のリンクをご覧ください。
Windows の Ruby の fiddle で lib○○.dll が読み込めない時、何をチェックすればよいでしょうか?
GR.rbのこれから
red-data-toolsのプロジェクトになりました
もともと使いやすいRubyのグラフ描画ライブラリが見当たらないこととから自分のために作りはじめたGR.rbですが、個人のプロジェクトなのでなかなかユーザーも増えず発展は難しいだろうなと思っていました。そんな時にred-data-toolsのプロジェクトにしないかとお声がかかり、いつもお世話になっているので参加することにしました。プロジェクトを更新する権限が自分にしかないと、不測の事態で自分がいなくなった時に、その時点でプロジェクトが終了してしまう可能性もあるので、それを防止するためにもいいかなと思いました。
issue報告求めています
ここまでGR.rbの報告をしてきました。たくさんスクリーンショットも貼り付けてきたのですが、実際には開発は始まったばかりで、たくさんのバグや実装上の課題が残っています。GR.rbのメンテナンスは、これから最低でも5年、できれば10年単位で継続していきたいなと思っていて、見つかったバグは少しずつ除去していきたいと思っています。なのでもしも不具合が見つかったら、GithubのGR.rbのページからissueやプルリクエストをして頂けると嬉しいなと思います。
2019年を振り返って
Rubyはオブジェクト指向言語であり、関数のかわりにメソッドが多用されます。そのため、データ処理には使いにくいという意見を持っておられる方も少なくないと思います。しかし今年一年間の、Rubyとデータ処理まわりを振り返ってみると、非常に多くの進歩がありました。
- 機械学習ライブラリRumaleのブレイク
- AnkaneさんによるPyTorch、LightGBMのバインディングの公開
- Numo NArrayが本格的に海外で知名度を獲得しはじめた
- ApacheArrowの開発が進んだ
ほかにも、4日目のアドベントカレンダーのうなぎおおとろさんがRuby用のdeep learningライブラリruby-dnnを作ってくれたりして、地味ながらも着実な成長が感じられる1年だったと思います。
来年のみなさまの生活もきっと豊かなものでありますように。
この記事は以上です。