Polarsで処理を高速化し、ゆとり時間を生み出そう！

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

# はじめに
Pythonを2017年ころから使い始めてから、6年くらいの年月が経ちましたが、
2023年の大きな変化の1つとして、Polarsを使うようになったということが挙げられるかと思います。

今回、2023年にPolarsを使い始めたことで体感したメリットを綴って行きたいと思います。
(主にPandasを使っていて、Polarsをまだ使っていない人に向けて)

# Polarsとの出会い
もはや曖昧で覚えていないですが、2023年の2月くらいだったのではないかと思います。  
Kaggleの[Predict Student Performance from Game Play](https://www.kaggle.com/competitions/predict-student-performance-from-game-play)あたりから、「あれ？Polarsってライブラリ使っている人がるなぁ。」って気づいて使い始めたかと思います。

# Polarsの特徴 (主にPandasと比較して)
あまり中身のことは詳しくないですが、(中身はAdvent Calenderで多くの人が書いてくれる(はず！))
下記のようなメリット・デメリットを感じています。

特に、大きなサイズのデータを使う程、その恩恵を感じるかと思います。

- メリット
    - Pythonで永らく活用されてきたPandasと同じ様に、DataFrameでデータを処理できるライブラリ
    - Pandasよりも圧倒的に高速。(Rustで実装されているから？仕組みはよく知らない。)
    - Pandasと同じような表計算機能を広くカバー。
- デメリット
    - Pandasの持つ機能の一部をカバーできていない。(きっと今後改良されるだろうと期待。)
    - 参考になるドキュメントが少ない。
    - 故に、困ったときにChatGPTに質問しても解決しないことが多い。(嘘の回答が返ってくる。2023年末現在の実感)

# Polarsで生み出される時間
2023年は、実務でもPrivate(主にKaggle)でも、Polarsの活用によって多くの時間が生み出されたかと思います。
私の場合は、実務では大量のデータを前処理しますし、
Kaggleのテーブルコンペでも、2023年は用意されたデータが大容量というケースが多かったため、かなり助けてもらうことになりました。

どのくらい早く終わるかは、マシンのスペックやデータに因ると思いますが、
私の場合では、下記のような経験があり、大分Polarsの有難みを体感しています。
- Pandasを使っていた時
    - 夜帰る前に計算を開始して、翌朝の出社時に処理の結果を確認していた
- Polarsに書き換えた時
    - 昼休憩前に計算を開始して、休憩明けに処理が終わっていた。

Polarsを使い始める前は、翌朝の出社時に、計算ミスってた！！なんて気づいて、
計算をやり直すこともありましたし、
単純にこうした処理を行う回数分の掛け算で得をするので、もうPolarsを止められないですね。

# Polarsを活用するデータ処理の例
実際には下記のような感じで処理をしています。
ほんの一例ですが、下記のようなループ処理をする場合に、同じ処理をPandasでやるよりも圧倒的に早く処理が終わります。

はじめはPandasよりも複雑な様に見えて戸惑うかもしれませんが、ここは慣れるだけかと思います。
慣れたときのメリットが大きいですし、
困ったら最悪pl.to_pandas(), pl.from_pandas()でpolars.DataFrane <--> pandas.DataFrameの変換も可能なので、
まだ使っていない方は、まずは使ってみて頂きたいです。

```
# データを読み込み (data.parquetという名前のファイル)
import gc
import polars as pl
df = pl.read_parquet("data.parquet")

# 高速にデータ処理 (data内に存在するユニークなuser_id毎にデータを前処理)
df_list = []
user_list = df["user_id"].unique()

for _user in tqdm(user_list):
    # filterで、特定のユーザーのデータを抽出 -> 処理
    user_df = df.filter(pl.col("user_id") == _user)
    
    # 時間でソート
    user_df = user_df.sort(["jst_gps_timestamp"], descending=False)
    
    # シンプルな四則演算
    df = df.with_columns(
        (pl.col("col1")/90).alias('new_col1'),
        ((pl.col("col2")+1).log()).alias('new_col2'),
    )
    
    # map_elements関数とlambdaを使った処理の例
    df = df.with_columns(
        df.get_column('timestamp').map_elements(lambda x: x.year).alias("year"),
        df.get_column('timestamp').map_elements(lambda x: x.month).alias("month"),
        df.get_column('timestamp').map_elements(lambda x: x.day).alias("day"),
        df.get_column('timestamp').map_elements(lambda x: x.hour).alias("hour"),
        df.get_column('timestamp').map_elements(lambda x: x.minute).alias("minute"),
        df.get_column('timestamp').map_elements(lambda x: x.second).alias("second"),
        df.get_column('timestamp').dt.weekday().alias("weekday"),
    )

    # 処理したユーザーのデータをリストに格納
    df_list.append(df)

# concatして1つのデータフレームに集約
df = pl.concat(df_list)
del df_list
gc.collect()
```
("user_id", "jst_gps_timestamp", "col1", "col2", "timestamp"はdata.parquet内に含まれるカラム(列)です。必要に応じて書き換えて活用ください。)

# Polarsを使うにあたって、よく参考にしているサイト・記事
- [pandasから移行する人向け polars使用ガイド](https://qiita.com/nkay/items/9cfb2776156dc7e054c8)<br>多くの代表的な処理が紹介されています。<br>ここにアクセスしてCntl+Fでキーワードを検索することで、問題解決につながるケースが多いです。
- [公式リファレンス](https://pola-rs.github.io/polars/py-polars/html/reference/datatypes.html#)<br>公式も親切にまとまっています。<br>どんな関数があるのか？活用例を知りたい！という時は眺めてみると良いかと思います。

# まとめ
2023年ももう終わりですね。
今年はPolarsのおかげで処理待ちの時間が大きく削減され、多くのタスクを従来よりも早く終わらせることができたと思います。

処理が早く終わることで、その分早く帰宅できるとかだと嬉しいのですが、
処理が早く終わっても、次の作業に追われることになるのでトータルでPCの前にいる時間は変わらない気もしないではないが…