LoginSignup
61
42

import polars as pd でどこまでいけるか!

Last updated at Posted at 2023-12-01

この記事は Polars Advent Calendar 2023 1日目の記事です。

はじめに

こんにちは。
この記事ではタイタニックのデータセットを使って、Polars で予測モデルを作ろうと思います。
ただ、普通に作るのではなく「import polars as pd 」とインポートし、どこまで pandas のように Polarsが書けるか試していきます!!!!

Polarsって何?

Polars は Python で使える高速なデータフレームライブラリです。pandas に似ていますが、特に大量のデータを扱う際の処理速度が pandas と比べて高速なのが特徴です。

import polars as pd

それではさっそくコードを書いていきたいと思います!
なお、この記事では Polars のバージョン 0.19.15 を使用します。

import

まずはimportです。

import polars as pd

Polars 君には pandas になりきってもらいました。
ここからは、 pandas を扱っているつもりでコードを書いていきます。

read_csv()

まずは、タイタニックのデータセットを読み込みます。read_csv メソッドを使います。

df = pd.read_csv('train.csv')
print(df)
Output
shape: (891, 12)
┌─────────────┬──────────┬────────┬──────────────────┬───┬────────────┬─────────┬───────┬──────────┐
│ PassengerId ┆ Survived ┆ Pclass ┆ Name             ┆ … ┆ Ticket     ┆ Fare    ┆ Cabin ┆ Embarked │
│ ---         ┆ ---      ┆ ---    ┆ ---              ┆   ┆ ---        ┆ ---     ┆ ---   ┆ ---      │
│ i64         ┆ i64      ┆ i64    ┆ str              ┆   ┆ str        ┆ f64     ┆ str   ┆ str      │
╞═════════════╪══════════╪════════╪══════════════════╪═══╪════════════╪═════════╪═══════╪══════════╡
│ 1           ┆ 0        ┆ 3      ┆ Braund, Mr. Owen ┆ … ┆ A/5 21171  ┆ 7.25    ┆ null  ┆ S        │
│             ┆          ┆        ┆ Harris           ┆   ┆            ┆         ┆       ┆          │
│ 2           ┆ 1        ┆ 1      ┆ Cumings, Mrs.    ┆ … ┆ PC 17599   ┆ 71.2833 ┆ C85   ┆ C        │
│             ┆          ┆        ┆ John Bradley     ┆   ┆            ┆         ┆       ┆          │
│             ┆          ┆        ┆ (Flor…           ┆   ┆            ┆         ┆       ┆          │
│ 3           ┆ 1        ┆ 3      ┆ Heikkinen, Miss. ┆ … ┆ STON/O2.   ┆ 7.925   ┆ null  ┆ S        │
│             ┆          ┆        ┆ Laina            ┆   ┆ 3101282    ┆         ┆       ┆          │
│ 4           ┆ 1        ┆ 1      ┆ Futrelle, Mrs.   ┆ … ┆ 113803     ┆ 53.1    ┆ C123  ┆ S        │
│             ┆          ┆        ┆ Jacques Heath    ┆   ┆            ┆         ┆       ┆          │
│             ┆          ┆        ┆ (Li…             ┆   ┆            ┆         ┆       ┆          │
│ …           ┆ …        ┆ …      ┆ …                ┆ … ┆ …          ┆ …       ┆ …     ┆ …        │
│ 888         ┆ 1        ┆ 1      ┆ Graham, Miss.    ┆ … ┆ 112053     ┆ 30.0    ┆ B42   ┆ S        │
│             ┆          ┆        ┆ Margaret Edith   ┆   ┆            ┆         ┆       ┆          │
│ 889         ┆ 0        ┆ 3      ┆ Johnston, Miss.  ┆ … ┆ W./C. 6607 ┆ 23.45   ┆ null  ┆ S        │
│             ┆          ┆        ┆ Catherine Helen  ┆   ┆            ┆         ┆       ┆          │
│             ┆          ┆        ┆ …                ┆   ┆            ┆         ┆       ┆          │
│ 890         ┆ 1        ┆ 1      ┆ Behr, Mr. Karl   ┆ … ┆ 111369     ┆ 30.0    ┆ C148  ┆ C        │
│             ┆          ┆        ┆ Howell           ┆   ┆            ┆         ┆       ┆          │
│ 891         ┆ 0        ┆ 3      ┆ Dooley, Mr.      ┆ … ┆ 370376     ┆ 7.75    ┆ null  ┆ Q        │
│             ┆          ┆        ┆ Patrick          ┆   ┆            ┆         ┆       ┆          │
└─────────────┴──────────┴────────┴──────────────────┴───┴────────────┴─────────┴───────┴──────────┘

問題なくファイルを読み込めましたね!!幸先のいいスタートです!!

head()

続いて head() メソッドを使ってデータの先頭を確認してみましょう。

print(train_df.head())
Output
shape: (5, 12)
┌─────────────┬──────────┬────────┬───────────────────┬───┬───────────┬─────────┬───────┬──────────┐
│ PassengerId ┆ Survived ┆ Pclass ┆ Name              ┆ … ┆ Ticket    ┆ Fare    ┆ Cabin ┆ Embarked │
│ ---         ┆ ---      ┆ ---    ┆ ---               ┆   ┆ ---       ┆ ---     ┆ ---   ┆ ---      │
│ i64         ┆ i64      ┆ i64    ┆ str               ┆   ┆ str       ┆ f64     ┆ str   ┆ str      │
╞═════════════╪══════════╪════════╪═══════════════════╪═══╪═══════════╪═════════╪═══════╪══════════╡
│ 1           ┆ 0        ┆ 3      ┆ Braund, Mr. Owen  ┆ … ┆ A/5 21171 ┆ 7.25    ┆ null  ┆ S        │
│             ┆          ┆        ┆ Harris            ┆   ┆           ┆         ┆       ┆          │
│ 2           ┆ 1        ┆ 1      ┆ Cumings, Mrs.     ┆ … ┆ PC 17599  ┆ 71.2833 ┆ C85   ┆ C        │
│             ┆          ┆        ┆ John Bradley      ┆   ┆           ┆         ┆       ┆          │
│             ┆          ┆        ┆ (Flor…            ┆   ┆           ┆         ┆       ┆          │
│ 3           ┆ 1        ┆ 3      ┆ Heikkinen, Miss.  ┆ … ┆ STON/O2.  ┆ 7.925   ┆ null  ┆ S        │
│             ┆          ┆        ┆ Laina             ┆   ┆ 3101282   ┆         ┆       ┆          │
│ 4           ┆ 1        ┆ 1      ┆ Futrelle, Mrs.    ┆ … ┆ 113803    ┆ 53.1    ┆ C123  ┆ S        │
│             ┆          ┆        ┆ Jacques Heath     ┆   ┆           ┆         ┆       ┆          │
│             ┆          ┆        ┆ (Li…              ┆   ┆           ┆         ┆       ┆          │
│ 5           ┆ 0        ┆ 3      ┆ Allen, Mr.        ┆ … ┆ 373450    ┆ 8.05    ┆ null  ┆ S        │
│             ┆          ┆        ┆ William Henry     ┆   ┆           ┆         ┆       ┆          │
└─────────────┴──────────┴────────┴───────────────────┴───┴───────────┴─────────┴───────┴──────────┘

いいですね!ちゃんと先頭レコードが表示されています!
同様に tail() も呼び出すことができました!

describe()

続いて describe() メソッドを使ってデータの統計量を確認してみます。

train_df.describe()
Output
shape: (9, 13)
┌────────────┬─────────────┬──────────┬──────────┬───┬───────────┬───────────┬───────┬──────────┐
│ describe   ┆ PassengerId ┆ Survived ┆ Pclass   ┆ … ┆ Ticket    ┆ Fare      ┆ Cabin ┆ Embarked │
│ ---        ┆ ---         ┆ ---      ┆ ---      ┆   ┆ ---       ┆ ---       ┆ ---   ┆ ---      │
│ str        ┆ f64         ┆ f64      ┆ f64      ┆   ┆ str       ┆ f64       ┆ str   ┆ str      │
╞════════════╪═════════════╪══════════╪══════════╪═══╪═══════════╪═══════════╪═══════╪══════════╡
│ count      ┆ 891.0       ┆ 891.0    ┆ 891.0    ┆ … ┆ 891       ┆ 891.0     ┆ 891   ┆ 891      │
│ null_count ┆ 0.0         ┆ 0.0      ┆ 0.0      ┆ … ┆ 0         ┆ 0.0       ┆ 687   ┆ 2        │
│ mean       ┆ 446.0       ┆ 0.383838 ┆ 2.308642 ┆ … ┆ null      ┆ 32.204208 ┆ null  ┆ null     │
│ std        ┆ 257.353842  ┆ 0.486592 ┆ 0.836071 ┆ … ┆ null      ┆ 49.693429 ┆ null  ┆ null     │
│ min        ┆ 1.0         ┆ 0.0      ┆ 1.0      ┆ … ┆ 110152    ┆ 0.0       ┆ A10   ┆ C        │
│ 25%        ┆ 223.0       ┆ 0.0      ┆ 2.0      ┆ … ┆ null      ┆ 7.8958    ┆ null  ┆ null     │
│ 50%        ┆ 446.0       ┆ 0.0      ┆ 3.0      ┆ … ┆ null      ┆ 14.4542   ┆ null  ┆ null     │
│ 75%        ┆ 669.0       ┆ 1.0      ┆ 3.0      ┆ … ┆ null      ┆ 31.0      ┆ null  ┆ null     │
│ max        ┆ 891.0       ┆ 1.0      ┆ 3.0      ┆ … ┆ WE/P 5735 ┆ 512.3292  ┆ T     ┆ S        │
└────────────┴─────────────┴──────────┴──────────┴───┴───────────┴───────────┴───────┴──────────┘

describe() も問題なく表示させることができました!
pandas の describe() はデフォルトの引数だと、数値カラムしか統計量が出力されませんが Polars だと数値カラム以外も表示されています。

value_counts()

続いて value_counts() で性別毎の件数を確認してみます。

print(train_df['Sex'].value_counts())
Output
shape: (2, 2)
┌────────┬────────┐
│ Sex    ┆ counts │
│ ---    ┆ ---    │
│ str    ┆ u32    │
╞════════╪════════╡
│ male   ┆ 577    │
│ female ┆ 314    │
└────────┴────────┘

こちらも問題なく表示されました。
今のところ順調ですね!Polars 君は pandas になりきれていますね。

isnull()

続いて isnull メソッドを使て欠損値の件数を数えてみましょう。

train_df['Age'].isnull().sum()
Error
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[44], line 1
----> 1 train_df['Age'].isnull().sum()

AttributeError: 'Series' object has no attribute 'isnull'

おっと、怒られてしまいました。
Polars データフレームには isnull というメソッドはないようですね。
代わりに Polars には is_null というメソッドがあります。
よくよく考えたら isnull は別の単語なのでアンダースコアで繋ぐのが自然ですよね。これまで疑問に思ってきませんでしたが、改めて考えてみると pandas の isnull のほうが不自然ですよね。

では書き換えてみましょう。

train_df['Age'].is_null().sum()
177

欠損値の件数が数えられましたね!

fillna()

次は欠損値補完をしてみます!

train_df['Age'].fillna(28)
Error
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[48], line 1
----> 1 train_df['Age'].fillna(28)

AttributeError: 'Series' object has no attribute 'fillna'

ぎゃあ、またダメでした!

Polars で欠損値補完をする場合は fill_null メソッドが用意されています。書き換えてみましょう。

train_df['Age'].fill_null(28).tail(5)
Output
shape: (5,)
Age
f64
27.0
19.0
28.0
26.0
32.0

欠損値補完できました(下から3行目が欠損していたのですが、28 が入っています)

しかし、この結果をもとのデータフレームに新たな列に追加しようとすると、以下のようなエラーが出て怒られてしまいます。

train_df['Age'] = train_df['Age'].fill_null(28)
Error
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/tmp/ipykernel_88/1646558793.py in ?()
----> 1 train_df['Age'] = train_df['Age'].fill_null(28)

/opt/conda/lib/python3.10/site-packages/polars/dataframe/frame.py in ?(self, key, value)
   1726         value: Any,
   1727     ) -> None:  # pragma: no cover
   1728         # df["foo"] = series
   1729         if isinstance(key, str):
-> 1730             raise TypeError(
   1731                 "DataFrame object does not support `Series` assignment by index"
   1732                 "\n\nUse `DataFrame.with_columns`."
   1733             )

TypeError: DataFrame object does not support `Series` assignment by index

Use `DataFrame.with_columns`.

DataFrame.with_columns を使えと言っていますね。
なので、ここは with_columns を使ってこう書き換えてあげます。

train_df = train_df.with_columns(train_df['Age'].fill_null(28))

with_columns は新たに作成した列を元のデータフレームに加えて返してくれるメソッドです。

列同士の計算

ここからは特徴量エンジニアリングをしていきます。
「年齢 * チケット料金」を求めてみます。
この特徴量に何の意味があるかわからないですが、Polars が pandas のように使えるか試すためにやってみます!

train_df['Age'] * train_df['Fare']
Output
shape: (891,)
Age
f64
159.5
2708.7654
206.05
1858.5
281.75
null
2800.575
42.15
300.5991
420.9912
66.8
1539.9
…

計算できました!
しかし、この列をもとのデータフレームの新たな列に代入しようとすると欠損値補完の時と同じように with_columns を使えと怒られるので、 with_columns を使ってあげましょう。

train_df = train_df.with_columns((train_df['Age'] * train_df['Fare']).alias('Age*Fare'))

groupby()

次は groupby を使って集計をしてみましょう。
チケットクラス(Pclass)と性別(Sex)ごとの平均年齢を求めてみましょう。

train_df.groupby(['Pclass', 'Sex'])['Age'].mean()
Error
/tmp/ipykernel_88/1520120906.py:1: DeprecationWarning: `groupby` is deprecated. It has been renamed to `group_by`.
  train_df.groupby(['Pclass', 'Sex'])['Age'].mean()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[38], line 1
----> 1 train_df.groupby(['Pclass', 'Sex'])['Age'].mean()

TypeError: 'GroupBy' object is not subscriptable

Groupby オブジェクトに対して [] でアクセスすることはできないみたいですね。

さらに DeprecationWarning も出ていますね。groupby() は廃止されて group_by() になるようです。

では、こう書き換えましょう。

average_age_df = train_df.group_by(['Pclass', 'Sex']).agg(
    pd.col('Age').mean().alias('Average_Age')
)
print(average_age_df)
Output
shape: (6, 3)
┌────────┬────────┬─────────────┐
│ Pclass ┆ Sex    ┆ Average_Age │
│ ---    ┆ ---    ┆ ---         │
│ i64    ┆ str    ┆ f64         │
╞════════╪════════╪═════════════╡
│ 3      ┆ female ┆ 21.75       │
│ 3      ┆ male   ┆ 26.507589   │
│ 2      ┆ female ┆ 28.722973   │
│ 2      ┆ male   ┆ 30.740707   │
│ 1      ┆ female ┆ 34.611765   │
│ 1      ┆ male   ┆ 41.281386   │
└────────┴────────┴─────────────┘

Groupby オブジェクトの agg() メソッドを使っています。
agg() メソッドには pd.col('Age').mean().alias('Average_Age') を渡しており、これは Age カラムの平均値を求め、カラム名を Average_Age とする処理を与えています。

merge()

集計した average_age_df は集計のキーである、Age の平均値と、集計のキーである、PClassSex を持つデータフレームですので train_df に結合しましょう。

train_df.merge(average_df, how='left', on=['Pclass', 'Sex'])
Error
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[5], line 1
----> 1 train_df.merge(average_df, how='left', on=['Pclass', 'Sex'])

AttributeError: 'DataFrame' object has no attribute 'merge'

おっと、merge() メソッドもないようですね。
Polars でテーブルを結合するときには join() メソッドを使います。pandas では結合するメソッドは merge() なのでここも pandas と違うポイントですね。

train_df = train_df.join(average_age_df, on=['Pclass', 'Sex'])
print(train_df)
Output
shape: (891, 15)
┌────────────┬──────────┬────────┬────────────┬───┬──────────┬────────────┬───────────┬────────────┐
│ PassengerI ┆ Survived ┆ Pclass ┆ Name       ┆ … ┆ Embarked ┆ Average_Ag ┆ Age*Fare  ┆ Average_Ag │
│ d          ┆ ---      ┆ ---    ┆ ---        ┆   ┆ ---      ┆ e          ┆ ---       ┆ e_right    │
│ ---        ┆ i64      ┆ i64    ┆ str        ┆   ┆ str      ┆ ---        ┆ f64       ┆ ---        │
│ i64        ┆          ┆        ┆            ┆   ┆          ┆ f64        ┆           ┆ f64        │
╞════════════╪══════════╪════════╪════════════╪═══╪══════════╪════════════╪═══════════╪════════════╡
│ 1          ┆ 0        ┆ 3      ┆ Braund,    ┆ … ┆ S        ┆ 26.911873  ┆ 159.5     ┆ 26.911873  │
│            ┆          ┆        ┆ Mr. Owen   ┆   ┆          ┆            ┆           ┆            │
│            ┆          ┆        ┆ Harris     ┆   ┆          ┆            ┆           ┆            │
│ 2          ┆ 1        ┆ 1      ┆ Cumings,   ┆ … ┆ C        ┆ 33.978723  ┆ 2708.7654 ┆ 33.978723  │
│            ┆          ┆        ┆ Mrs. John  ┆   ┆          ┆            ┆           ┆            │
│            ┆          ┆        ┆ Bradley    ┆   ┆          ┆            ┆           ┆            │
│            ┆          ┆        ┆ (Flor…     ┆   ┆          ┆            ┆           ┆            │
│ 3          ┆ 1        ┆ 3      ┆ Heikkinen, ┆ … ┆ S        ┆ 23.572917  ┆ 206.05    ┆ 23.572917  │
│            ┆          ┆        ┆ Miss.      ┆   ┆          ┆            ┆           ┆            │
│            ┆          ┆        ┆ Laina      ┆   ┆          ┆            ┆           ┆            │
│ 4          ┆ 1        ┆ 1      ┆ Futrelle,  ┆ … ┆ S        ┆ 33.978723  ┆ 1858.5    ┆ 33.978723  │
│            ┆          ┆        ┆ Mrs.       ┆   ┆          ┆            ┆           ┆            │
│            ┆          ┆        ┆ Jacques    ┆   ┆          ┆            ┆           ┆            │
│            ┆          ┆        ┆ Heath (Li… ┆   ┆          ┆            ┆           ┆            │
│ …          ┆ …        ┆ …      ┆ …          ┆ … ┆ …        ┆ …          ┆ …         ┆ …          │
│ 888        ┆ 1        ┆ 1      ┆ Graham,    ┆ … ┆ S        ┆ 33.978723  ┆ 570.0     ┆ 33.978723  │
│            ┆          ┆        ┆ Miss.      ┆   ┆          ┆            ┆           ┆            │
│            ┆          ┆        ┆ Margaret   ┆   ┆          ┆            ┆           ┆            │
│            ┆          ┆        ┆ Edith      ┆   ┆          ┆            ┆           ┆            │
│ 889        ┆ 0        ┆ 3      ┆ Johnston,  ┆ … ┆ S        ┆ 23.572917  ┆ 656.6     ┆ 23.572917  │
│            ┆          ┆        ┆ Miss.      ┆   ┆          ┆            ┆           ┆            │
│            ┆          ┆        ┆ Catherine  ┆   ┆          ┆            ┆           ┆            │
│            ┆          ┆        ┆ Helen …    ┆   ┆          ┆            ┆           ┆            │
│ 890        ┆ 1        ┆ 1      ┆ Behr, Mr.  ┆ … ┆ C        ┆ 38.995246  ┆ 780.0     ┆ 38.995246  │
│            ┆          ┆        ┆ Karl       ┆   ┆          ┆            ┆           ┆            │
│            ┆          ┆        ┆ Howell     ┆   ┆          ┆            ┆           ┆            │
│ 891        ┆ 0        ┆ 3      ┆ Dooley,    ┆ … ┆ Q        ┆ 26.911873  ┆ 248.0     ┆ 26.911873  │
│            ┆          ┆        ┆ Mr.        ┆   ┆          ┆            ┆           ┆            │
│            ┆          ┆        ┆ Patrick    ┆   ┆          ┆            ┆           ┆            │
└────────────┴──────────┴────────┴────────────┴───┴──────────┴────────────┴───────────┴────────────┘

to_csv()

最後に to_csv() を使ってデータフレームの内容をファイルに保存します。

train_df.to_csv('output.csv')
Output
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[86], line 1
----> 1 train_df.to_csv('train_df.csv')

AttributeError: 'DataFrame' object has no attribute 'to_csv'

to_csv() も使えないみたいですね。
Polars ではファイル出力には write_csv() を使います。

Polars 公式 Twitter でも語られていますが、そもそも read に対応するのは write ですよね。
https://twitter.com/datapolars/status/1627957870334451713?s=46&t=XysrDrTArT1XiPU95kfsvg

train_df.write_csv('output.csv')

モデル作ってみる

pandas と同じように使えるメソッドはあるものの、少し修正しないといけないものがあったりと、さすがに import polars as pd とするだけでは使えない感じでした。
ここまでいろいろ試してみたので、書き換えが必要なものは書き換えるとして予測モデルを作ってみたいと思います!!

import polars as pd
import lightgbm as lgb
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_auc_score
from sklearn.preprocessing import LabelEncoder

# データの読み込み
train_df = pd.read_csv('train.csv')

# データの先頭行を表示してデータを確認
print(train_df.head())

# データの基本統計量を確認
print(train_df.describe())

# 性別の分布を確認
print(train_df['Sex'].value_counts())

# 欠損値の処理
train_df = train_df.with_columns([
    train_df['Age'].fill_null(28),
    train_df['Embarked'].fill_null('S')
])

# カテゴリカル変数のLabel Encoding
label_encoder_sex = LabelEncoder()
label_encoder_embarked = LabelEncoder()
train_df = train_df.with_columns([
    pd.Series('Sex', label_encoder_sex.fit_transform(train_df['Sex'].to_numpy())).alias('Sex_encoded'),
    pd.Series('Embarked', label_encoder_embarked.fit_transform(train_df['Embarked'].to_numpy())).alias('Embarked_encoded')
])


# 欠損値の処理
train_df = train_df.with_columns([
    train_df['Age'].fill_null(28),
    train_df['Embarked'].fill_null('S')
])

# 新しい特徴量の追加
train_df = train_df.with_columns((train_df['Age'] * train_df['Fare']).alias('Age_Fare'))

# 平均年齢の特徴量を追加
average_age_df = train_df.group_by(['Pclass', 'Sex']).agg(
    pd.col('Age').mean().alias('Average_Age')
)
train_df = train_df.join(average_age_df, on=['Pclass', 'Sex'])

# 不要な列の削除
columns_to_drop = ['Name', 'Ticket', 'Cabin', 'Sex', 'Embarked']
train_df = train_df.drop(columns_to_drop)

# 特徴量とラベルの分離
X = train_df.drop(['Survived'])
y = train_df['Survived']

# データをトレーニングセットとテストセットに分割
# LightGBMで学習するためにpandasに変換
X_train, X_test, y_train, y_test = train_test_split(X.to_pandas(), y.to_numpy(), test_size=0.2, random_state=42)

# LightGBMのデータセットの作成
lgb_train = lgb.Dataset(X_train, y_train, categorical_feature=['Sex_encoded', 'Embarked_encoded'])
lgb_eval = lgb.Dataset(X_test, y_test, reference=lgb_train, categorical_feature=['Sex_encoded', 'Embarked_encoded'])

# パラメータの設定
params = {
    'objective': 'binary',
    'metric': 'binary_logloss',
    'verbosity': -1
}

# モデルのトレーニング
gbm = lgb.train(
    params,
    lgb_train,
    num_boost_round=200,
    valid_sets=lgb_eval,
    callbacks=[
        lgb.early_stopping(10),
        lgb.log_evaluation(100)
    ]
)

# モデルの評価
y_pred = gbm.predict(X_test, num_iteration=gbm.best_iteration)

auc = roc_auc_score(y_test, y_pred)
print(f"ROC AUC: {auc:.2f}")

このコードで無事学習することができました!!
ちなみに精度はAUC0.9でした。

速度比較

Polars を pandas と極力同じ書き方をして、必要最低限の書き換えだけを行いました。
Polars といえばやはり高速な処理が売りですので、必要最小限の書き換えのみで pandas と比べてどの程度高速化されたのか確認していきます。

データ量がそれなりにないと比較ができないので、データはタイタニックのデータを 10,000 倍したデータを使っていきます。
また、LightGBM による学習は処理時間に含めたくないので特徴量エンジニアリングまでの速度を計測していきます。

Polars 速度検証用コード
%%time

import polars as pd
import lightgbm as lgb
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_auc_score
from sklearn.preprocessing import LabelEncoder

# データの読み込み
train_df = pd.read_csv('expanded_train.csv')

# データの先頭行を表示してデータを確認
print(train_df.head())

# データの基本統計量を確認
print(train_df.describe())

# 性別の分布を確認
print(train_df['Sex'].value_counts())

# 欠損値の処理
train_df = train_df.with_columns([
    train_df['Age'].fill_null(28),
    train_df['Embarked'].fill_null('S')
])

# カテゴリカル変数のLabel Encoding
label_encoder_sex = LabelEncoder()
label_encoder_embarked = LabelEncoder()
train_df = train_df.with_columns([
    pd.Series('Sex', label_encoder_sex.fit_transform(train_df['Sex'].to_numpy())).alias('Sex_encoded'),
    pd.Series('Embarked', label_encoder_embarked.fit_transform(train_df['Embarked'].to_numpy())).alias('Embarked_encoded')
])


# 欠損値の処理
train_df = train_df.with_columns([
    train_df['Age'].fill_null(28),
    train_df['Embarked'].fill_null('S')
])

# 新しい特徴量の追加
train_df = train_df.with_columns((train_df['Age'] * train_df['Fare']).alias('Age_Fare'))

# 平均年齢の特徴量を追加
average_age_df = train_df.group_by(['Pclass', 'Sex']).agg(
    pd.col('Age').mean().alias('Average_Age')
)
train_df = train_df.join(average_age_df, on=['Pclass', 'Sex'])

# 不要な列の削除
columns_to_drop = ['Name', 'Ticket', 'Cabin', 'Sex', 'Embarked']
train_df = train_df.drop(columns_to_drop)

# 特徴量とラベルの分離
X = train_df.drop(['Survived'])
y = train_df['Survived']
pandas 速度検証用コード
%%time

import polars as pd
import lightgbm as lgb
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_auc_score
from sklearn.preprocessing import LabelEncoder

# データの読み込み
train_df = pd.read_csv('expanded_train.csv')

# データの先頭行を表示してデータを確認
print(train_df.head())

# データの基本統計量を確認
print(train_df.describe())

# 性別の分布を確認
print(train_df['Sex'].value_counts())

# 欠損値の処理
train_df = train_df.with_columns([
    train_df['Age'].fill_null(28),
    train_df['Embarked'].fill_null('S')
])

# カテゴリカル変数のLabel Encoding
label_encoder_sex = LabelEncoder()
label_encoder_embarked = LabelEncoder()
train_df = train_df.with_columns([
    pd.Series('Sex', label_encoder_sex.fit_transform(train_df['Sex'].to_numpy())).alias('Sex_encoded'),
    pd.Series('Embarked', label_encoder_embarked.fit_transform(train_df['Embarked'].to_numpy())).alias('Embarked_encoded')
])


# 欠損値の処理
train_df = train_df.with_columns([
    train_df['Age'].fill_null(28),
    train_df['Embarked'].fill_null('S')
])

# 新しい特徴量の追加
train_df = train_df.with_columns((train_df['Age'] * train_df['Fare']).alias('Age_Fare'))

# 平均年齢の特徴量を追加
average_age_df = train_df.group_by(['Pclass', 'Sex']).agg(
    pd.col('Age').mean().alias('Average_Age')
)
train_df = train_df.join(average_age_df, on=['Pclass', 'Sex'])

# 不要な列の削除
columns_to_drop = ['Name', 'Ticket', 'Cabin', 'Sex', 'Embarked']
train_df = train_df.drop(columns_to_drop)

# 特徴量とラベルの分離
X = train_df.drop(['Survived'])
y = train_df['Survived']

速度計測結果

ライブラリ 処理時間
Polars 2.35 秒
pandas 6.6 秒

なんと、最低限の書き換えだけで2倍以上早くなりました!!!
ステキ!

まとめ

実はこの記事は「Polars って意外と pandas と同じように書けるよ。簡単でしょ。」と言いたくて書き始めたのですが、結果としては、import polars as pd だけで簡単に移行できるほど甘くはなかったです。

しかしながら、pandas と Polars では共通しているメソッドも多く存在することもわかりました!

今回のケースでは一部を書き換えただけで処理速度を 2 倍以上にすることができました。Polars をこれから始めたいけどハードルが高いと感じている人は、pandas と同じような使い方をしてうまくいかないところだけ書き換えてみるところからスタートするのもアリかもしれません。

今回はなるべく pandas っぽく Polars を使いましたが、Polars にはエクスプレッションを使用したクエリ最適化や、LazyFrame を使用した遅延評価などまだまだ高速化できる機能がありますので、是非調べて使ってみてください!!

61
42
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
61
42