Ruby + ruby_gnuplot（gnuplot gem）で散布図を描く

https://rubygems.org/gems/gnuplot

https://github.com/rdp/ruby_gnuplot




# 準備

- gnuplot コマンドをインストール
  - Ubuntu 18.04 の場合は `sudo apt install gnuplot`
- ruby_gnuplot をインストール
  - `gem install gnuplot`

これだけ。

ただし、 Ruby 3.1.0 で matrix が bundled gem になったようなので、3.1.0 以降の場合は `gem install matrix` も必要です。
参考: [[Ruby] Bundled gemsはGemfileに指定して使おう](https://zenn.dev/shuichi/articles/bundled-gem-with-gemfile)




# 最低限の例

PNG ファイルに出力する最低限の例です。

```ruby
# sample.rb

require "gnuplot"

Gnuplot.open do |gp|
  Gnuplot::Plot.new(gp) do |plot|
    plot.terminal "png"
    plot.output "output.png"

    x = [1, 2, 3, 4, 5]
    y = [11, 22, 33, 44, 55]
    plot.data << Gnuplot::DataSet.new([x, y])
  end
end
```

```sh
  # 実行 ... output.png が生成される
ruby sample.rb
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234055/c89e9024-dfc7-2e78-4d26-9020c551c83d.png)


以上。簡単ですね。
ちょっと見えにくいですが、 両端の点は枠線に重なっています。

PNG 以外にも、 SVG や PDF として出力することもできます。



# 環境

```sh
Ubuntu Linux 18.04
gnuplot -V #=> gnuplot 5.2 patchlevel 2
ruby -v    #=> ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [x86_64-linux]
gem:
  gnuplot 2.6.2
  matrix 0.4.2
```



# メモ

- とにかくインストールが楽なのが良い。gnuplot コマンドのインストールさえ済んでしまえば、他にインストール絡みでトラブルが起きる余地がなさそう。気分的に楽 :ok_hand:
- gnuplot でできることは大体何でもできるはず。たぶん。
- `gnuplot.rb` ( https://github.com/rdp/ruby_gnuplot/blob/master/lib/gnuplot.rb ) はコメントを除くと300行くらいなので、サッと読める
  - gnuplot 用のスクリプトに変換して gnuplot コマンドに渡しているだけ
- 「○○がやりたいけどどうすればいい？」となった場合は gnuplot 向けの情報を探す
  - gnuplot は枯れたプロダクトなので、情報はたくさんある
  - すでに gnuplot に習熟している人は楽そう
- [Charty](https://github.com/red-data-tools/charty) のバックエンドとして使える？（まだ試していません）
  - （追記） 2022-05-29 現在では未対応でした

非常に薄いラッパーなので、素の gnuplot をそのまま使うのと実はそんなに大差なくて、そこを踏まえた上で使う分には便利。
「gnuplot の使い方を覚えたいわけではないんだけどなあ……」という人には向かないかもしれません。




# 自分が使いそうな機能についてメモ

- グラフのタイトル指定
- x軸, y軸の範囲指定、軸タイトル指定
- 複数の系列の描画
- 数式のプロット

などは [README](https://github.com/rdp/ruby_gnuplot) にサンプルがあるので、そっちを見てください。

## 系列名の凡例

`DataSet.new` にブロックを付けると、ブロックパラメータとして DataSet のインスタンスが渡されるので、それを使って指定できる。

```ruby
plot.data << Gnuplot::DataSet.new([x, y]) { |ds|
  ds.title = "dataset"
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234055/1e462ad6-0331-98a1-b4b2-778476be13bc.png)




## 日本語フォント

無指定だと日本語が豆腐になりました。
`gnuplot フォント 日本語 set terminal` のような検索ワードで調べると、 `Plot#terminal` で指定できることが分かります。

```ruby
Gnuplot.open do |gp|
  Gnuplot::Plot.new(gp) do |plot|
    plot.terminal "png font 'VL Gothic,20'"
    plot.output "output.png"

    x = [1, 2, 3]
    y1 = [11, 25, 32]

    plot.data << Gnuplot::DataSet.new([x, y1]) { |ds|
      ds.title = "系列1"
    }
  end
end
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234055/5260e28d-7115-2493-de57-8d2598297d53.png)



## 点の代わりにラベルを表示する

```ruby
Gnuplot.open do |gp|
  Gnuplot::Plot.new(gp) do |plot|
    plot.terminal "png"
    plot.output "output.png"

    x = [1, 2, 3]
    y = [11, 22, 33]
    label = ["A", "B", "C"]
    plot.data << Gnuplot::DataSet.new([x, y, label]) { |ds|
      ds.with = "labels"
    }
  end
end
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234055/1c090280-4da9-cc4a-69af-046cf4e24112.png)

