RedAmberを触ってみた

データフレーム（２次元の表形式のデータ）を用意されていて、それに何かしらの処理しようと思った時パッと思い浮かぶ利用ツールはPythonのPandasでしょうか？それともExcelなどでしょうか？
今回はそのどちらでもなく、RubyKaigiでも紹介されたRubyのgemの一つであるRedAmberを触ってみます。

## RedAmberとは？
データフレームを扱うためのRubyのライブラリです。(ソースコードは下記)
ApacheArrowのRubybindingであるRedArrowが中では動いているみたいです。

https://github.com/red-data-tools/red_amber

詳細は開発者の方がRubyKaigiで発表したスライドがあります。

https://speakerdeck.com/heronshoes/the-adventure-of-redamber-a-data-frame-library-in-ruby

## 検証環境
今回は下記の環境で使ってみます。

- Ruby 3.2.2
- M1 Pro

## セットアップ

Readmeを参考に下記コマンドを実行します。

```shell
brew install apache-arrow
brew install apache-arrow-glib
```

RedAmberを動かすためにはRedArrowが必要で、RedArrowを動かすためにはApacheArrowが必要、というわけでApacheArrowをインストールしているわけですね。

次に、RedAmberをインストールします。
今回はGemfileを使って`bundle install`します。(普通に`gem install red_amber`でもいいと思います)
データセットのサンプルとして、[red-datasets-arrow](https://github.com/red-data-tools/red-datasets-arrow)も一緒にインストールしています。

```ruby:Gemfile
# -*- mode: ruby -*-
# frozen_string_literal: true

source 'https://rubygems.org'

ruby '3.2.2'

gem 'red_amber'
gem 'red-datasets-arrow'
```

https://github.com/red-data-tools/red-datasets からデータセットの情報を見ることができます。
今回はIrisを使ってみましょう。

まずはデータを覗いてみます。まずは読み込んで出力してみましょう。

```ruby:p_iris.rb
require 'datasets-arrow'
require 'red_amber'
include RedAmber
dataset = Datasets::Iris.new
iris = DataFrame.new(dataset) # before v0.2.3, should be `dataset.to_arrow`
p iris
```

```sh
#<RedAmber::DataFrame : 150 x 5 Vectors, 0x000000000000337c>
    sepal_length sepal_width petal_length petal_width label
        <double>    <double>     <double>    <double> <string>
  0          5.1         3.5          1.4         0.2 Iris-setosa
  1          4.9         3.0          1.4         0.2 Iris-setosa
  2          4.7         3.2          1.3         0.2 Iris-setosa
  3          4.6         3.1          1.5         0.2 Iris-setosa
  4          5.0         3.6          1.4         0.2 Iris-setosa
・・・(中略)
146          6.3         2.5          5.0         1.9 Iris-virginica
147          6.5         3.0          5.2         2.0 Iris-virginica
148          6.2         3.4          5.4         2.3 Iris-virginica
149          5.9         3.0          5.1         1.8 Iris-virginica
```

ヘタと花弁の縦横(?)の長さ、それとIrisの種類の５項目が表示されました。

次にlabelごとに平均を出してみましょう。

まずは、label毎にデータセットを分割する必要がありますが、それはgroupを使うことで解決します。
先ほどのプログラムを少し変えて、irisをgroup関数を使って分けてみましょう。

```ruby:ave_group_label.rb
require 'datasets-arrow'
require 'red_amber'
include RedAmber
dataset = Datasets::Iris.new
iris = DataFrame.new(dataset)

df = iris
  .group(:label)

p df
```

実行するとこんな感じ。

```sh
ruby ave_group_label.rb
#<RedAmber::Group : 0x000000000000337c>
  label             count
  <string>        <int64>
0 Iris-setosa          50
1 Iris-versicolor      50
2 Iris-virginica       50
```

ラベルとラベルを振られた数が表示されています。3つのラベルが50ずつあるのがわかります。

さらにmeanを使ってlabel毎のそれぞれの平均を出してみましょう。

```diff_ruby
df = iris
  .group(:label)
+ .mean(:sepal_length, :sepal_width, :petal_length, :petal_width)
```

実行すると、それぞれの値の平均を出してくれます。

```
❯ ruby ave_group_label.rb
#<RedAmber::DataFrame : 3 x 5 Vectors, 0x000000000000337c>
  label           mean(sepal_length) mean(sepal_width) mean(petal_length) mean(petal_width)
  <string>                  <double>          <double>           <double>          <double>
0 Iris-setosa                   5.01              3.42               1.46              0.24
1 Iris-versicolor               5.94              2.77               4.26              1.33
2 Iris-virginica                6.59              2.97               5.55              2.03
```

さらに今回はsepal_widthを基準にソートしてみましょう。下記を追記するとソートしてくれます。

```diff_ruby
df = iris
  .group(:label)
  .mean(:sepal_length, :sepal_width, :petal_length, :petal_width)
+ .sort('-mean(sepal_length)')
```

実行してみると、sepal_widthが高いlabel順にソートされています。

```
❯ ruby ave_group_label.rb
#<RedAmber::DataFrame : 3 x 5 Vectors, 0x000000000000337c>
  label           mean(sepal_length) mean(sepal_width) mean(petal_length) mean(petal_width)
  <string>                  <double>          <double>           <double>          <double>
0 Iris-setosa                   5.01              3.42               1.46              0.24
1 Iris-virginica                6.59              2.97               5.55              2.03
2 Iris-versicolor               5.94              2.77               4.26              1.33
```

低い方から出したい時は、sortの部分を一部変えると良いです。

```diff_ruby
df = iris
  .group(:label)
  .mean(:sepal_length, :sepal_width, :petal_length, :petal_width)
- .sort('-mean(sepal_length)')
+ .sort('+mean(sepal_length)')
```

```
❯ ruby ave_group_label.rb
#<RedAmber::DataFrame : 3 x 5 Vectors, 0x000000000000337c>
  label           mean(sepal_length) mean(sepal_width) mean(petal_length) mean(petal_width)
  <string>                  <double>          <double>           <double>          <double>
0 Iris-versicolor               5.94              2.77               4.26              1.33
1 Iris-virginica                6.59              2.97               5.55              2.03
2 Iris-setosa                   5.01              3.42               1.46              0.24
```


## 触ってみて
ActiveRecordを触ったことがあってSQLもそこそこ描ける人なら、ActiveRecord触っている感覚でデータをサクサクと集計などを進めることができそうです。
今後ちょっとした集計などではRedAmberを使ってみて、フィードバックとしてPRやIssueも出していきたいです。
今回触れた機能はごく一部でまだまだいろんなことができるので、気になった人は是非リポジトリを覗いてみてください。

## おまけ
今回作ったコードはここにアップしてあります。
https://github.com/Umekawa/red_amber_demo