RedAmberでやろうとしていること

Rubyアドベントカレンダー2022の18日目の記事です。

タイトルにあるRedAmberとは何かについては、それぞれ違う例を使いながらqiitaとnoteに記事を書いたので良かったら見てみてください。

https://qiita.com/heronshoes/items/3e60ee24a436530ddaac

https://note.com/heronshoes/n/nafb267e9a99b

とってもざっくりと言うと、Pythonにおけるpandas、Rにおけるdata.frame, dplyr/tidyrがやるようなことをRubyでできるようにするライブラリです。

[2022年度Rubyアソシエーション開発助成](https://www.ruby.or.jp/ja/news/20221027)に採択されたプロジェクトとして開発を進めています。

今日は、RedAmberで私がやろうとしていることをお話しします。

## 1. Rubyでデータサイエンスしたい

 一番の目的はこれです。Rubyはデータ処理の分野が弱いです。それでも書きやすいRubyでデータサイエンスっぽいことをしてみたいです。そんな中、Red Data Toolsプロジェクトを始めとしていくつかのライブラリが頑張っています。特に、Apache Arrowプロジェクトの中にはRuby実装としてRed Arrowがあり、データサイエンスの分野でRubyを使う環境は少しずつ整ってきています。Ruby本体もRuby3になって速くなりましたし、もうすぐ3.2も出ます。

RedAmberはそのような環境の中でRed Arrowよりもユーザー寄りなレイヤーに当たるデータフレームを使いやすく提供する目的で作られています。


## 2. PythonやRのデータフレームよりも使いやすいツールを作りたい

 私も ~~（仕方なく）~~ RのdplyrやPythonのpandasを使っていましたが、Rubyならこんな感じに書けそうだな、とか、ここが使いにくいと思うことがいくつかありました。（例えばRのパイプ、pandasのloc/iloc/at/iatのあたりなど）

なにより、データ分析の手前のデータの下準備のようなところでは、Rubyの記述力を生かせるはずなのでRubyで使えるツールは必要なはずだと思っていました。こう言った経験を踏まえてRedAmberでやろうとしていることは次のようなことです。

### 2.1 Ruby汎用のクラスを生かしたシンプルなAPI

 Rubyの組み込みクラスは非常に強力なので、これを生かすAPIができれば少ないメソッドの組み合わせで様々な表現ができると考えました。

RedAmberのデータフレームの基本操作は、
- pick/drop で列単位の操作
- slice/remove で行単位の操作
- rename でキーの名前の変更
- assign で列データの変更/新設

ができます。図で書くとこんな感じ。

![basic operations in RedAmber](https://raw.githubusercontent.com/heronshoes/red_amber/main/doc/image/basic_verbs.png "RedAmberの基本操作")

行と列のアクセスメソッドが分けられているため、(行, 列)のような形式でインデックスを指定する必要がありません。代わりに行の指定を長くして、`slice(0, 3, 5..10, -5..-1)`のように書くこともできます。

データフレームの列ラベルの一覧は、`DataFrame#keys`で取れますが、これはSymbolのArrayです。RedAmberではインデックスクラスやラベルクラスのようなクラスを作る代わりにRuby標準のコレクションクラスを多用しています。これはRubyのクラスは十分に強力であるためであり、ルールをシンプルにして組み合わせで豊富な機能を実現したい、という思想に基づいているからです。

### 2.2. ブロックを使うこと

ブロックはRuby最大の特徴ではないでしょうか。ブロックで詳細な動作をカスタマイズできるような使い方がRubyらしい書き方につながると思います。

RedAmberでは上の図のメソッドはブロックも受け付けて処理をカスタマイズできます。例えば`slice`はインデックスまたはブーリアン配列を返すようなブロックを取ることができます。

![slice operation](https://raw.githubusercontent.com/heronshoes/red_amber/main/doc/image/dataframe/slice_operation.png "sliceの操作")

```ruby:ブロックの中でbooleanのベクトルを返すような処理を記述する
penguins.slice do |df|
  length = df.bill_length_mm
  min = length.mean - length.std
  max = length.mean + length.std
  (length >= min) & (bill_length <= max)
end
#=> bill_length_mmが2σの範囲にあるレコードをデータフレームで返す
```

ブロックの中ではVector（列）が受け付ける関数的なメソッド（上で言うと#mean, #std, #>=, #&, #<= が該当）を使いながらRubyの式として複雑な処理が書けます。Vectorのメソッドは [Apache ArrowのC++実装由来の豊富な関数](https://arrow.apache.org/docs/cpp/compute.html) の多くを使えるようにしてあります。

### 2.3. 暗黙のルールを駆使する

データフレームの変形(Reshaping)は、例えばmessyとtidyの変換(ワイドとロングの変換)がありますが期待される動作と必要なオプションは覚えにくいと思います。

Rubyでは明示的な指定よりも、直観に反しない範囲で暗黙的に行われる動作が好まれる文化があると思います。RedAmberでは暗黙の動作で使いやすくなるようにReshapingの操作を設計しました。

![Reshaping DataFrames](https://raw.githubusercontent.com/heronshoes/red_amber/main/doc/image/reshaping_dataframe.png "データフレームの変形操作")

ここで左側にあるtransposeは行/列を入れ替える操作ですが、何も指定しなければ一番左にある列を新しいキーとみなして入れ替えを行います

```ruby:転置用のテストデータ
uri = URI("https://raw.githubusercontent.com/heronshoes/red_amber/master/test/entity/import_cars.tsv")
import_cars = RedAmber::DataFrame.load(uri)

#=>
#<RedAmber::DataFrame : 5 x 6 Vectors, 0x000000000000f230>
     Year    Audi     BMW BMW_MINI Mercedes-Benz      VW
  <int64> <int64> <int64>  <int64>       <int64> <int64>
0    2017   28336   52527    25427         68221   49040
1    2018   26473   50982    25984         67554   51961
2    2019   24222   46814    23813         66553   46794
3    2020   22304   35712    20196         57041   36576
4    2021   22535   35905    18211         51722   35215
```

このデータに対して、transposeを引数無しで適用すると、一番左の列が新しいキーになり、元のキーが一番左の列になります。一番左の列の名前は、指定がないので既定値の`NAME`となります。

```ruby:引数を与えずにtransposeする
import_cars.transpose

#=>
#<RedAmber::DataFrame : 5 x 6 Vectors, 0x000000000000f244>
  NAME              2017     2018     2019     2020     2021
  <string>      <uint32> <uint32> <uint32> <uint16> <uint16>
0 Audi             28336    26473    24222    22304    22535
1 BMW              52527    50982    46814    35712    35905
2 BMW_MINI         25427    25984    23813    20196    18211
3 Mercedes-Benz    68221    67554    66553    57041    51722
4 VW               49040    51961    46794    36576    35215
```

実は、transposeは既定値:NAMEと同じ名前の:nameオプションを受け付けて、新しくできる名前の設定ができるように作ってあるので、オプション名を覚えていなくても思い出して使えるようになっています。

```ruby:新しくできる列の名前を指定してtransposeする
import_cars.transpose(name: :Manufacturer)

#=>
#<RedAmber::DataFrame : 5 x 6 Vectors, 0x000000000000f258>
  Manufacturer      2017     2018     2019     2020     2021
  <string>      <uint32> <uint32> <uint32> <uint16> <uint16>
0 Audi             28336    26473    24222    22304    22535
1 BMW              52527    50982    46814    35712    35905
2 BMW_MINI         25427    25984    23813    20196    18211
3 Mercedes-Benz    68221    67554    66553    57041    51722
4 VW               49040    51961    46794    36576    35215
```

この辺りはRやpandasの使用例を眺めながら、RedAmberではどのようにするのが使いやすいか自問しながら作っています。

## 3. コミットメッセージの絵文字を普及させる

RedAmberでやろうとしていることの3番目はこれです。
RedAmberではコミットメッセージの先頭に絵文字を使って分類しています。
![commit_tree_of_git.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2986819/b9f3f195-1887-3827-c87c-bb4f802eee96.png "コミットメッセージの例")

例えば新メソッドの追加では :zap: (zap)、リファクタリングは :recycle: (recycle)、ドキュメントの改善は:memo: (memo)、テストの改善は :white_check_mark: (white_check_mark)などとしています。独自なのは バグフィックスで 🦋 (butterfly)を使うことです。

バグフィックスで文字通り :bug: (bug)を使うのは良くと思うあるのですが、バグ治った感が出せると思うので🦋を広めていきたいです。

ちなみにmacでは青くてきれいな蝶なのですがWindowsでは赤い蛾のような絵文字なのでちょっと残念です。またQiitaでは :butterfly: って出ないんですね。

絵文字は、changelogをまとめる際に種類別に分類しやすくなるので便利、という長所もあります。私が使っている絵文字の一覧は、[gistにまとめてある](https://gist.github.com/heronshoes/b9b6991b3b855030ae25ec45dcffecde)のでご覧ください。VS codeのgit graphで出てこない絵文字を追加する設定も置いています。

## 4. 使う側から作る側の活動へ

私はこれまでユーザーとしてRubyを使ってきましたが、今年になって初めてオープンソースソフトウェアの活動に参加しました。今は特にRubyのメタプログラミングの部分で技術不足を感じていますが、成果物がこうして出来つつありますのでやってみてよかったと思っています。

Rubyを使うだけという立場の方は多いと思うので、Rubyの世界をもっと広げていくために私の経験を生かしていけるような機会を作っていきたいと思っています。

## おわりに

今日、RedAmber 0.3.0 をリリースしました。前バージョンの0.2.3までは機能追加するたびにだんだん遅くなっていましたが、ベンチマークを入れてコードを全面的に見直し、[qiita](https://qiita.com/heronshoes/items/3e60ee24a436530ddaac)と[note](https://note.com/heronshoes/n/nafb267e9a99b)の紹介記事で例に挙げた処理をベンチマークとして 11.7倍の高速化を実現しました。

それでもまだまだ遅いですが、コードの書き味には特徴あると思いますので試してみていただけると嬉しいです。

また、テストカバレッジをsimplecovで測定して100%まで引き上げました。修正の中でテストが書かれていないraiseがいくつかみつかりましたし、後置ifで１行だったためカバーされているように見えてしまっていたraiseもありました（RubocopのLineLengthを90に下げたので見つかった）。今後はカバレッジを維持していきます。

最後にRedAmberに関するリソースを紹介します。

- [nn examples of RedAmber - Jupyter notebook形式の操作事例集](https://github.com/heronshoes/docker-stacks/blob/RedAmber-binder/binder/examples_of_red_amber.ipynb)
([本体生データ](https://raw.githubusercontent.com/heronshoes/docker-stacks/RedAmber-binder/binder/examples_of_red_amber.ipynb))

- GitHubのdiscussionsで基本的な操作をプレゼンスタイルで解説しています。
https://github.com/heronshoes/red_amber/discussions/96