Pandas.DataFrame (Python) 風のクラスを作って rubygem として公開した
ruby には CSV::Table
があるので、基本的なことはできるが、 Pandas の DataFrame.read_csv
のように多機能な CSV の読み込みメソッドが(多分)なかったので、 Pdtable::Table
という CSV::Table
を拡張したクラスを Gem で作って公開した。はじめは CSV の読み込みを行うメソッドだけのモジュールとして作っていたが、拡張して Pandas の DataFrame のように多機能なクラスにすることもあるかも、と思って CSV::Table
の拡張クラスとした。
pdtable | RubyGems.org | your community gem host
使い方
詳細は README をどうぞ。 CSV::Table
のサブクラスなので、 CSV::Table
の全メソッドが使える。読み込んだあとの加工や参照は二次元の Array より強い。今のところ列のデータタイプの明示的な指定と、スキップする行の指定ができる。
require 'pdtable' t = Pdtable::Table.new 'path/to/data.csv', dtype: {col1: String}, skiprows: [1, 3]
動機
- 週末でなんか書きたいな
- 最近 Ruby 書いてない
- Rails で Web アプリ作るのもいいけど、何作っていいかわからない
- rubygem でも作ろう
- 最近お世話になっている Python を参考にしよう
- Pandas の DataFreme は強い。 Ruby にもあっていい
DataFrame.read_csv
だけ作ろう
弱点
CSV.table
で読み込んでから加工しているので、原理的に CSV.table
より速くならない。ベンチマークは測っていないが、加工もデータ量が大きくなるほど遅くなるようになっている。大きなデータを読み込むのではなく、 CSV を手軽に読み込みたいときに使うツールになっている。
以下、メモ。
Rubygem の開発
Gem を作るのは久しぶりだったので、こまめにググりながらの開発になった。すぐにできるだろうと思って書き始めたが、いろいろハマリポイントがあって時間がかかってしまった。ちゃんと TDD 的に開発を進めた。
準備
# gem自信のアップデート gem update --system # bundler未インストールの場合はインストール gem install bundler # bundlerインストール済の場合はアップデート gem update bundler
雛形の作成
$ bundle gem test_gem --test=minitest # minitest の場合
gemspec の編集
Gem::Specification.new do |spec| spec.name = "pdtable" spec.version = Pdtable::VERSION spec.authors = ["kohei-kimura"] spec.email = ["kkimura62@icloud.com"] # summary と description を編集 spec.summary = %q{A Pndas.DataFrame-like class} spec.description = %q{Pdtable is a Pandas.DataFrame-like class that is expanded from CSV::Table. It has some Pandas.DataFrame-like methods, for example `read_csv`.} spec.homepage = "https://github.com/kohei-kimura/pdtable" spec.license = "MIT" # rubygem.org に公開する場合は以下のブロックを消す # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host' # to allow pushing to a single host or delete this section to allow pushing to any host. # if spec.respond_to?(:metadata) # spec.metadata["allowed_push_host"] = " https://rubygems.org" # else # raise "RubyGems 2.0 or newer is required to protect against " \ # "public gem pushes." # end
クラスの作成
モジュールにクラスを追加する場合は <module名>/<class名>.rb
を作ってそこで定義するといいっぽい。((実際にはクラス名 = モジュール名としていたときの名残で lib/pdtable/pdtable.rb
になっている。))
# lib/pdtable/table.rb require 'csv' module Pdtable class Table < CSV::Table end end
ここが第一のハマリポイントになって、クラス名をモジュール名と同じにしていた。 Gem の公開までは問題なかったのだが、 公開した Gem をインストールして使ってみると問題が生じる。実際に Pdtable を require すると、 Pdtable
はクラスじゃないよと怒られる。
テスト、ビルド、リリース
# テスト # Rakefile に test.verbose = true を追加しておく $ rake test # ビルド $ rake build # リリース # rubygems.org への登録、 API キーの取得、 git commit が必要 $ rake release
Case 文における Class
ハマリポイントその2。列の値を変換するときにクラスごとに処理を分岐させているが、そのときに Case 文を使っていた。 Case の比較は ===
が使われる。インスタンスのクラスによって分岐させるのではなく、 {col1: String}
のような形で列名とクラスを持っているハッシュを Case に渡していたので、意図したように分岐しなかった。その原因にすぐには気付けず、辛かった。
# ※type には Integer, Float, String, DateTime などが入る # NG case type when Integer # 処理 when Float # 処理 when String # 処理 when DateTime # 処理 end # OK if type == Integer # 処理 elsif type == Float # 処理 elsif type == String # 処理 elsif type == DateTime # 処理 end