polarsでの機械学習モデル構築（LightGBM）

# はじめに
初投稿になります。最近使っているPolarsについての備忘録で、自己学習のアウトプットとしてまとめています。読みやすさ、内容については度外視しているので、ご了承ください。
# やったこと
Pandasで行っていた機械学習モデル構築を、Polarsに切り替えて実装を行いました。
基本的な流れは以下の通りです。
1. データ読み込み
2. データ分布確認
3. データ前処理（ワンホット化）
4. モデル構築および予測
# Polarsとは
## Pandasとの比較
Pandasと同様、データ処理に特化したライブラリで、記述方法やAPIも似ているため、Pandasと同じような感覚で使うことができます。Pandasと比較して、ほぼすべての処理が、高速かつ省メモリで実行できるため、作業の効率化、マシンスペックを抑えた大規模データ処理が可能です。詳しい性能比較は、[こちら](https://note.com/fireducks/n/n604974763502)を参照していただければと思いますが、Pandasとの性能差は圧倒的です。

私自身も使用していて、Pandasに比べて5倍～10倍ほどの処理速度であると感じます。特に、データ読み込みの速度は顕著に差が表れ、数ギガ単位のデータでも数秒程度で読み込めてしまいますし、メモリも食わないので、大規模データを扱う際も、快適に作業できます。

余談ですが、Google colabでは、デフォルトでPolarsがインストールされているので、天下のGoogleさんも認知していますね。
## 記述方法やAPIについて
記述については、前述したとおり、基本的にPandasと似ていますが、独特の記述もそこそこあるため、慣れるまでにやや時間を要するかと思います。自分の場合は、普段の業務でコードを書くことが多く、使い始めてから1～2週間ほどで、使用感に慣れました。自分が参考にした記事や公式ドキュメントで、すごく丁寧にまとめてくれているので、具体的な処理については、以下の記事を参考にしてください。

<参考記事>
- [pandasから移行する人向け polars使用ガイド](https://qiita.com/nkay/items/9cfb2776156dc7e054c8)
- [テーブルデータ処理に悩むあなたに朗報！Polarsの使い方を徹底解説　その１：基本編](https://dev.classmethod.jp/articles/polars-tutorial-001-standard/)
- [Polars公式ドキュメント](https://docs.pola.rs/api/python/stable/reference/dataframe/api/polars.DataFrame.to_dummies.html)

自分も分からないときは、まとめ記事⇒公式ドキュメント⇒ChatGPTという順で調べて、今のところ100％解決してるので、適宜使い分けてもらえればと思います。
# Polarsを用いたデータ処理及びモデル構築
前置きが長くなりましたが、本題になります。コード実行はJupyter notebook上で行いました。python, polarsのバージョンは以下の通りです。
- python：3.10.14
- polars：1.2.1

## 0. 事前準備、環境確認
Polarsを使用する際に注意したいのが、バージョンによって、引数の名称やAPI名が異なる場合がある点です。自分は業務で古いバージョンのPolars（0.18）を使用していたのですが、バージョン違いによる記述エラーに気づけず、混乱しました。。。

ネット記事を調べると、ほぼすべて1.0以上のバージョンで記述されており、コピペしてもエラーで動作しないことも幾度かありましたので、古いバージョンを利用する方は、公式ドキュメントを確認するのが一番正確です（公式ドキュメントもデフォルトで1.0のページが表示されるので、ページ内で古いドキュメントに切り替えてください）

polarsのバージョンは、pythonのバージョンに依存するっぽい（python 3.7.12で、polars 0.18.2でした）ので、polarsをインストールしたら、バージョン確認をおすすめします。
```python:polarsインストール
!pip install polars
```
```python:python, polarsのバージョン確認
import sys
import polars as pl

#polarsバージョン確認
print("polars", pl.__version__)

# Pythonバージョン確認 
sys.version
```

## 1. データ読み込み
Pandasと同様、csv,tsv等のデータを読み込めます。
今回使用するデータは以下になります。
https://www.kaggle.com/datasets/shubhambathwal/flight-price-prediction
3つのcsvファイルのうち、Clean_Dataset.csvを使用してます。
```python:データ読み込み
df = pl.read_csv("Clean_Dataset.csv")
```
データフレームは以下のように表示されます。列の型も一緒に確認できるため、df.info()より見やすいですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3795996/4ba1d04b-61c7-d05f-75ab-97ed79e5da33.png)

上記の通り、polarsでは各カラムごとに、データ型が固定されます。そのため、1つのカラムに文字列と数値が混在するようなデータを読み込むとエラーが発生します。人によっては、面倒に思うかもしれませんが、事前に型混在に気付けるので、個人的にはありがたい仕様です。

読み込み時にエラーが出る際は、引数オプションで型指定をしたり、エラーが出る箇所を無視するなどの処理が必要です。自分がよく使用する引数は以下になります。
|引数|型|説明|
|:-:|:-:|:-:|
|infer_schema_length|int|読み込み時のデータ型推定に使用する行数。デフォルト値は100で、先頭から100行のデータを先に読み込んで型推定を行う。|
|schema_overrides（旧：dtypes）|dict|列ごとに読み込むデータ型を指定可能。指定してない列に関しては、infer_schema_lengthをもとに型推定を行う。polars 0.18, 0.19では、引数名が"dtypes"なので注意。|
|columns|list|使用する列を指定。リストはインデックスもしくは列名で指定|
|ignore_errors|bool|Trueにすると、エラーが出る箇所をnullに置き換える。例えば、intを指定して読み込んだ列に、文字列が混じっていた場合、文字列がnullに置換されます。置換前の値を知りたい場合は、schema_overridesにて、該当列をstrで読み込み、個別に処理する必要があります。

他にも便利な引数があるので、詳しくは公式ドキュメントや前述の参考記事を参照してください。

```python:データ読み込み（引数例）
df = pl.read_csv("data.csv",
                 infer_schema_length = 100, #カラムの型推定に使用する行数
                 schema_overrides = {"column_1": pl.Int64, #Int型で読み込み
                                     "column_2": pl.Float64, #Float型で読み込み
                                     "column_3": pl.String, #Str型で読み込み
                                    },
                 columns = ["column_1","column_2","column_3","column_4"],
                 # columns = [0,1,2,3], #インデックス指定も可能 
                 ignore_errors = True
                 )
```

## 2. データ分布確認
### 2.1 基本統計量の確認
基本統計量は、pandasと同様の記述で確認できます。欠損値（null_count）も確認できるので便利です。

```python:基本統計量の確認
df.describe()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3795996/6b0dfc33-b175-4f21-e8ac-4cc46f35467f.png)
### 2.2 データの可視化
polarsでは、df.hist()のような可視化メソッドはないですが、以下のようにpandasに変換することでpandasの機能も利用できます。polarsの処理で困ったら、とりあえずpandasに変換すれば何とかなります。
```python:ヒストグラム出力
df.to_pandas().hist()
```
一方、matplotlib, seabornでは、polarsのデータ型のままで動くので、pandasへの変換は不要で、pandasと同じコードで可視化できます。
```python:matplotlibでの可視化
import matplotlib.pyplot as plt

# matplotlibでのでのヒストグラム出力
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
ax.hist(df["price"], bins=50)
plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3795996/55566646-0f3f-bf5b-579c-7eafbe532806.png)


```python:seabornでの可視化
import seaborn as sns

# seabornでのヒストグラム出力
sns.histplot(df['price'], bins = 50)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3795996/1a1db5ea-b469-d8f5-0e8b-c32a274623f9.png)

### 2.3 半自動可視化ライブラリへの対応（sweetviz）
こちら余談ですが、自分の場合は、sweetvizのような半自動化ライブラリで可視化することが多いので、試してみました。結論、sweetvizではpolarsのデータ型のままだとエラーになるので、pandasに変換してから渡す必要があります。


```python:sweetvizでの可視化
import sweetviz as sv

# Buisness,Economyのデータを比較
report = sv.compare([df.filter(pl.col("class") == "Business").to_pandas(),'Business'],
                    [df.filter(pl.col("class") == "Economy").to_pandas(),'Economy'],
                    target_feat='price') 
report.show_html("Business_Economy_compare.html")
```
ちなみに、sweetvizを使用すると以下のようなレポートをhtmlファイルで出力してくれます。
分析初期段階で網羅的に特徴を把握したいときに便利なので、気になる方はぜひ使用してみてください！
参考：[個人的に最もおすすめなEDAツール Sweetvizの使い方](https://qiita.com/d_sato_/items/0485af3b0a9271a71c88)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3795996/1e55dd98-68b0-be87-b520-23e0f994b2ef.png)

## 3. データ前処理
今回は、機械学習モデル構築の確認が目的ですので、最低限の処理のみ実施してます。
データ抽出、集計、文字列処理などの、データ加工に関する処理は、以下の記事ですごく丁寧にまとめられているので、こちらを確認してください。
- [pandasから移行する人向け polars使用ガイド](https://qiita.com/nkay/items/9cfb2776156dc7e054c8)
- [テーブルデータ処理に悩むあなたに朗報！Polarsの使い方を徹底解説　その１：基本編](https://dev.classmethod.jp/articles/polars-tutorial-001-standard/)

今回使用するLightGBMは、欠損値があっても大丈夫な機械学習モデルなので、ワンホット化のみ実施します。
```python:文字列のみワンホット化
import polars.selectors as cs # int,strなどのデータ型で、列選択が可能

# チケット情報を削除
df = df.drop("flight")

# ワンホット化
df_dummy = df.to_dummies(cs.string())
df_dummy
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3795996/2d5b1b57-8b8e-d4e8-56d5-931b173713d6.png)

polarsのデータフレームでは、データ型の指定されているため、データ抽出や、列選択時に、データ型で指定できます。pandasで同じことをやろうとすると、少し手間ですし、データ型が混同しているとエラーを吐かれたりもするので、データ型の指定により、処理が安定するのは、個人的に気に入っている点です。

特徴量が増えてしまうので、flight（チケット情報）を削除してますが、番号部分は特徴量として有効に働き、精度が上がるので、興味ある方は試してみてください。

## 4. モデル構築および予測
### 4.1 データ分割
scikit-learnのtrain_test_splitは普通に使えます。
```python:データ分割
import lightgbm as lgb
from sklearn.model_selection import train_test_split

# train, validに分割
train, valid = train_test_split(df_dummy, test_size=0.2, random_state=1234)
print("train_size:{}, valid_size:{},".format(len(train), len(valid)))
display(train,valid)
```

### 4.2 LightGBMでの学習モデル作成
LightGBMでの学習で、データを渡す際にエラーが出てしまうので、pandasに変換してからLightGBMに渡しています。pandasに変換してからは、今まで通りモデルを定義して学習するだけです。polarsからpandasへの変換も、ほとんど時間はかかりません。

```python:モデル作成、学習
# polars -> pandasに変換
train_pd = train.to_pandas()
valid_pd = valid.to_pandas() 

#LightGBM用にデータセットを作成
lgb_train = lgb.Dataset(train_pd.drop("price",axis=1), train_pd["price"])
lgb_valid = lgb.Dataset(valid_pd.drop("price",axis=1), valid_pd["price"])

#ハイパーパラメタの設定
hyper_params = {     
    'boosting_type':'gbdt',#ブースティングの種類。ここでは勾配ブースティング
    'objective':'regression',#機械学習タスクの種類。ここでは回帰分類     
    'metric':'rmse',#モデルの評価指標 
    'num_leaves':100,#葉(ノード)の数。大きいほど複雑になる
    'max_depth':-1, #木の深さ、デフォルト-1で、負の数は制限なしを意味する
    'learning_rate':0.1,#学習率 
    'random_state':1234, #乱数のシード値
    }

# この数字を1にすると学習スコア推移が表示される、表示しない場合は0にする
verbose_eval = 1  

#モデル作成、学習回数、学習打ち切り回数の設定
model = lgb.train(params = hyper_params,
                train_set = lgb_train,
                valid_sets = [lgb_train, lgb_valid],
                valid_names=['train','val'], # 学習経過で表示する名称

                #学習数と学習打ち切り回数のみ自分で自由に決める。
                num_boost_round=200,  #学習数
                callbacks=[lgb.early_stopping(stopping_rounds=10, #学習打ち切り回数 
                                              verbose=True), 
                          lgb.log_evaluation(verbose_eval)]
                )
```

せっかくなので、Feature importanceも確認してみましたが、class_Businessが非常に高い値になってました。お試しでのモデル作成なので、特に深く言及しません。
```Python:Feature importanceの確認
# 特徴量の重要度を（Feature importance）を確認する
lgb.plot_importance(model, figsize=(8,4), max_num_features=10, importance_type='gain')
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3795996/bdcd3f27-96af-50a7-3cc8-e90f6e0a4980.png)

以上でpolarsを使用した機械学習モデル構築のパイプラインが組めたので、無事に目標達成できました！！

# まとめ
Pandasからpolarsへの切り替えということで、可視化、データ処理、機械学習モデル作成までを実践してみました。とにかく処理が早くて快適です（特にデータ読み込み）。今回使用したデータ以外にも、5,000,000×17ほどのデータで、集計・加工を試していますが、pandasだと数十秒かかる処理も、「えっ、もう終わったの？」というくらい一瞬で終わるので、びっくりしてます。

はじめは、polarsの記述に戸惑う部分もありましたが、pandasと似ているので、Pandasを触ったことがあれば、すんなり移行できる印象です。
pandasにしか対応してないpythonライブラリもあるため、その場合は、to_pandas()でpandasに変換すれば問題ないかと思います。

個人的には、polarsでの記述の方がしっくりきますし、処理も早く快適なので、今後もデータ処理においては、polarsメインで使用していきたいと思います。