---

点とラベルを両方表示するのは若干面倒そう。

<!--
In gnuplot, how to label each point in the plot with its coordinates? - Stack Overflow
-->

https://stackoverflow.com/questions/23177716/in-gnuplot-how-to-label-each-point-in-the-plot-with-its-coordinates



## 生成されたスクリプトだけ欲しい

以下のように `Gnuplot.open` にモンキーパッチを当てると、グラフの描画は行わずに gnuplot 用に生成されたスクリプトの出力だけを行うことができます。

```ruby
module Gnuplot
  def self.open(persist = true)
    io = $stdout
    yield io
  end
end

Gnuplot.open do |gp|
  Gnuplot::Plot.new(gp) do |plot|
    x = [1, 2, 3]
    y = [11, 22, 33]
    plot.data << Gnuplot::DataSet.new([x, y])
  end
end
```

```sh
$ ruby sample_print_script.rb > sample.gs

$ cat sample.gs
plot '-'
1 11
2 22
3 33
e

  # スクリプトを gnuplot コマンドに渡して実行
$ gnuplot -persist sample.gs
```




# まとめたもの

せっかくなのでいろいろ含めたものも貼ってみます。

```ruby
require "gnuplot"

Gnuplot.open do |gp|
  Gnuplot::Plot.new(gp) do |plot|
    plot.terminal "png font 'Noto Sans CJK JP,12'"
    plot.output "output.png"
    plot.title "グラフのタイトル"
    plot.xrange "[0:6]"
    plot.yrange "[0:60]"
    plot.xlabel "x軸のラベル"
    plot.ylabel "y軸のラベル"

    x = [1, 2, 3]
    y1 = [11, 25, 32]
    y2 = [21, 35, 42]
    y3 = [31, 45, 52]

    plot.data << Gnuplot::DataSet.new([x, y1]) { |ds|
      ds.title = "系列1"
    }

    plot.data << Gnuplot::DataSet.new([x, y2]) { |ds|
      ds.title = "系列2（線のみ）"
      ds.with = "lines"
    }

    plot.data << Gnuplot::DataSet.new([x, y3]) { |ds|
      ds.title = "系列3（点と線）"
      ds.with = "linespoints"
    }

    # 数式
    plot.data << Gnuplot::DataSet.new("x * 10") { |ds|
      ds.title = "y = x * 10"
    }
  end
end
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234055/48108a9d-a4c2-a43f-6bd3-1f57e6b04672.png)

`ds.with = "lines"` を使うか、または数式を使えば任意の直線が描画できるので、回帰直線もこれでいけますね（フィッティングの話については割愛）。

# 参考

<!--
Rubyのgnuplotで時系列データをプロット | Lifelog
-->

https://ytkyk.info/blog/2016/07/21/ruby%E3%81%AEgnuplot%E3%81%A7%E6%99%82%E7%B3%BB%E5%88%97%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92%E3%83%97%E3%83%AD%E3%83%83%E3%83%88/

---

<!--
gnuplot documentation
-->
http://gnuplot.info/documentation.html

日本語のPDFがあります。

---

<!--
Demos for gnuplot version 5.4
-->

http://gnuplot.sourceforge.net/demo_5.4/

豊富なサンプル。gnuplot マジ何でもできるな、という気持ちになります。




# この記事を読んだ人は（ひょっとしたら）こちらも読んでいます

<!-- SVG::Graph（svg-graph gem）で散布図を描く - Qiita -->
https://qiita.com/sonota88/items/86afece3b2595648a656

<!--
Galaaz を触ってみた（TruffleRuby + ggplot2 で散布図を描いてみた） - Qiita
-->
https://qiita.com/sonota88/items/b586e96f7805a5695a34