0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

みなさん、こんにちは!

今回は、「新時代のデータ操作ライブラリ」として話題のPolarsについて解説します。
「pandasより早いらしい」という話はデータエンジニアであれば耳にしたことがある方が多いのではないでしょうか。

公式https://pola.rs/によると、
"エンジンはRustで書かれており、マルチスレッドでの効率的な処理が可能。また、カラム指向の処理とベクトル化技術を駆使して、最新のプロセッサ上で高いパフォーマンスを実現。"
..だそうです。

色々な魅力がありそうなPolarsですが、今回はトピックを絞ってPolarsの機能の中でも大きな特徴ともいえる「Lazy(遅延実行)モード」について検証していきたいと思います。

2つのデータ処理モード

Polarsでは、EagerモードLazyモードの2つの異なる実行モードが提供されています。
Eagerモードでは、操作が即座に実行され結果がすぐに得られます。
一方Lazyモードでは操作が蓄積され、実際の計算は明示的にcollect()を呼び出すまで遅延され、処理全体を最適化します。

Polarsのscan_csv()とread_csv()の違い

Polarsには、CSVファイルを読み込むための2つの主要な関数があります。
それがscan_csv()とread_csv()です。この2つの関数は同じCSVファイルを読み込むためのものですが、どうやってデータを扱うかに違いがあります。
それぞれの違いは以下の通りです。

read_csv(): すぐにデータを見たい時に

read_csv()は、CSVファイルを「DataFrameオブジェクト」として読み込んで結果を得るための関数です。
この関数を使うと、ファイル全体が一気にメモリに読み込まれます。
小規模なデータやすぐにデータを確認したい時に便利です。

scan_csv():大きなデータを効率よく扱いたい時に

一方、scan_csv()は、データを「LazyFrameオブジェクト」で読み込むための関数です。これは、ファイル全体を一気に読み込むのではなく、必要な部分だけを後で計算するというものです。大規模なデータセットを扱いたい時に効率的です。

では実際にデータフレーム操作を行い、2つのモードの違いを確認しましょう。
使用するデータセットは、Kaggleのタイタニックコンペのtrainデータセットを使用します。

操作

①欠損値が存在する列の取得(各列毎の欠損値の割合の導出)
②数値列に存在する欠損値の補完(※今回は中央値を使用)
の2つの操作をLazyモードとEagerモードでそれぞれ実行し、データの読み込みを含めた実行時間の確認も合わせて行っていきます。
まずはライブラリをインポートしてから実行してみましょう!

qiita.rb
import polars as pl

①-1:欠損値が存在する列の取得(各列毎の欠損値の割合の導出)/Lazyモード

qiita.rb
%%time

lazy_train = pl.scan_csv("//content//train.csv")

column_names = lazy_train.collect_schema().names()

# データ処理の定義

query = (

lazy_train

# 各列の欠損値の数を取得

.with_columns([pl.col(column).null_count().alias(column + '_na_count') for column in column_names])

# 各列の欠損値の割合を計算

.with_columns([

(pl.col(column + '_na_count') / pl.len()).alias(column + '_na_ratio')

for column in column_names

])

# 欠損値の割合が0でない列をフィルタリング

.select([

pl.col(column + '_na_ratio')

for column in column_names

if lazy_train.select((pl.col(column).null_count() / pl.len()).alias(column + '_na_ratio')).collect()[0, 0] > 0

])

# 重複行を削除

.unique()

)

# 実行して結果を取得

lazy_result = query.collect()

# 結果の表示

print("LAZYモード:欠損値の割合が0以上の列")

lazy_result

①-1実行結果

image.png

①-2:欠損値が存在する列の取得(各列毎の欠損値の割合の導出)/Eagerモード

qiita.rb
%%time

eager_train = pl.read_csv("//content//train.csv")

# データフレームの行数を取得

total_rows = eager_train.shape[0]

# 各列の欠損値の数を取得

na_counts = eager_train.null_count()

# 各列の欠損値の割合を計算

na_ratios = na_counts.select([(pl.col(column) / total_rows).alias(column) for column in na_counts.columns])

# 欠損値の割合が0でない列をフィルタリング

eager_result = na_ratios.select([column for column in na_ratios.columns if na_ratios[column][0] > 0])

print("EAGERモード:欠損値の割合が0以上の列")

eager_result

①-2実行結果

image.png

②-1:数値列に存在する欠損値の補完(※今回は中央値を使用)/Lazyモード

qiita.rb
%%time

lazy_train = pl.scan_csv("//content//train.csv")

# 欠損値補完の対象列を指定

columns_to_fill = ['Age', 'Fare']

# 各列の中央値を計算し、欠損値を中央値で補完、そして各列の欠損値の数を取得する処理をまとめる

filled_and_na_counts = (

lazy_train

.with_columns([

pl.col(col).fill_null(pl.col(col).median()).alias(col)

for col in columns_to_fill

])

)

# 計算を実行

na_counts_filled = filled_and_na_counts.collect()

print("LAZYモード:['Age', 'Fare']の欠損値補完後のdf")

na_counts_filled.describe()

②-1実行結果(describeの上部のみ切り取り)

image.png

②-2:数値列に存在する欠損値の補完(※今回は中央値を使用)/Eagerモード

qiita.rb
%%time

eager_train = pl.read_csv("//content//train.csv")

# 欠損値補完の対象列を指定

columns_to_fill = ['Age','Fare']

# 各列の中央値を計算

median = eager_train.select([pl.col(col).median().alias(col) for col in columns_to_fill])

# 欠損値を中央値で補完するためのマッピングを作成

fill_values = {col: median[col][0] for col in columns_to_fill}

# 欠損値を中央値で補完

df_filled = eager_train.with_columns([pl.col(col).fill_null(fill_values[col]).alias(col) for col in columns_to_fill])

print("EAGERモード:['Age', 'Fare']の欠損値補完後のdf")

df_filled.describe()

②-2実行結果(describeの上部のみ切り取り)

image.png

●実行時間の比較

処理 実行モード total実行時間(ms)
①-1 Lazy 13.4
①-2 Eager 43.2
①-1 Lazy 12.8
①-2 Eager 46.4

Lazyモードのほうが、通常の処理よりも約3倍早くなっていますね!

記法について

2つのモードの記述の違いはシンプルで、Lazyモードでは、データ処理を1つにまとめて記述しその処理を"collect()"で呼び出します。

また今回はcsvの読込みも含めた実行時間の確認を行いましたが、read_csv()で読み込んだデータフレームは".lazy()"関数でLazyFraemに変換することも可能です。
DataFrameオブジェクトに対しcollect()を使用すると下記のようにエラーになってしまいますので、ご注意ください。

image.png

以上、Polarsのモードについての検証でした。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?