🧮

[Python] Pandas/Polars よく使う処理での差異について

2022/03/21に公開

RustでPandasに似たライブラリはないか?と調べてみたところPolarsライブラリがありました。Pythonでも使えるということで、どんな差分があるか気になったのでまとめました。

updates

  • 2024.07.27: polarsのバージョンを0.20.31から1.2.1にアップデートしました。いよいよ初のメジャーアップデートです!
  • 2024.06.23: polarsのバージョンを0.20.5から0.20.31にアップデートしました
  • 2024.01.21: polarsのバージョンを0.18.3から0.20.5にアップデートしました
    • apply → map_elements にリネームされました。
    • groupby → group_by にリネームされました。
    • groupby_dynamic → group_by_dynamic にリネームされました
  • 2023.06.17: polarsのバージョンを0.16.4から0.18.3にアップデートしました
    • groupby_dynamicを使う前に、明示的に昇順ソートするか、ソートフラグをTrueにしないとエラーになる仕様変更に対応しました。
    • v0.16.17以降、pivotで明示的にaggregate_functionを指定する必要がある警告に対応しました。
  • 2023.03.18: polarsのバージョンを0.15.8から0.16.14にアップデートしました
    • sortのreverse属性がdescending属性にリネームされました。
    • with_columnがwith_columnsにリネームされました。
  • 2022.12.30: polarsのバージョンを0.14.11から0.15.8にアップデートしました(APIの利用方法に変更はありません、print時にdtype=datetimeのタイムゾーンが表示されるようになっています)
  • 2022.09.17: polarsのバージョンを0.13.56から0.14.11にアップデートしました (列参照・更新などに変更があります)
  • 2022.07.24: polarsのバージョンを0.13.13から0.13.56にアップデートしました(APIに変更はありません)

注釈

  • polarsモジュールは開発中なので、今後APIが大きく変わる可能性があります。
  • ここでの差異・差分については主だったものをまとめています。そのため、全てを網羅しているわけではありません。

仕様上の大きな差分

公式ドキュメントのComing from Pandasに一通り記載があります。このうち、主に影響するものを抜粋します。

  • pandasにはindexがあるものの、polarsには存在しません。そのため、indexに由来する処理やパラメータは使えません。
    • polarsでは、colなどの代わりにスライスを使います。
    • polarsにて一定日時で集計したいとき、resamplingのかわりにgroup_by_dynamicsを使います。
      • 2023.06.17追記: v0.18.0より、group_by_dynamicsを呼び出す前に明示的にソートするか、ソート済みフラグの付与が必須となりました。sortでソートを行うか、set_sorted()を呼び出してソート済みであることを明示的に示す必要があります[1]
  • 欠損値の取り扱いについて、pandasではNA(pd.NA)、polarsではnull(pl.Null)が割り当てられます。
    • pandasでNAが入ったカラムはobject型になりますが、polarsではnull以外の型がキープされます。例えば、pl.Series=[1, null, 2]であればi64型になります。
  • polarsでは、df["hoge"] = 1など単体の数値やSeriesをそのまま代入することはできません。df = df.with_columns(pl.Series(name="hoge", values=[1 * len(df)]))で列を追加するか、df = df.replace("hoge", pl.Series(name="hoge", values=[1 * len(df)]))などで列を置き換えます。

定義上の差分

  • pandasモジュールはpd, polarsモジュールはplと略称で記載します。
  • dfは1データフレームを指します。
