3 PyCallを使ってみる(2)
3.4 PyCallによるデータ分析の実例
PyCall経由でpandasを使う例として、タイタニック号の乗客データをデータフレームとして読み込み、簡単な前処理として分布を調べ、欠損値処理をしてみます。以下のコードは、pryを使って実行していくことを想定しています。
まずは、利用するライブラリをロードしておきます。
require 'pycall/import' include PyCall::Import require 'matplotlib/pyplot' pyimport :pandas, as: :pd pyimport :seaborn, as: :sns
データはseabornライブラリのload_dataset
関数を使いpandasのデータフレーム形式で取得します。
df = sns.load_dataset.('titanic'); nil # この nil は df の中身が全て表示されてしまうのを防ぐため
変数df
が参照するオブジェクトがpandasのデータフレームです。
df.type #=> pytype(DataFrame)
データの大きさを調べましょう。
df.shape #=> (891, 15)
このデータは、15カラムで構成されるデータが890個あります(残りの1行はヘッダ)。15個のカラムのうち、以下のように内容が重複しているものがあります。
-
survived
はalive
をno
→ 0、yes
→ 1と変換して生成したもの -
embarked
はembark_town
の頭文字を取ったもの -
pclass
はclass
を数値にしたもの -
sex
とwho
はmale
→man
、female
→woman
という対応関係にある
このように内容が重複するカラムが存在すると、情報量が変わらないのに処理量が増えてしまうため、これらは削除しましょう。
df = df.drop.(%w[alive embark_town class who], axis: 1); nil
次に欠損値の処理をします。このデータはどのくらい欠損値を含んでいるでしょうか? 調べてみましょう。
df.isnull.().sum.() # => survived 0 # pclass 0 # sex 0 # age 177 # sibsp 0 # parch 0 # fare 0 # embarked 2 # adult_male 0 # deck 688 # alone 0 # dtype: int64
これより、age
カラムに177個、deck
カラムに688個、embarked
カラムに2個の欠損値が存在することが分かりました。
全体で890行なので、半分以上が欠損しているdeck
カラムは分析では使えません。捨てることにします。
df = df.drop.('deck', axis: 1); nil
次にage
カラムの分布を見てみましょう。分布の可視化のため、seabornのkdeplot
とrugplot
を使います。
sampled_age = df.age.dropna.().sample.(100) sns.kdeplot.(sampled_age, shade: true, cut: 0) sns.rugplot.(sampled_age) Matplotlib::Pyplot.show()
ageの平均値も見てみましょう。データフレームのdescribe
メソッドを使うと要約統計量を全て確認できます。
df.describe.() # => survived pclass age sibsp parch fare # count 891.000000 891.000000 714.000000 891.000000 891.000000 891.000000 # mean 0.383838 2.308642 29.699118 0.523008 0.381594 32.204208 # std 0.486592 0.836071 14.526497 1.102743 0.806057 49.693429 # min 0.000000 1.000000 0.420000 0.000000 0.000000 0.000000 # 25% 0.000000 2.000000 20.125000 0.000000 0.000000 7.910400 # 50% 0.000000 3.000000 28.000000 0.000000 0.000000 14.454200 # 75% 1.000000 3.000000 38.000000 1.000000 0.000000 31.000000 # max 1.000000 3.000000 80.000000 8.000000 6.000000 512.329200
これより、age
の平均は約29.7、中央値は28であることが分かります。
可視化した分布の最頻値もだいたいその辺りの値なので、ここでは欠損値を中央値で埋めることにします。
df.age.fillna.(df.age.median.(), inplace: true)
もう一度、age
カラムの欠損値の個数を調べてみましょう。
df.age.isnull.().sum.() #=> 0
全ての欠損値が埋まっています。
ここまでと同じ内容を含み、さらにカテゴリ変数のダミー変数化、分類モデルの構築をしているIRubyノートブックをpycallリポジトリのexampleディレクトリに入れてあります。以下のURLから閲覧できるのでぜひご覧ください。
3.5 その他の使用例
PyCallのリポジトリには、本稿で紹介していない例も用意しています。
この中のnotebooksディレクトリには、IRubyとPyCall、およびmatplotlibを利用した例をノートブック形式で用意しています。
4 PyCallの今後の開発予定
本稿執筆時点では、PyCallの既存実装の見直しと改善、及びnumpyのC-APIを利用したラッパーの開発を進めています。
RubyKaigi 2017までに安定版をリリースします。安定版では、本稿執筆時点のPyCallよりも、よりRubyらしい書き方を許せる仕組みを導入する予定です。また、ActiveRecordやRedArrowとpandasとの間でのデータの連携など、RubyのライブラリとPythonのライブラリの間の相互運用性を高めます。
2017年中にはKerasとChainerを完全にサポートし、RubyでもGPUを用いた本格的な深層学習モデルを利用できるようにする予定です。
2018年以降は、R、Julia、Stan、Scala(Spark)などPython以外のプログラミング言語やシステムとの間のブリッジを開発し、それらを同時に利用した際の相互運用性の向上を目指していきます。
5 まとめ
本稿では、Rubyをデータサイエンスで使えるプログラミング言語にするためのRuby-PythonブリッジライブラリであるPyCallについて紹介し、PyCallを介してPythonのライブラリを利用したデータ分析工程の例を示しました。さらに、PyCallの現在の開発状況と今後の開発計画についても説明しました。
Rubyをデータサイエンスで使えるプログラミング言語にするために言語間ブリッジを整備していくアプローチは多数の課題を抱えています。本稿執筆時点でPyCallの開発者は私一人です。上述の通り、多言語間ブリッジについて他にもやりたいことはいくつもあるので、この方向性に賛同し開発をサポートしてくださる方を募集しています。ライブラリを自力で実装できる方、ドキュメンテーションが得意な方、ウェブサイトを作るのが得意な方を探しています。われこそはと思った方は今すぐ筆者までご連絡ください。
PyCallの開発をはじめ、Rubyでのデータサイエンス環境を整備していくにあたり、実世界での利用例や困りごとの実例をたくさん知りたいと思っています。「Railsで作ったシステムがあって、データ分析を導入したいが良いやり方が分からない」とか、「PyCallをこういう用途で使おうと思っている」といった相談や報告をいただけるとうれしいです。ご連絡をお待ちしております。
参考文献
- [1]村田賢太「データサイエンスにおけるRubyの現在の位置づけと可能性」ITOC 研究レポート(2016)
- [2]芦田恵大、三軒家佑將、西田孝三「Rubyを用いた初等統計解析の整備と構築」最終報告書
- [3]somaticio/tensorflow.rb.
- [4]Red Data Tools.
- [5]DataScience.rb ワークショップ 〜ここまでできる Rubyでデータサイエンス〜:RubyもApache Arrowでデータ処理言語の仲間入り #datasciencerb - (2017-05-22)
- [6]mrkn/pycall.
- [7]Anaconda install | Continuum Analytics: Documentation.
- [8]pyenv/pyenv.
- [9]Building Python.