Pandas 2.0はPolarsよりも速いのか？

PandasはPythonのデータ解析ライブラリです。
データの加工や集計、分析に皆様活用されていると思います。

最近では[Polars](https://github.com/pola-rs/polars)というRustベースの高速データ解析ライブラリが登場し、こちらを利用する人も増えているのではないかと思います。

PolarsではApache Arrowという列指向のデータフォーマットをベースにしており、データの高速処理を実現しています。

そんな中、最近Pandasの2.0がプレリリースされたようです。
2.0では、Apache ArrowをPython操作できるようにしたライブラリPyArrowがPandasで使えるようになったようなので、どれだけ高速に処理できるのか早速試してみました。

参考：[Pandas2.0の新機能](https://pandas.pydata.org/pandas-docs/version/2.0/whatsnew/v2.0.0.html)

# 前提
Pandas2.0は**プレリリース版を使用します。**
（この記事を書いている2023/3/21時点では、まだ正式リリースされていないようです。）

追記(2023/4/6):
2023/4/3にPandas2.0.0が正式リリースされたようです。



# 実行環境
Google Colaboratory（無料版、GPU利用なし）を利用します。

# 準備
pandasのプレリリース版`2.0.0rc0`と、比較用に`polars`をインストールします。
```
!pip install --upgrade --pre pandas==2.0.0rc0
!pip install polars
```

<br>

インポートとバージョンの確認
```python
import pandas as pd
import numpy as np
import polars as pl
print('pandas: ', pd.__version__)
print('polars: ', pl.__version__)

# 実行結果
# pandas:  2.0.0rc0
# polars:  0.16.13
```

<br>

性能評価用のダミーデータを準備します。
今回は**10万行のデータ**を利用します。
```python
# ダミーファイル用のデータ準備
str_val = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
sample_n = 100000
df = pd.DataFrame({
    'col1': np.random.choice(str_val, sample_n),
    'col2': np.random.rand(sample_n),
    'col3': np.random.rand(sample_n),
})
print(df.shape)
display(df.head())

# ダミーファイル作成
df.to_csv('./df_data.csv', header=True, index=False)

# 実行結果
# (100000, 3)
#      col1	  col2	    col3
# 0	   g	  0.622630	0.878988
# 1	   f	  0.389823	0.525934
# 2	   d	  0.043534	0.703387
# 3	   e	  0.750366	0.596771
# 4	   f	  0.599945	0.038867
```

# 評価用コードの実装と実行
以下３つのライブラリの処理速度を比較します。（PyArrow未使用のPandasも2.0.0rc0を使います。）
* Pandas - PyArrow未使用
* Pandas - PyArrow**使用**
* Polars

ファイル読み込み
```python
# Pandas PyArrow未使用
%timeit -n 100 -r 10 pd.read_csv('./df_data.csv')

# Pandas PyArrow使用
%timeit -n 100 -r 10 pd.read_csv('./df_data.csv', \
                                 engine='pyarrow', \
                                 use_nullable_dtypes=True)

# Polars
%timeit -n 100 -r 10 pl.read_csv('./df_data.csv')

# 実行結果
# 54.5 ms ± 6.76 ms per loop (mean ± std. dev. of 10 runs, 100 loops each)
# 25.8 ms ± 6.02 ms per loop (mean ± std. dev. of 10 runs, 100 loops each)
# 17.3 ms ± 3.79 ms per loop (mean ± std. dev. of 10 runs, 100 loops each)
```

<br>

ファイル保存
```python
# Pandas PyArrow未使用
%timeit -n 10 -r 5 df_read.to_csv('./df_read.csv', index=False)

# Pandas PyArrow使用
%timeit -n 10 -r 5 df_read_arrow.to_csv('./df_read_arrow.csv', index=False)

# Polars
%timeit -n 10 -r 5 df_read_polars.write_csv('./df_read_polars.csv')

# 実行結果
# 472 ms ± 97.8 ms per loop (mean ± std. dev. of 5 runs, 10 loops each)
# 532 ms ± 185 ms per loop (mean ± std. dev. of 5 runs, 10 loops each)
# 62.1 ms ± 2.65 ms per loop (mean ± std. dev. of 5 runs, 10 loops each)
```

<br>

mean
```python
# Pandas PyArrow未使用
%timeit -n 100 -r 10 df_read['col2'].mean()

# Pandas PyArrow使用
%timeit -n 100 -r 10 df_read_arrow['col2'].mean()

# Polars
%timeit -n 100 -r 10 df_read_polars.select('col2').mean()

# 実行結果
# 324 µs ± 47.7 µs per loop (mean ± std. dev. of 10 runs, 100 loops each)
# 98.6 µs ± 28.4 µs per loop (mean ± std. dev. of 10 runs, 100 loops each)
# 196 µs ± 56 µs per loop (mean ± std. dev. of 10 runs, 100 loops each)
```

<br>

unique
```python
# Pandas PyArrow未使用
%timeit -n 100 -r 10 df_read['col1'].unique()

# Pandas PyArrow使用
%timeit -n 100 -r 10 df_read_arrow['col1'].unique()

# Polars
%timeit -n 100 -r 10 df_read_polars.select('col1').unique()

# 実行結果
# 3.99 ms ± 299 µs per loop (mean ± std. dev. of 10 runs, 100 loops each)
# 1.55 ms ± 84.3 µs per loop (mean ± std. dev. of 10 runs, 100 loops each)
# 3.69 ms ± 170 µs per loop (mean ± std. dev. of 10 runs, 100 loops each)
```

<br>

round
```python
# Pandas PyArrow未使用
%timeit -n 100 -r 10 df_read[['col2', 'col3']].round()

# Pandas PyArrow使用
%timeit -n 100 -r 10 df_read_arrow[['col2', 'col3']].round()

# Polars
%timeit -n 100 -r 10 df_read_polars.with_columns([ \
    pl.col(c).round(0) for c in ['col2', 'col3'] \
])

# 実行結果
# 2.17 ms ± 79.9 µs per loop (mean ± std. dev. of 10 runs, 100 loops each)
# 4.7 ms ± 92.5 µs per loop (mean ± std. dev. of 10 runs, 100 loops each)
# 542 µs ± 68.7 µs per loop (mean ± std. dev. of 10 runs, 100 loops each)
```

<br>

集計
```python
# Pandas PyArrow未使用
%timeit -n 100 -r 10 df_read.groupby('col1').sum()

# Pandas PyArrow使用
%timeit -n 100 -r 10 df_read_arrow.groupby('col1').sum()

# Polars
%timeit -n 100 -r 10 df_read_polars.groupby('col1').sum()

# 実行結果
# 8.14 ms ± 2.07 ms per loop (mean ± std. dev. of 10 runs, 100 loops each)
# 13.9 ms ± 3 ms per loop (mean ± std. dev. of 10 runs, 100 loops each)
# 5.36 ms ± 1.26 ms per loop (mean ± std. dev. of 10 runs, 100 loops each)
```

<br>

merge
```python
# マージ用ダミーデータ作成
df_tmp = pd.DataFrame({
    'col1': ['a', 'b', 'c', 'd', 'e', 'f', 'g'],
    'val': ['000', '001', '002', '003', '004', '005', '006']
})

# Pandas PyArrow未使用
%timeit -n 100 -r 10 df_read.merge(df_tmp, on='col1', how='left')

# Pandas PyArrow使用
%timeit -n 100 -r 10 df_read_arrow.merge(df_tmp, on='col1', how='left')

# Polars
df_tmp_pl = pl.from_pandas(df_tmp)
%timeit -n 100 -r 10 df_read_polars.join(df_tmp_pl, on='col1', how='left')

# 実行結果
# 16.8 ms ± 3.39 ms per loop (mean ± std. dev. of 10 runs, 100 loops each)
# 21.2 ms ± 3.5 ms per loop (mean ± std. dev. of 10 runs, 100 loops each)
# 6.74 ms ± 1.85 ms per loop (mean ± std. dev. of 10 runs, 100 loops each)
```

# 結果
結果をグラフで確認します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3038053/b135d4b5-41d8-689f-d1ad-49fe6d053092.png" width=50%>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3038053/774742df-2997-cb78-f7f6-19440fd618a8.png" width=90%>

<br>

meanとuniqueでPandas PyArrow使用が一番処理速度が速いが、それ以外は全てPolarsが速いという結果になりました。

<br>
<br>

データ件数を**1000万件に増やした**バージョンでも試してみました。
結果は以下の通りです。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3038053/318b8ade-fe61-bc4c-ae35-f292d514245b.png" width=50%>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3038053/e2e782ff-ad9c-7aac-8499-377107ef27b4.png" width=90%>

unique以外全てPolarsが速いという結果になりました。

# まとめ
Polars強い。。