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

この記事は [Polars Advent Calendar 2023](https://qiita.com/advent-calendar/2023/polars) 1日目の記事です。

# はじめに
こんにちは。
この記事ではタイタニックのデータセットを使って、Polars で予測モデルを作ろうと思います。
ただ、普通に作るのではなく「import polars as **pd** 」とインポートし、どこまで pandas のように Polarsが書けるか試していきます！！！！
    
# Polarsって何？
Polars は Python で使える高速なデータフレームライブラリです。pandas に似ていますが、特に大量のデータを扱う際の処理速度が pandas と比べて高速なのが特徴です。

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

## import
まずはimportです。
```python
import polars as pd
```
Polars 君には pandas になりきってもらいました。
ここからは、 pandas を扱っているつもりでコードを書いていきます。

## read_csv()

まずは、タイタニックのデータセットを読み込みます。read_csv メソッドを使います。
```python
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()` メソッドを使ってデータの先頭を確認してみましょう。
```python
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()` メソッドを使ってデータの統計量を確認してみます。
```python
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()` で性別毎の件数を確認してみます。

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

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

## isnull()
続いて `isnull` メソッドを使て欠損値の件数を数えてみましょう。
```python
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` というメソッドがあります。
よくよく考えたら `is` と `null` は別の単語なのでアンダースコアで繋ぐのが自然ですよね。これまで疑問に思ってきませんでしたが、改めて考えてみると pandas の `isnull` のほうが不自然ですよね。

では書き換えてみましょう。
```python
train_df['Age'].is_null().sum()
```
```
177
```
欠損値の件数が数えられましたね！

## fillna()
次は欠損値補完をしてみます！
```python
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` メソッドが用意されています。書き換えてみましょう。
```python
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 が入っています）

しかし、この結果をもとのデータフレームに新たな列に追加しようとすると、以下のようなエラーが出て怒られてしまいます。
```python
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` を使ってこう書き換えてあげます。

```python
train_df = train_df.with_columns(train_df['Age'].fill_null(28))
```
`with_columns` は新たに作成した列を元のデータフレームに加えて返してくれるメソッドです。


## 列同士の計算
ここからは特徴量エンジニアリングをしていきます。
「年齢 * チケット料金」を求めてみます。
この特徴量に何の意味があるかわからないですが、Polars が pandas のように使えるか試すためにやってみます！
```python
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` を使ってあげましょう。

```python
train_df = train_df.with_columns((train_df['Age'] * train_df['Fare']).alias('Age*Fare'))
```
## groupby()
次は `groupby` を使って集計をしてみましょう。
チケットクラス（`Pclass`）と性別（`Sex`）ごとの平均年齢を求めてみましょう。

```python
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()` になるようです。

では、こう書き換えましょう。
```python
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` の平均値と、集計のキーである、`PClass`、`Sex` を持つデータフレームですので `train_df` に結合しましょう。

```python
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 と違うポイントですね。
```python
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()` を使ってデータフレームの内容をファイルに保存します。
```python
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

```python
train_df.write_csv('output.csv')
```

# モデル作ってみる
pandas と同じように使えるメソッドはあるものの、少し修正しないといけないものがあったりと、さすがに `import polars as pd` とするだけでは使えない感じでした。
ここまでいろいろ試してみたので、書き換えが必要なものは書き換えるとして予測モデルを作ってみたいと思います！！
```python
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 による学習は処理時間に含めたくないので特徴量エンジニアリングまでの速度を計測していきます。


<details>
  <summary>Polars 速度検証用コード</summary>


```python
%%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']
```
</details>


<details>
  <summary>pandas 速度検証用コード</summary>

```python
%%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']
```
</details>

## 速度計測結果

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

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

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

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

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

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