🐥

RubyのPolarsでデータサイエンティスト協会の100本ノックやってみた — 1 ~ 15問

2024/03/27に公開

環境

本記事では、下記の環境で実行しています。

バージョン
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へアクセスしてください。

https://github.com/The-Japan-DataScientist-Society/100knocks-preprocess/tree/master/docker/work/data

データの読み込み

先ほど、取得してきたデータを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"

表示オプションの変更については、下記の記事を参考にいたしました。

https://qiita.com/kojix2/items/dbe8849184a9c87d6126

演習問題

ここから、演習問題に入っていきたいと思います。
演習問題(Python版)は下記のURLにあるJupyter Notebook(.ipynb)からご覧になれます。
さくさく解答例と結果を載せていきます。

https://github.com/The-Japan-DataScientist-Society/100knocks-preprocess/blob/master/docker/work/

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があり、リファレンスとしてとても充実していますので、こちらをご参照ください。

https://www.rubydoc.info/gems/polars-df

データサイエンスのイメージのないRubyであっても、polars-dfを利用することでかなり自由度高くデータフレームの加工を行うことができそうです。今日は力尽きたのでこのつづきはまた書ければと思います。

Discussion