🔬

脱 Pandas !〜Ibis, Polars の勧め〜

2024/06/03に公開

脱 Pandas !〜Ibis, Polars の勧め〜

はじめに

アイディオットのデータサイエンティスト、佐藤です。
昨年(2023 年)末ぐらいから polarsIbis というデータ処理ライブラリを Qiita でよく目にするようになったので、実務で 1 週間ほど触ってみました。
今回は pandas と比較しての感想、便利に感じた機能などを紹介していきます。

対象

  • pandas で大容量ファイルの読み込みにイライラしている方
  • pandas から新しいデータ処理ライブラリへの乗り換えを考えているけど、学習がめんどくさい方
  • polars 使ってみたけど、pandas との違いに悪戦苦闘したり、違和感覚えたりしている方

筆者のレベル感

pandas: 3 年以上。データ処理は pandas から入りました。普段触らない関数とかは覚えてないです。
polars: pandas との互換表を見たり、実際に触ってみたりして違和感を覚えたので、3 日ほどで Ibis に乗り換え。
Ibis: こちらの Ibis 100 本ノック中、37 まで実行。あとは回答を辞書的に使いながら、1 週間ほど実務で使用。

結論

pandas ネイティブの自分にとって、やはり pandas は便利
Ibis をプラットフォームにして、メインエンジンは pandas、pandas の弱いところはエンジンを polars にしたり、Ibis の関数で処理する のがしっくりきました。

ライブラリ説明

polars

2023 年末ぐらいから Qiita でよく目にするようになりました。超速い。
Perplexity 君に聞いたら、2021 年にリリースされたそうです。
pandas との違いとして、Rust 製であり、遅延評価も実装できるため、処理の高速化・メモリの効率化を達成できているそうです。参照
O'REILLY も 2024 年の第 3 クォーターに "Python Polars" を出版する予定なので、業界でも注目度は高めではないでしょうか。

Ibis

データ処理統合ライブラリ。
pandas、polars の他、DASK、MySQL など主要なエンジンを 20 以上統合しています。参照
Ibis の書式に則れば、エンジンごとに記述を変えることなく、同じ記述で処理を完結可能。
エンジンの切り替えは 1 コードで完了。
大きなデータ処理のために DASK を覚えたり、高速化のために polars の書き方を覚えたり、といったことをせずに、Ibis さえ覚えれば、学習コストを抑えられるのでは...?

環境

Google Colab
CPU のみを使用し、ハイメモリにしておきます。
Ibis と polars をインストールしておきましょう。
なお、ibis-framework としないと、正しく Ibis はインストールされません。

!pip install ibis-framework
!pip install polars

比較

読み込み速度・メモリ

データ

Kaggle の H&M Personalized Fashion Recommendations にある、顧客データ・商品データ・売上データから 5GB ほどのデータを作成しました。

pandas

pandas では読み込みに 1 分 30 秒ほどかかりました。

import pandas as pd

start = time.time()
# データ読み込み
pd.read_csv("./data/orderly_data.csv")
processing_time = time.time() - start
print(f"time: {processing_time:.2f} sec")

また、使用したメモリも 5GB ほどですので、読み込み時にデータをそのままメモリに書き込んでいることがわかります。

polars

polars でも pandas と同じように、read_csv() でデータを読み込めます。
かかった時間は 30 秒も行きませんでした。pandas の 1/3 以下ですね。

import polars as pl

start = time.time()
# データ読み込み
pl.read_csv("./data/orderly_data.csv")
processing_time = time.time() - start
print(f"time: {processing_time:.2f} sec")

使用メモリも 400MB 程度と、こちらは 0.08 倍と大幅に負荷を減少できました。

Ibis

お次に Ibis で試してみましょう。
Ibis では、実装する前にバックエンドで働くエンジンを指定する必要があります。
また、遅延評価か逐次評価も選べることができます。
せっかくなので、エンジンに polars、評価モードを遅延評価にして読み込んでみましょう。

