🧮
[Python] Pandas/Polars よく使う処理での差異について
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型になります。
- pandasでNAが入ったカラムはobject型になりますが、polarsではnull以外の型がキープされます。例えば、pl.Series=
- 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の動作の比較用にご利用ください。
-
polars.DataFrame.groupby_dynamicより。
The index column must be sorted in ascending order.
と記載あり ↩︎
Discussion