RubyのPolarsでデータサイエンティスト協会の100本ノックやってみた — 1 ~ 15問
環境
本記事では、下記の環境で実行しています。
バージョン | |
---|---|
macOS | 14.1 |
Ruby | 3.0.6p216 (2023-03-30 revision 23a532679b) [arm64-darwin22] |
polars-df | 0.7.0 arm64-darwin |
記事の趣旨
本記事では、Rust製の高速データフレームであるPolarsのRuby版を利用して、データサイエンティスト協会の提供する「データサイエンス100本ノック(構造化データ加工編)」をやってみることを目的にしています。
Polarsについては、下記の公式サイトを参照してください。
Ruby版のPolarsは、polars-dfというgem名で開発されています。
Rubyを使用したデータサイエンスライブラリをばりばり開発されているankane(Andrew Kane)さんによるgemです。
深層学習やデータフレーム、LightGBM、ベイズ推定など幅広い用途のRuby用データサイエンスライブラリを開発されているすごい方です。
また、本記事では「データサイエンティスト協会スキル定義委員」の「データサイエンス100本ノック(構造化データ加工編)」を利用しています。
実際のデータサイエンスの現場に近いデータと素晴らしい問題が用意されています。
どちらもありがたく利用させていただきたいと思います。
データの準備
ライブラリの読み込み
はじめに利用するgemを読み込んでおきます。
require 'open-uri'
require "polars-df"
データの取得
下記のコードでGitHub上からCSVデータをダウンロードしてきます。
# データのダウンロード
# カレントディレクトリの変更とdataディレクトリの作成
Dir.chdir(File.dirname(__FILE__))
Dir.mkdir("data") unless Dir.exist?("data")
# CSVデータのダウンロード
base_url = 'https://raw.githubusercontent.com/The-Japan-DataScientist-Society/100knocks-preprocess/master/docker/work/data/'
file_names = [
'category.csv',
'customer.csv',
'geocode.csv',
'product.csv',
'receipt.csv',
'store.csv',
]
# ローカル環境に保存
file_names.each do |file_name|
url = base_url + file_name
File.open("./data/#{file_name}", 'wb') do |f|
f.write URI.open(url).read
end
end
もし手動でダウンロードする人は、下記のURLへアクセスしてください。
データの読み込み
先ほど、取得してきたデータをPolarsのデータフレームとして読み込みます。
# データの読み込み
df_customer = Polars.read_csv("data/customer.csv")
df_category = Polars.read_csv("data/category.csv")
df_geocode = Polars.read_csv("data/geocode.csv")
df_product = Polars.read_csv("data/product.csv")
df_receipt = Polars.read_csv("data/receipt.csv")
df_store = Polars.read_csv("data/store.csv")
また、データフレームを出力した際の表示オプションについても変更しておきます。
最大行数を50行に、最大列数を20列に設定します。
# 表示オプションの変更
ENV["POLARS_FMT_MAX_ROWS"] = "50"
ENV["POLARS_FMT_MAX_COLS"] = "20"
表示オプションの変更については、下記の記事を参考にいたしました。
演習問題
ここから、演習問題に入っていきたいと思います。
演習問題(Python版)は下記のURLにあるJupyter Notebook(.ipynb)からご覧になれます。
さくさく解答例と結果を載せていきます。
P-001: レシート明細データ(df_receipt)から全項目の先頭10件を表示し、どのようなデータを保有しているか目視で確認せよ。
解答例
puts df_receipt.head(10)
結果
P-002: レシート明細データ(df_receipt)から売上年月日(sales_ymd)、顧客ID(customer_id)、商品コード(product_cd)、売上金額(amount)の順に列を指定し、10件表示せよ。
解答例
puts (
df_receipt
.select(['sales_ymd', 'customer_id', 'product_cd', 'amount'])
.head(10)
)
結果
P-003: レシート明細データ(df_receipt)から売上年月日(sales_ymd)、顧客ID(customer_id)、商品コード(product_cd)、売上金額(amount)の順に列を指定し、10件表示せよ。ただし、sales_ymdをsales_dateに項目名を変更しながら抽出すること。
解答例
puts (
df_receipt
.rename({"sales_ymd" => "sales_date"})
.select(["sales_date", "customer_id", "product_cd", "amount"])
.head(10)
)
結果
P-004: レシート明細データ(df_receipt)から売上日(sales_ymd)、顧客ID(customer_id)、商品コード(product_cd)、売上金額(amount)の順に列を指定し、以下の条件を満たすデータを抽出せよ。
- 顧客ID(customer_id)が"CS018205000001"
解答例
PolarsのExpressionも利用できます。
puts (
df_receipt[
Polars.col("customer_id") == "CS018205000001"
]
.select(["sales_ymd", "customer_id", "product_cd", "amount"])
)
結果
P-005: レシート明細データ(df_receipt)から売上日(sales_ymd)、顧客ID(customer_id)、商品コード(product_cd)、売上金額(amount)の順に列を指定し、以下の全ての条件を満たすデータを抽出せよ。
- 顧客ID(customer_id)が"CS018205000001"
- 売上金額(amount)が1,000以上
解答例
puts (
df_receipt[
(Polars.col("customer_id") == "CS018205000001") &
(Polars.col("amount") >= 1000)
]
.select(["sales_ymd", "customer_id", "product_cd", "amount"])
)
結果
P-006: レシート明細データ(df_receipt)から売上日(sales_ymd)、顧客ID(customer_id)、商品コード(product_cd)、売上数量(quantity)、売上金額(amount)の順に列を指定し、以下の全ての条件を満たすデータを抽出せよ。
- 顧客ID(customer_id)が"CS018205000001"
- 売上金額(amount)が1,000以上または売上数量(quantity)が5以上
解答例
puts (
df_receipt[
(Polars.col("customer_id") == "CS018205000001") &
((Polars.col("amount") >= 1000) | (Polars.col("quantity") >= 5))
]
.select(["sales_ymd", "customer_id", "product_cd", "quantity", "amount"])
)
結果
P-007: レシート明細データ(df_receipt)から売上日(sales_ymd)、顧客ID(customer_id)、商品コード(product_cd)、売上金額(amount)の順に列を指定し、以下の全ての条件を満たすデータを抽出せよ。
- 顧客ID(customer_id)が"CS018205000001"
- 売上金額(amount)が1,000以上2,000以下
解答例
puts (
df_receipt[
(Polars.col("customer_id") == "CS018205000001") &
(Polars.col("amount") >= 1000) &
(Polars.col("amount") <= 2000)
]
.select(["sales_ymd", "customer_id", "product_cd", "amount"])
)
結果
P-008: レシート明細データ(df_receipt)から売上日(sales_ymd)、顧客ID(customer_id)、商品コード(product_cd)、売上金額(amount)の順に列を指定し、以下の全ての条件を満たすデータを抽出せよ。
- 顧客ID(customer_id)が"CS018205000001"
- 商品コード(product_cd)が"P071401019"以外
解答例
puts (
df_receipt[
(Polars.col("customer_id") == "CS018205000001") &
(Polars.col("product_cd") != "P071401019")
]
.select(["sales_ymd", "customer_id", "product_cd", "amount"])
)
結果
P-009: 以下の処理において、出力結果を変えずにORをANDに書き換えよ。
- df_store.query('not(prefecture_cd == "13" | floor_area > 900)')
解答例
puts (
df_store[
(Polars.col("prefecture_cd").cast(String) != "13") &
(Polars.col("floor_area") <= 900)
]
)
結果
P-010: 店舗データ(df_store)から、店舗コード(store_cd)が"S14"で始まるものだけ全項目抽出し、10件表示せよ。
解答例
puts (
df_store[
Polars.col("store_cd").str.starts_with("S14")
]
.head(10)
)
結果
P-011: 顧客データ(df_customer)から顧客ID(customer_id)の末尾が1のものだけ全項目抽出し、10件表示せよ。
解答例
puts (
df_customer[
Polars.col("customer_id").str.ends_with("1")
]
.head(10)
)
結果
P-012: 店舗データ(df_store)から、住所 (address) に"横浜市"が含まれるものだけ全項目表示せよ。
解答例
puts (
df_store[
Polars.col("address").str.contains("横浜市")
]
)
結果
P-013: 顧客データ(df_customer)から、ステータスコード(status_cd)の先頭がアルファベットのA〜Fで始まるデータを全項目抽出し、10件表示せよ。
解答例
polars-dfでは、sortメソッドの昇順・降順のオプションは、descending
ではなくreverse
になっているようでした。
puts (
df_customer[
Polars.col("status_cd").str.contains("^[A-F]")
]
.sort("status_cd", reverse: true)
.head(10)
)
結果
P-014: 顧客データ(df_customer)から、ステータスコード(status_cd)の末尾が数字の1〜9で終わるデータを全項目抽出し、10件表示せよ。
解答例
puts (
df_customer[
Polars.col("status_cd").str.contains("[1-9]$")
]
.head(10)
)
結果
P-015: 顧客データ(df_customer)から、ステータスコード(status_cd)の先頭がアルファベットのA〜Fで始まり、末尾が数字の1〜9で終わるデータを全項目抽出し、10件表示せよ。
解答例
puts (
df_customer[
Polars.col("status_cd").str.contains("^[A-F].*[1-9]$")
]
.head(10)
)
結果
結論
polars-dfはまだドキュメントが充実していないのですが、やりたいことでPolarsで検索してpolars-dfのGitHubの実装箇所を直接確認することでpolars-dfでの実現方法を早く調べることができると思いました。
失礼しました。polars-dfのRubyDocがあり、リファレンスとしてとても充実していますので、こちらをご参照ください。
データサイエンスのイメージのないRubyであっても、polars-dfを利用することでかなり自由度高くデータフレームの加工を行うことができそうです。今日は力尽きたのでこのつづきはまた書ければと思います。
Discussion