Ruby + PyCall でデータサイエンス100本ノック（構造化データ加工編）をやってみた

まだ誰もやってなさそうだったのでやってみました。

https://github.com/sonota88/100knocks-preprocess/tree/ruby-pycall

オリジナルのリポジトリ [The-Japan-DataScientist-Society/100knocks-preprocess](https://github.com/The-Japan-DataScientist-Society/100knocks-preprocess) をフォークして `ruby-pycall` ブランチを追加しています。

GitHub でノートブックを閲覧したい場合はこちら:
https://github.com/sonota88/100knocks-preprocess/blob/ruby-pycall/docker/work/answer/ans_preprocess_knock_Ruby_PyCall.ipynb

---

解答例が複数ある場合など一部TODOにしている箇所がありますが、100問全部動かすことができました（私は Python版からコピペして書き換えただけなので、これは PyCall.rb がすごい！ という話です）。


一応筆者について書いておくと、データサイエンスに関しては入門者レベルです。データフレームという概念は一応知ってるとか、Jupyter Notebook を触ったことはあるが日常的に使っているわけではない、という感じの人です。Python, Pandas, NumPy のいずれにも詳しくありません。

:::note

ちなみに Ruby + RedAmber 版は @heronshoes さんが作成中とのことです。

- [RedAmberで書いたらこうなる - pandasのquery - Qiita](https://qiita.com/heronshoes/items/962e312146dac8673b49)
- [The Adventure of RedAmber - A data frame library in Ruby - Speaker Deck](https://speakerdeck.com/heronshoes/the-adventure-of-redamber-a-data-frame-library-in-ruby?slide=43)
  - RubyKaigi 2023

:::




# 動かし方

リポジトリとブランチが違う以外はオリジナルの[データサイエンス100本ノック（構造化データ加工編）](https://github.com/The-Japan-DataScientist-Society/100knocks-preprocess)の README で書かれている手順と同じです。

```sh
git clone --branch ruby-pycall https://github.com/sonota88/100knocks-preprocess.git

cd 100knocks-preprocess
docker compose up -d --build --wait
```

`docker compose up` が完了するのを待ってからブラウザで [http://127.0.0.1:8888/](http://127.0.0.1:8888/) を開きます。

左のメニューから `/work/answer/ans_preprocess_knock_Ruby_PyCall.ipynb` を選択してノートブックを開きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234055/3b789f3a-edbe-7810-1b1c-76937d168007.png)



左側に Table of Contents を表示させておくと各問題に移動したいときに便利です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234055/e91d7768-8690-de7c-29aa-5d4b6716841e.png)




# メモ


## Ruby 向けのカスタマイズ

- Python 版の Dockerfile をコピー＋修正して Ruby 用の Dockerfile-ruby を用意する
- docker-compose.yml を修正して Dockerfile-ruby に切り替える

やったのはこれだけですね。ここはほとんど手間がかかっていません。

RubyData プロジェクトでメンテナンスされている `rubydata/datascience-notebook` という Docker イメージがありまして、

https://github.com/RubyData/docker-stacks

これをベースイメージとして使うのがポイントです。自前でカスタマイズする必要がほとんどなく、たいへん助かりました。このイメージは便利なのでどんどん使わせてもらいましょう！（← この記事で一番強調したいのはここ）

Python版の Dockerfile との差分はこんな感じ。

```diff
--- Dockerfile  2023-05-06 12:13:18.226946937 +0900
+++ Dockerfile-ruby     2023-05-06 12:13:18.242946463 +0900
@@ -1,5 +1,6 @@
-FROM jupyter/datascience-notebook:python-3.10.8
-#FROM jupyter/datascience-notebook:d53a302fbcd0
+# 2023-02-03
+FROM rubydata/datascience-notebook:24a7f04dfc46
+
 USER root
 ENV DEBCONF_NOWARNINGS yes
 
@@ -29,4 +30,6 @@
     && Rscript -e 'pak::repo_add(CRAN = "RSPM@2022-12-16"); pak::pak(c("DBI", "RPostgreSQL", "themis"))' \
     && rm -rf Pipfile* /tmp/* /var/tmp/*
 
+RUN pip install tabulate
+
 HEALTHCHECK --interval=5s --retries=20 CMD ["curl", "-s", "-S", "-o", "/dev/null", "http://localhost:8888"]
```

<!-- 問題によってエラーが出ることがあったため tabulate をインストールしていますが、それを除くとベースイメージの差し替えだけで済んでいます。 -->

ほぼベースイメージの差し替えだけで済んでいます。


## DataFrame#query

Python版の解答例では DataFrame#query を使っている箇所がいくつかあるのですが、pandas.rb ではエラーになります（2023-05-13 時点）。そこで、次のように DataFrame#[] を使う方法で愚直に書き換えました。

```ruby
# P-004 Python版の解答例1
# df_receipt[['sales_ymd', 'customer_id', 'product_cd', 'amount']] \
#            .query('customer_id == "CS018205000001" & amount >= 1000')

df_receipt[['sales_ymd', 'customer_id', 'product_cd', 'amount']] \
    [(df_receipt['customer_id'] == "CS018205000001") & (df_receipt['amount'] >= 1000)]
```

[PyCall.rb + Pandas: DataFrame#query の代わりにS式っぽく書けないか試してみた](https://qiita.com/sonota88/items/7c81beca2c86fdf21556) という別記事で書いたように他の対策もありますが、今回は素朴に DataFrame#[] を使うだけにしました。なるべく素のまま書く方針です。





# 参考

- [mrkn/pycall.rb: Calling Python functions from the Ruby language](https://github.com/mrkn/pycall.rb)
- [Ruby-Pythonブリッジライブラリ「PyCall」を使ってRubyでデータ分析をしよう！ (1/3)|CodeZine（コードジン）](https://codezine.jp/article/detail/10251)
- [workshop-materials/users_guide.md at master · RubyData/workshop-materials · GitHub](https://github.com/RubyData/workshop-materials/blob/master/iruby_guides/users_guide.md)
  - IRuby Notebook 入門用テキスト
- [PyCall Ruby版 Tips - Qiita](https://qiita.com/stat/items/a383451e7f824e4c9627)




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

<!-- Docker+IRuby（rubydata/datascience-notebook）にgemを追加して使う - Qiita -->
https://qiita.com/sonota88/items/9d9687105d3fc18f2fdf

<!-- Ruby+PyCall.rbでLibreOffice Calcのオートメーションをやってみた（Ubuntu 18.04） - Qiita -->
https://qiita.com/sonota88/items/aeacaf0ec7957715cdff

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

<!-- Ruby+PyCall.rbでLibreOffice Calcのオートメーションをやってみた（Ubuntu 18.04） -->
https://qiita.com/sonota88/items/aeacaf0ec7957715cdff