役割 pandas polars 備考
データフレームの定義 pd.DataFrame pl.DataFrame -
csvファイル読み込み pd.read_csv pl.read_csv -
csvファイル書き込み df.to_csv 差分なし -
特定カラム抽出(単列) df["hoge"] or df.hoge df["hoge"] or df.get_column("hoge") ※1
特定カラム抽出(複数列) df[["hoge","fuga"]] 差分なし -
特定カラム抽出(範囲指定) df.loc[:, :"fuga"] df[:, :"fuga"] polarsにはindexがないため、pl.DataFrame.locは存在しない
フィルタリング(比較演算子) df[(df.hoge == "a") | (df.hoge == "b")] 使用不可、filterを使う -
フィルタリング(query) df.query('hoge == "a" or hoge == "b"') df.filter[(pl.col("hoge") == "a") | (pl.col("hoge") == "b")] -
行数を出す len(df) 差分なし -
特定列に代入・置換する df.assign(A=...) df = df.with_columns(pl.Series(name="A",values=...)) -
特定列(複数)に代入・置換する df.assign(A=...,B=...) df = df.with_columns([pl.Series(name="A",values=...), pl.Series(name="B",values=...)]) -
あるカラムに対して関数を適用する(map) pd.Series.map 存在しない -
あるカラムに対して関数を適用する(apply) pd.Series.apply pl.Series.map_elements polarでは第二引数で関数の返り値の型が必要※2
DataFrameの内、ユニークな部分を返す pd.DataFrame.drop_duplicates pl.DataFrame.unique -
Seriesの内、ユニークな部分を返す pd.Series.drop_duplicates pl.Series.unique -
Seriesをリストにする pd.Series.to_list 差分なし -
特定カラム(群)でソートする pd.DataFrame.sort_values pl.DataFrame.sort pandasでのacsendingが、polarsでのdescendingに対応する。pandasのacsending=[True, False]はpolarsのdescending=[False, True]に対応する。
na(欠損値)を置き換える pd.DataFrame.fillna pl.DataFrame.fill_null ※3
nan(Not a Number)を置き換える pd.DataFrame.fillna pl.DataFrame.fill_nan -
na(欠損値)を削除する pd.DataFrame.dropna pl.DataFrame.drop_nulls -
nan(Not a Number)を削除する pd.DataFrame.dropna pl.DataFrame.drop_nans -
カラム名をリネーム df.rename(columns={"hoge": "baz", ...}) df.rename({"hoge": "baz", ...}) -
グルーピング pd.DataFrame.groupby pl.DataFrame.group_by -
集約処理 df_group.agg({"hoge": "sum"}) df_group.agg([pl.col("hoge").sum()]) -
統計量の計算 pd.DataFrame.describe pl.DataFrame.describe polarsでは、count, 25%, 75%は出力されない。また、50%はmedianに対応する ※4
一定期間ごとでグルーピング pd.DataFrameのindexにdatetime型のリストを入れ、pd.DataFrame.resampleを使う pd.DataFrameの対象カラムをdf.date.str.strptimeなどでdatetimeにしてから、groupby_dynamicを使う polarsではoffsetが有効でない?
2つのDataFrameを縦につなげる pd.concat pl.concat -
2つのDataFrameをmergeする pd.DataFrame.merge pd.DataFrame.join 名前はjoinだが、pandasのmergeの挙動に近い。pd.DataFrame.joinの様に3つ以上のDataFrameをmergeできない。
ピボットテーブルを作る pd.DataFrame.pivot pl.DataFrame.pivot -

※1: v0.14以上より、df.hoge などのように列指定をすることはできなくなりました。
※2: v0.14より前のバージョンでは、返り値を指定しない場合NotImplementedErrorとなっていました。
※3: valueで値を指定するか、strategyで以下いずれかのルールに従ってfillすることができます。
※4: v1.2.1現在、集約名を示すカラムが"describe"から"statistic"に変更されています。

  • "forward": Series内の直前の値で置き換える
  • "backward": Series内の直後の値で置き換える
  • "mean": Series内の平均値で置き換える
  • "max": Series内の最大値で置き換える
  • "min": Series内の最小値で置き換える
  • "one": 1で置き換える
  • "zero": 0で置き換える

ただし、valueはv0.14より前のバージョンでは使用できません。

サンプルコード

以下のGitHubリポジトリに、今回の内容を踏まえたサンプルコードを置いています。pandasとpolarsの動作の比較用にご利用ください。
https://github.com/Niccari/pandas_polars_processing_example

脚注
  1. polars.DataFrame.groupby_dynamicより。The index column must be sorted in ascending order.と記載あり ↩︎

Discussion