import ibis

# 遅延評価モードに
ibis.options.interactive = False
# エンジンを polars に
ibis.set_backend("polars")

start = time.time()
# データ読み込み
ibis.read_csv("./data/orderly_data.csv")
processing_time = time.time() - start
print(f"time: {processing_time:.2f} sec")

結果、時間は 1 秒もかかりませんでした。
純粋な polars の 1/30、pandas の 1/90 以下の時間です。

使用メモリを見ても 40MB ほどですね。これは Ibis の圧勝のように見えます。

ちなみに、遅延評価のままエンジンに pandas を使うとどうなるのでしょうか?

import ibis

# 遅延評価モードに
ibis.options.interactive = False
# エンジンを pandas に
ibis.set_backend("pandas")

start = time.time()
# データ読み込み
ibis.read_csv("./data/orderly_data.csv")
processing_time = time.time() - start
print(f"time: {processing_time:.2f} sec")

何と、読み込みだけで 2 分以上と、純粋な pandas よりも遅くなってしまいました。
使用メモリも 10GB を超えていますので、遅延評価が逆に災いして、裏でよからぬこと(恐らく、重複してデータベースにクエリを飛ばしてそう)をしていますね。

polars の問題点

データ読み込みの速度だけ見ると、pandas よりも polars や Ibis に軍配が上がりそうですが、次に polars で個人的に使わなくていいやと感じた点を上げます。
なお、これは pandas ネイティブな私にとっての問題点であり、他の方にとっては利点でもありますので、ご注意ください。

使用データ

ここから最後まで、kaggle の WiDSHIROSHIMA データソン『広島フード×需要予測』 より、売り上げデータを使用します。

データ読み込み

では早速、polars でデータを読み込んでみましょう。

pl.read_csv("../data/sales.csv")

すると、"Original error: remaining bytes non-empty" とエラーが出ます。

<p>

これは、あるカラムに複数の型が混在していることを示しています。
polars では、上位 100 行のデータから各カラムの型を推論するため、このようなエラーが起きてしまいます。
修正のためには、以下の作業が必要です。

  1. read_csv() の引数 infer_schema_length に大きな値を入れて推論に使う行数を拡大
  2. 各カラムのユニークな値を調べて、型が混在しているカラムを見つける
  3. read_csv() の引数 dtypes={"column": type} に、混在しているカラム名と想定の型を与える
# 1. 推論に使う行数を拡大し、想定と違う型が入っていないかを確認
df = pl.read_csv("../data/sales.csv", infer_schema_length=10000)
df

# 2. 各カラムのユニークな値を調査し、型の混在を確認
df.select("見積番号").unique()

# 3. dtypes に任意のカラム名と型を与える
df = pl.read_csv("../data/sales.csv", dtypes={"見積番号": pl.datatypes.Int16})

実際にやってみると、データのドメイン知識がないと結構時間かかりますし、めんどくさいです(笑)。
また、私にとって Python の大きな利点は、C のように型を指定せずとも暗黙的に型推定してくれること(もちろん、これは大きなデメリットでもあります)だと考えています。
pandas、Ibis では、違う型が入っていても読み込んでくれますし、想定と違う型だとしても、分析を進めていくうちに直していけばいいやと考えています。
ですので、最初のデータ読み込みから躓く可能性のある polars は私にとってはあまり魅力的に写りませんでした。

Ibis の問題点

行の操作

まず、Ibis には index という概念がないようです。
そのため、特定の条件の行を操作したい場合、一回 pandas や別のエンジンに直す必要があります。
以下は、削除の例です。

import ibis
import pandas as pd

# データ読み込み
df = ibis.read_csv("../data/sales.csv", index_col="NO")
# 発送予定日カラムを時系列型にキャスト
df = df.mutate(発送予定日=df["発送予定日"].cast("timestamp"))
# 発送予定日から、年月を取ってきて、新たに発送予定年月カラムを作成
df = df.mutate(発送予定年月=df["発送予定日"].strftime("%Y-%m"))

