この記事は 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)
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())
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()
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())
shape: (2, 2)
┌────────┬────────┐
│ Sex ┆ counts │
│ --- ┆ --- │
│ str ┆ u32 │
╞════════╪════════╡
│ male ┆ 577 │
│ female ┆ 314 │
└────────┴────────┘
こちらも問題なく表示されました。
今のところ順調ですね!Polars 君は pandas になりきれていますね。
isnull()
続いて isnull
メソッドを使て欠損値の件数を数えてみましょう。
train_df['Age'].isnull().sum()
---------------------------------------------------------------------------
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
というメソッドがあります。
よくよく考えたら is
と null
は別の単語なのでアンダースコアで繋ぐのが自然ですよね。これまで疑問に思ってきませんでしたが、改めて考えてみると pandas の isnull
のほうが不自然ですよね。
では書き換えてみましょう。
train_df['Age'].is_null().sum()
177
欠損値の件数が数えられましたね!
fillna()
次は欠損値補完をしてみます!
train_df['Age'].fillna(28)
---------------------------------------------------------------------------
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)
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)
---------------------------------------------------------------------------
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']
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()
/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)
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
の平均値と、集計のキーである、PClass
、Sex
を持つデータフレームですので train_df
に結合しましょう。
train_df.merge(average_df, how='left', on=['Pclass', 'Sex'])
---------------------------------------------------------------------------
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)
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')
---------------------------------------------------------------------------
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 を使用した遅延評価などまだまだ高速化できる機能がありますので、是非調べて使ってみてください!!