👻

「Pandas使うよりPolarsの方がいいよ」は本当かもね??

に公開

こんにちは☀️

TRUSTART株式会社のみつです!

最近は、大きめなデータを扱うことが多いです。

そんな中、先輩エンジニアにpandasよりもPolarsの方がいいという話を聞き、Polarsを使うようになったんですが、「本当に??」という気持ちもどこかにありました。(自分の知らないもの恐怖症)

実際に使ってみるとすごく早い気がして、いいじゃん!や使う中での学びもあり、少しまとめてみた記事になります。

😕扱うデータが重すぎる

扱っているデータは、本当に大きくPC壊れちゃうんじゃないかと思うぐらいです。

数十万件〜数百万件のデータは、普通にある。

数千万件のデータだって、至る所に。

ほんの少し実行スピードが違うだけで、実行完了までに大きな差が出てきます。

😕とあるCSVデータを縦にマージしたい

私個人が使っているコードの一部を紹介しつつ、Polarsでの実装を眺めてみます。

下記では、input ディレクトリにある output_というプレフィックスから始まるcsvを縦に合体します。

input ディレクトリなのに、output_は、紛らわしいですね・・・。💦

どこかでoutputされたものたちをinputディレクトリに全部入れてさらに合体したい、という実装です。

📍Polarsで実装をするとこうなる

polars_main.py
import os
import polars as pl
import time 

INPUT_DIR: str = "./input"
INPUT_FILE_PREFIX: str = "output_"
INPUT_FILE_SCHEMA: dict[str, pl.DataType] = {
    "XXX": pl.Utf8,
    "XXX": pl.Utf8,
    "XXX": pl.Utf8,
    "XXX": pl.Utf8,
    "XXX": pl.Utf8,
    "XXX": pl.Utf8,
    "XXX": pl.Utf8,
    "XXX": pl.Utf8,
    "XXX": pl.Utf8,
    "XXX": pl.Utf8,
    "XXX": pl.Utf8,
    "XXX": pl.Utf8,
    "XXX": pl.Utf8,
    "XXX": pl.Utf8,
    "XXX": pl.Utf8,
    "XXX": pl.Utf8,
    "XXX": pl.Utf8,
}

def get_file_paths() -> list[str]:
    """
    入力ディレクトリ内のファイルパスを取得する関数
    """
    file_paths = []
    for file_name in os.listdir(INPUT_DIR):
        if file_name.startswith(INPUT_FILE_PREFIX):
            file_paths.append(os.path.join(INPUT_DIR, file_name))
    return file_paths

def get_input_csvs(file_paths: str) -> list[pl.DataFrame]:
    """
    入力CSVファイルを読み込み、整形して返す関数
    """
    dataframes = pl.DataFrame()
    for file_path in file_paths:
        df = pl.read_csv(
            file_path, schema=INPUT_FILE_SCHEMA, infer_schema_length=10000000000
        )
        dataframes = pl.concat([dataframes, df])
    return dataframes

def fix_no_rows(df: pl.DataFrame) -> pl.DataFrame:
    """
    'No'列の値を1から始まる連番に修正する関数
    """
    df = df.with_columns((pl.arange(1, df.height + 1)).alias("No"))
    return df

def main():
    start_time = time.time()

    file_paths = get_file_paths()
    dataframes = get_input_csvs(file_paths)
    dataframes = fix_no_rows(dataframes)
    dataframes.write_csv("../output_XXX/merged_output.csv")

    end_time = time.time()
    elapsed_time = end_time - start_time
    print(f"Processing completed in {elapsed_time:.2f} seconds.")

if __name__ == "__main__":
    main()
    print("Processing completed.")

📍pandasで実装をするとこうなる

Polarsとあまり変わらない実装にはなりました。

pandas_main.py
import os
import pandas as pd
import time 

