Rubyでグラフを描画するツール GR.rb の紹介

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/b595f6f2-ab0d-c8bb-92b8-5ed77bcfc719.png) ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/9084865a-f2fd-29b2-38e1-006f99aa6a40.png) ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/ef7aa4dc-c7a8-ee4e-3fe9-87da7ca77c6c.png) ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/4d324ad5-0977-060b-a8e0-c1eb994d849d.png) ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/211852fe-3811-14d9-e768-847f6e7240a0.png) ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/bef0b1c0-933c-aa6d-bbee-13c7c4ff4ac7.png) ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/15a4c693-321e-df9d-87a8-4ac01bb410c5.png) ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/bebfe1c7-aeb6-dde1-f59a-936d9b92d50d.png) ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/ca207ba6-480c-e5ee-e95d-2fed7b05dc17.png) ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/c41c9110-35df-b574-cf1d-41d4472da16c.png) ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/f388b2be-4e6b-d356-0b2c-6b866137f339.png) ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/b0b48c59-24c4-b0f6-0a4e-74a80cad9271.png) ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/bbc7eb89-6548-c3e4-0779-5eb6201eaf71.png) ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/6c1b0b0a-9ab5-d41a-4a17-694d07595f89.png) ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/4863cd4c-a4b0-1742-f86b-cd760e3e08b0.png) 

## これはなんの記事？
GRというグラフ描画ライブラリのRubyバインディングの記事です。

* https://github.com/red-data-tools/GR.rb

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

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

　Rubyでグラフを描きたいって思ったことはありませんか？ もちろんRubyにもグラフを描くツールはいくつかあります。たとえば