# pandas に変更
df = df.to_pandas()
# 発送予定年月が 1900 年代の行を削除
df = df.drop(index=df.loc[df["発送予定年月"]=="1900-01"].index)

統計処理

また、私はまだ試していませんが、相関係数などの統計処理も一部実装されていないようです。
このようなコードが少ない場合は、execute() を使うとバックエンドの型に変換されますので、以下のように書くといいでしょう。

import ibis
import ibis.selectors as s
import pandas as pd

df = ibis.read_csv("../data/sales.csv", index_col="NO")
# 1. df.select(s.numeric()) で数値型のみを抽出
# 2. 直後の .execute() で pandas 型に変換
df.select(s.numeric()).execute().corr()

Ibis の個人的に好きな機能

ここまで Ibis、polars のメリットデメリットを見てきましたが、最後に私の Ibis の個人的に好きな機能をご紹介したいと思います。

カラム操作

Ibis で便利な点は、カラムをリストや型で指定して、一挙に操作できることです。

import ibis
import ibis.selectors as s
import pandas as pd

df = ibis.read_csv("../data/sales.csv", index_col="NO")
# 金額、単価を数値型に
df = df.mutate(s.across(["金額", "単価"], _.replace(",", "").cast("int")))
# 数値型のカラムの合計、平均、S.D. を算出し、金額_sum のようなカラム名にする
df.mutate(s.across(s.numeric(),
          {"sum": _.sum(), "mean": _.mean(), "sd": _.std()},
          names="{col}_{fn}"))

データ確認

また、info() で pandas よりも詳細に全体のデータが確認できることも大きなメリットだと思います。
私は、pandas の行列表示が好きなので、バックエンドの pandas 型式に直しています。

import ibis
import pandas as pd

df.info().execute()

上記の 'nullable' は欠損値を取りうるか、'nulls' は欠損値の数、'non_nulls' は欠損値でない値の数、'null_frac' は欠損値の出現頻度、'pos' はカラムの位置を示しています。

まとめ

ここまで、polars、Ibis のメリットデメリットを pandas と見比べてきました。
polars は pandas よりも速いが型に厳しい、Ibis は行の操作や pandas にある機能を一部持っていないというデメリットがありつつも、統合ライブラリのため、適宜バックエンドのエンジンを切り替えれば、対応できることがわかりました。
pandas ネイティブな私にとって、もちろん pandas は一番やりやすいですが、Ibis には pandas よりも便利な機能が備わっていますので、今後は Ibis を中心に、やりたいことに合わせて pandas や polars などに切り替える方式 を取ってみようかと思います。

ここまでお読みいただき、ありがとうございました。

引用

新しいデータ処理ライブラリの学習はもう不要! Python 初学者のための Ibis 100 本ノック
超高速…だけじゃない!Pandasに代えてPolarsを使いたい理由
We Are Writing Python Polars: The Definitive Guide
Rust製高速データフレームライブラリ、Polarsを試す
pandasから移行する人向け polars使用ガイド
Polarsのread_csv()で、AhrefsのCSVが読み込みエラーになるときの対策

あとがき

AI・データ利活用をリードし、世界にインパクトを与えるプロダクトを開発しませんか?

アイディオットでは、今後の事業拡大及びプロダクト開発を担っていただけるエンジニアチームの強化を行っております。
さらに会社の成長を加速させるため、フロントエンドエンジニア、バックエンドエンジニア、インフラエンジニアのメンバーを募集しております!
日本を代表する企業様へ自社プロダクトを活用した、新規事業コンサルティング、開発にご興味のある方はお気軽にご連絡ください。

【リクルートページ】
https://aidiot.jp/recruit/
【募集ポジション一覧】
https://open.talentio.com/r/1/c/aidiot/homes/3925
【採用についてのお問合せ先】
株式会社アイディオット 採用担当:佐藤
メールアドレス:recruit@aidiot.jp

Discussion