INPUT_DIR = "./input"
INPUT_FILE_PREFIX = "output_"
INPUT_FILE_SCHEMA = {
    "XXX": str,
    "XXX": str,
    "XXX": str,
    "XXX": str,
    "XXX": str,
    "XXX": str,
    "XXX": str,
    "XXX": str,
    "XXX": str,
    "XXX": str,
    "XXX": str,
    "XXX": str,
    "XXX": str,
    "XXX": str,
    "XXX": str,
    "XXX": str,
    "XXX": str,
}

def get_file_paths() -> list[str]:
    """
    入力ディレクトリ内のファイルパスを取得する関数
    """
    file_paths = []
    for file_name in os.listdir(INPUT_DIR):
        if file_name.startswith(INPUT_FILE_PREFIX):
            file_paths.append(os.path.join(INPUT_DIR, file_name))
    return file_paths

def get_input_csvs(file_paths: list[str]) -> pd.DataFrame:
    """
    入力CSVファイルを読み込み、整形して返す関数
    """
    dataframes = []
    for file_path in file_paths:
        df = pd.read_csv(file_path, dtype=INPUT_FILE_SCHEMA, encoding="utf-8")
        dataframes.append(df)
    merged = pd.concat(dataframes, ignore_index=True)
    return merged

def fix_no_rows(df: pd.DataFrame) -> pd.DataFrame:
    """
    'No'列の値を1から始まる連番に修正する関数
    """
    df["No"] = [str(i) for i in range(1, len(df) + 1)]
    return df

def main():
    start_time = time.time()

    file_paths = get_file_paths()
    dataframes = get_input_csvs(file_paths)
    dataframes = fix_no_rows(dataframes)
    dataframes.to_csv("../output_XXX/merged_output.csv", index=False, encoding="utf-8")

    end_time = time.time()  # 計測終了
    elapsed_time = end_time - start_time
    print(f"Processing completed in {elapsed_time:.2f} seconds.")

if __name__ == "__main__":
    main()
    print("Processing completed.")

📍実行結果の比較

計測は end_time - start_timeという雑なtimeを使った引き算です・・・。💦

時間計測でいい方法が他にもあるのかは調べてません!!

比較の仕方はさておき、スピードが全然違うじゃん、という結果。

20万行程度のcsvを3つ縦に結合するだけだったんですが、明らかに実行スピードが異なります。

⭐️Polars

python polars_main.py
Processing completed in 0.18 seconds.
Processing completed.

⭐️pandas

python pandas_main.py 
Processing completed in 1.63 seconds.
Processing completed.

やっぱりPolarsなのかなぁ

やっぱりPolarsがいいのかなという気持ちになっているの今。

Polarsのドキュメントを読んでたらなるほどな一文を発見。

そもそもpandasは、シングルスレッドで動くけど、Polarsはマルチスレッドで動くようにいい感じになっているということ。

早い理由に納得感ありです。

Pandasは、Pythonデータ分析において広く採用されている包括的なツールであり、豊富な機能セットと強力なコミュニティサポートで知られています。しかし、シングルスレッドであるため、中規模および大規模データセットではパフォーマンスとメモリ使用量に問題が生じることがあります。

一方、Polarsは単一ノード上での高性能マルチスレッドコンピューティングに最適化されており、特に中規模から大規模のデータ処理において、速度とメモリ効率を大幅に向上させます。より構成可能で厳密なAPIにより、表現力が向上し、スキーマ関連のバグが減少します。

https://docs.pola.rs/user-guide/misc/comparison/

🔥まとめ

🔥Polars良さそう。笑🔥

あとこういう時は、pandasを使うんだを知りたい。

もっと色々調べてみようと思います。

おわり。

参考

https://docs.pola.rs/

https://pandas.pydata.org/docs/

最後に

TRUSTART株式会社は、一緒に働くメンバーを募集しています!
インターンメンバーも大募集中です!
興味を持っていただいた方は、ぜひ弊社のページをご確認ください!!!

https://www.trustart.co.jp/recruit/

TRUSTARTテックブログ

Discussion