* NArrayの作者の田中さんが作っている[numo-gnuplot](https://github.com/ruby-numo/numo-gnuplot)
* Jupyter-labで動かす[iruby-plotly](https://github.com/zach-capalbo/iruby-plotly)
* [Ankane](https://github.com/ankane/)ファミリーの[chartkick](https://github.com/ankane/chartkick)
* かつて一世を風靡し作者が忽然と姿を消した[Nyaplot](https://github.com/domitry/nyaplot)
* Seabornのようなフロントエンドを目指す[charty](https://github.com/red-data-tools/charty)
* [daru](https://github.com/SciRuby/daru)と一緒に使う[daru-view](https://github.com/SciRuby/daru-view)

　どれも良いツールではあるのですが、一長一短で私は満足できませんでした。

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

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

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/30c06309-0f7e-08d9-ed66-ac2cc9f1cc2d.png) ![gks.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/7ae89472-40d8-0ad8-cd8c-408c8383db3d.gif) ![gks.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/0989a960-2753-edb1-8760-fc027b172db6.gif) ![gks.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/d97fd3b0-e15c-555b-b78e-8295bc47d0a3.gif) ![gks.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/75fd299f-2f32-394f-ff15-ee7a0b890447.gif) ![gks.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/a5009acd-a5b5-c93e-9e46-79c40557418e.gif) ![gks.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/eaa61214-affc-f347-6d58-2ba6f7eab11d.gif) ![gks.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/cbaf8cb9-d2b5-d6d2-662c-7aae10195291.gif) ![gks.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/2bc534ed-29c3-17a3-3d79-7316852ac511.gif) ![gks.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/48800e76-7280-8e70-7ee9-0b5d065ce401.gif) 

（扉絵とアニメーションを描出するコードは全てExampleの中にあります。）

### インストール
[GRの最新のリリース版](https://github.com/sciapp/gr/releases)をダウンロードして解凍し、適当なディレクトリに配置します。
環境変数GRDIRを設定して、GR.rbにgrの場所を教えます。

```
export GRDIR=/your/path/to/gr
```

非公式ですがMacではHomebrewを用いてインストールすることもできます。(一部正常に動作しない場合があります。正常に動作しない理由はバージョンによってさまざまなのですが、`GKS_WSTYPE="gksqt"` など、環境変数でワークステーションを指定することによって改善する場合もあります。)

```sh
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のスクリプトが終了しないようにしてください。

#### 線グラフ

```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)
```
Macでの実行画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/79c5b523-9fce-c9bd-cb01-a97204ea4274.png)

#### ヒストグラム

実行には [histogram](https://rubygems.org/gems/histogram/versions/0.2.4.1) gem が必要です。

```ruby
require 'numo/narray'
require 'gr/plot'

data = Numo::DFloat.new(10_000).rand_norm
GR.histogram(data)
```
Ubuntuでの実行画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/22203189-a0c0-fe60-8c62-a15dcf987eb0.png)

#### 塗りつぶした2次元等高線図

```ruby
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/e83d8307-4f76-fabe-44f3-d97a29d3e1fc.png)

#### タイトル、軸ラベルなど

```ruby
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/4afb3976-d334-0373-4db2-ced87e4b5820.png)


より詳しい使い方を知りたい場合は、[Wiki](https://github.com/red-data-tools/GR.rb/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](https://github.com/red-data-tools/GR.rb/blob/master/examples/fast_plots.rb)
    * [Example その 2](https://github.com/red-data-tools/GR.rb/blob/master/examples/fast_plots_keywords.rb)
    * 完成度はまだ未熟、大きな変更も入る
    * GR.jlに準拠。オブジェクト指向よりも関数的に。

* **GRモジュール**
    * `require 'gr'`
    * モジュールメソッドはgrの関数を直接呼び出す
    * 主に2次元プロットや簡単な3次元プロットに使う
    * リファレンス [[python](https://gr-framework.org/python-gr.html)] [[julia](https://gr-framework.org/julia-gr.html)] [[ruby](https://rubydoc.info/gems/ruby-gr/GR)]
    * (Ruby版リファレンスは引数を省略しているのでJulia版・Python版もご参照ください)

* **GR3モジュール** 
    * `require 'gr3'`
    * モジュールメソッドはgr3の関数を直接呼び出す
    * 複雑な3D描写に使う
    * リファレンス [[python](https://gr-framework.org/python-gr3.html)] [[julia](https://gr-framework.org/julia-gr3.html)] [[ruby](https://rubydoc.info/gems/ruby-gr/GR3)]
    * (Ruby版リファレンスは引数を省略しているのでJulia版・Python版もご参照ください)




### Jupyterで使う
Jupyter + [IRuby](https://github.com/SciRuby/iruby)でも動作します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/11672aea-437c-6689-d5a5-59bb321f2a60.png)

### そもそもGRってなに？
https://github.com/sciapp/gr
[![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/6531ed6e-62f8-5de5-8146-50a373968a25.png)](https://github.com/sciapp/gr)

GRは[ユーリヒ総合研究機構](https://www.fz-juelich.de)の[Josef Heinen](https://github.com/jheinen)さん達が開発しているグラフ描画ライブラリです。GRは[Julia](https://github.com/JuliaLang/julia)では[Plot](https://github.com/JuliaPlots/Plots.jl)のデフォルトのバックエンドに採用されてます。PythonやR言語など特定の言語に依存しないグラフ描画ライブラリとしてはかなり有名な方じゃないかなと思います。Githubのコントリビューションを見ると、8年前から継続的な開発が続けられているのがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/1257242f-c111-4288-723b-f56a4e026184.png)

### 工夫したところ

Ankaneさんのプロジェクトのディレクトリ構成を参考にしました。ありがとうAnkaneさん。私がいろいろここに書くよりも、Ankaneさんのリポジトリを見るのが勉強になります。例えばlightgbmはコードの量も多くなくてオススメです。

* https://github.com/ankane/lightgbm

具体的にはFFIモジュールを作って、そこに`attach_function`でCの関数をRubyのメソッドとして登録します。Rubyといえばテストですが、Ankaneレポジトリを見ればffiを使うプロジェクトで`travis-ci`等をどう設定すればいいかわかると思います。

私が工夫したところは、ブロック構文と継承を使って、RubyのArrayとpointerの相互変換を自動化したところです。(私はこれまでblock構文がよくわかっていなくて、はじめて満足がいく方法でブロックを使うことができた気がします。しかし、ひょっとするとこの書き方は、第三者が見た時にどうやって動作しているのかわかりにくくて、メンテナンス上はあまりよろしくないかもしれません。)

```ruby
    def inqtext(x, y, string)
      inquiry [{ double: 4 }, { double: 4 }] do |tbx, tby|
        super(x, y, string, tbx, tby)
      end
    end
```

（同じメソッドがpython-grだとこうなる。Rubyの方がスッキリして見える気がする。）

```python
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](https://github.com/oneclick/rubyinstaller2)の方がメンテナンスしているので、Windowsの対応は抜群です。私はruby-ffiのことは結構好きです。けれども、ちょっと調べると[ruby-ffiはかつてプロジェクトが死にかけた過去](https://stackoverflow.com/questions/33451196/differences-between-ruby-ffi-module-and-the-fiddle-module)があるようです。ruby-ffiのリポジトリを眺めると、いくつかC言語のソースコードが含まれているのがわかります。私はC言語わかりませんが、Rubyの仕様が大きく変更になった時にruby-ffiを追従させていくのは結構な労力がいるんじゃないかなと推測します。

### ruby-ffiからfiddleへの移行について

　Rubyからffiを使用するライブラリはruby-ffiのほかに、[fiddle](https://docs.ruby-lang.org/ja/latest/library/fiddle.html)があります。こちらはRubyチームが管理しているので、Rubyの仕様が変更になっても安定して更新されると期待されます。けれどもfiddleは仕様がシンプルすぎて、ユーザーが使うのは正直しんどくて、普及しないのも仕方ないという感じもします。

　今回はfiddleへの移行にあたり[fiddley](https://github.com/unak/fiddley)というモジュールを使うことにしました。fiddleyはfiddleを使ってruby-ffiと同じ使用感を実現するツールです。でもfiddleyは未完成なので、実際にfiddleyを使うときは自分でいくつかコードを追加・改変していく必要がありました。

　そのほか、Windowsでfiddleを使う際に、パスを通すだけでは共有ライブラリ（dll）が読み込まれません。実は`RubyInstaller.add_dll_directory`を呼ばなければならないという注意点があります。ruby-ffiではこのへんは自動でうまくやってくれているようです。詳しくは下記のリンクをご覧ください。

[Windows の Ruby の fiddle で lib○○.dll が読み込めない時、何をチェックすればよいでしょうか？](https://ja.stackoverflow.com/questions/60784/windows-%e3%81%ae-ruby-%e3%81%ae-fiddle-%e3%81%a7-lib-dll-%e3%81%8c%e8%aa%ad%e3%81%bf%e8%be%bc%e3%82%81%e3%81%aa%e3%81%84%e6%99%82-%e4%bd%95%e3%82%92%e3%83%81%e3%82%a7%e3%83%83%e3%82%af%e3%81%99%e3%82%8c%e3%81%b0%e3%82%88%e3%81%84%e3%81%a7%e3%81%97%e3%82%87%e3%81%86%e3%81%8b)

## GR.rbのこれから

### red-data-toolsのプロジェクトになりました

　もともと使いやすいRubyのグラフ描画ライブラリが見当たらないこととから自分のために作りはじめたGR.rbですが、個人のプロジェクトなのでなかなかユーザーも増えず発展は難しいだろうなと思っていました。そんな時に[red-data-tools](https://red-data-tools.github.io/ja/)のプロジェクトにしないかとお声がかかり、いつもお世話になっているので参加することにしました。プロジェクトを更新する権限が自分にしかないと、不測の事態で自分がいなくなった時に、その時点でプロジェクトが終了してしまう可能性もあるので、それを防止するためにもいいかなと思いました。

### issue報告求めています

ここまでGR.rbの報告をしてきました。たくさんスクリーンショットも貼り付けてきたのですが、実際には開発は始まったばかりで、たくさんのバグや実装上の課題が残っています。GR.rbのメンテナンスは、これから最低でも5年、できれば10年単位で継続していきたいなと思っていて、見つかったバグは少しずつ除去していきたいと思っています。なのでもしも不具合が見つかったら、[GithubのGR.rbのページ](https://github.com/red-data-tools/GR.rb)からissueやプルリクエストをして頂けると嬉しいなと思います。

### 2019年を振り返って

Rubyはオブジェクト指向言語であり、関数のかわりにメソッドが多用されます。そのため、データ処理には使いにくいという意見を持っておられる方も少なくないと思います。しかし今年一年間の、Rubyとデータ処理まわりを振り返ってみると、非常に多くの進歩がありました。

* 機械学習ライブラリ[Rumale](https://github.com/yoshoku/rumale)のブレイク
* Ankaneさんによる[PyTorch](https://github.com/ankane/torch-rb)、[LightGBM](https://github.com/ankane/lightgbm/)のバインディングの公開
* [Numo NArray](https://github.com/ruby-numo/numo-narray)が本格的に海外で知名度を獲得しはじめた
* [ApacheArrow](https://www.clear-code.com/blog/2019/9/30.html)の開発が進んだ

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

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

この記事は以上です。