Rubyでグラフを描画するツール GR.rb を作ったので紹介します

image.png image.png image.png image.png image.png image.png image.png image.png image.png image.png image.png image.png image.png image.png image.png

これはなんの記事?

GRというグラフ描画ライブラリのRubyバインディングの記事です。

Rubyだってグラフを描きたいのです!

こんにちは。気がつくとRubyのコードをこちょこちょ書いているkojix2と申します。

 Rubyでグラフを描きたいって思ったことはありませんか? もちろんRubyにもグラフを描くツールはいくつかあります。例えばNArrayの作者の田中さんが作っているnumo-gnuplot、Jupyter-labで動かすiruby-plotly, Ankaneファミリーのchartkick、かつて一世を風靡し作者が忽然と姿を消したNyaplot、フロントエンドを目指すchartydaruと一緒に使うdaru-view。どれも良いツールではあるのですが、一長一短で私は満足できませんでした。

 2019年の初め、rubyplotの開発が発表されました。これはRubyにふさわしいAPIをもつグラフ描出用ライブラリということで、非常に期待していました。しかし開発者のみなさん忙しくてなかなか時間が取れないみたいで、開発は停滞して完成しなそうな雰囲気が漂ってきました。思えばRubyにふさわしいグラフ描画のためのAPIを一から考えて実装するのは簡単にできる仕事ではなかったのだと思います。

 ここ数ヶ月間、Ankaneさんが次々と機械学習のライブラリを発表されています。どうしてそんなことができるのかなと思って、プロジェクトの中身を観察してみると、ruby-ffiを利用していました。ffiを使えばC言語に詳しくなくてもバインディングを作成できるのではないかと思い、GR.rbの開発をはじめました。ネーミングは最初はffi-grだったのですが、文字が丸っこくて可愛いという理由でGR.rbを採用することにしました。

(GRではアニメーションの描出や、3Dの描出もできます! こういうのRubyではあんまり見ないでしょ?)

image.png gks.gif gks.gif gks.gif gks.gif gks.gif gks.gif gks.gif gks.gif gks.gif

インストール

GRの最新のリリース版をダウンロードして解凍し、適当なディレクトリに配置します。
環境変数GRDIRを設定して、GR.rbにgrの場所を教えます。

export GRDIR=/your/path/to/gr
gem install ruby-gr

簡単なつかい方

線グラフ

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)

Macでの実行画面
image.png

ヒストグラム

require 'numo/narray'
require 'gr/plot'

data = Numo::DFloat.new(10_000).rand_norm
GR.histogram(data)

Ubuntuでの実行画面
image.png

塗りつぶした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)

Windowsでの実行画面
image.png

タイトル、軸ラベルなど

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)

Windowsでの実行画面
image.png

さらに詳しい使い方を知りたい場合は、examplesディレクトリをご覧ください。スクリーンショットに取り上げられているグラフはいずれもExampleに入っています。(APIは今後も大きく変更になる可能性があります。)

GRの中には大きく2つのモジュールがあります。GRモジュールとGR3モジュールです。require 'gr' とすればGRモジュールが require 'gr3'とすればGR3モジュールが使えるようになります。これらは直接GRの関数を呼び出すものです。しかし、多くのケースではもっとお手軽にグラフを描出したいと思います。その場合は require 'gr/plot' をすると、GRモジュールに簡単にグラフを描出するメソッドが追加・上書きされるようになります。簡単にグラフを描画するメソッドの使用例は下記のExampleを見てください。

Jupyter

Jupyter + IRubyでも動作します。

image.png

そもそもGRってなに?

https://github.com/sciapp/gr
image.png

GRはユーリヒ総合研究機構Josef Heinenさん達が開発しているグラフ描画ライブラリです。GRはJuliaではPlotのデフォルトのバックエンドに採用されてます。PythonやR言語など特定の言語に依存しないグラフ描画ライブラリとしてはかなり有名な方じゃないかなと思います。Githubのコントリビューションを見ると、8年前から継続的な開発が続けられているのがわかります。
image.png

工夫したところ

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さんによるPyTorchLightGBMのバインディングの公開
  • Numo NArrayが本格的に海外で知名度を獲得しはじめた
  • ApacheArrowの開発が進んだ

ほかにも、4日目のアドベントカレンダーのうなぎおおとろさんがRuby用のdeep learningライブラリruby-dnnを作ってくれたりして、地味ながらも着実な成長が感じられる1年だったと思います。

来年のみなさまの生活もきっと豊かなものでありますように。

この記事は以上です。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account