Rustのデータフレームcrateのpolarsとpandasの比較
TL;DR
rustにも実はpandas likeなcrateがあることを知ったのでpandasとの対応関係をまとめてた。最善である保証はありません。またVersionごとに破壊的変更がそこそこあるので、Versionに注意する必要があります。
これを使えば大きなファイルを素早く処理できる可能性がありますが、さすがにrustなのでお手軽感はあまりありませんでした。
excvrを使えばJupyter上で動かせます。Jupyter labを使うとPythonとRustの比較が非常にやりやすくて良かったです。

ただ補完や型の推測が効かないので少し困りました。rust-analyzer対応もしてみました。補完は効くようになりましたが、やはりVSCodeなどに比べると微妙。
サンプルノートブックはこちら。docker-composeで起動できます。
polars
Apache Arrowsをベースにしたデータフレームライブラリ。なんかpy-polarsみたいなのもあって、pandasより速いらしい。polarsのgithubのREADMEにベンチマークテストがある。使い勝手としてはどちらかといえばRのtidyverseに似ている気がする。
ChunkedArray
多分特徴的なのが、Seriesから変換できるChunkedArrayという構造体を持つ点。ChunkedArrayは型があるので様々な演算ができる。また、条件をつかった列選択する際にはChunckedArray<BooleanType>を使う必要がある。
Install
featureを選ぶことで、日付変換やndarrayへの変換、ランダムサンプリングなどに対応できる。今回はndarrayとランダムサンプリングを試してみる。あとエラーハンドリングにanyhowを入れておく。
Cargo.toml
Jupyterを使う場合は、
Rustのバージョンは1.52以上が必要です。
pandasはお好みのパッケージ管理ツールでインストールしてください。
rust側は下記のtodo!()部分に相当する場所を書いているつもりです。
Python側も下記のimportを行っている前提です。
SeriesとDataFrameとChunkedArrayの演算
非常に長いので畳んである。ChunkedArrayは大抵の演算ができる。Seriesの比較は条件による行選択の際に必要なので見ておくとよいです。
numberとSeries
| 演算名 | vs number |
|---|---|
add | s + 1 |
sub | s - 1 |
div | s / 1 |
mul | s * 1 |
SeriesとSeries
| 演算名 | 操作 |
|---|---|
add | &s1 + &s2 |
sub | &s1 - &s2 |
div | &s1 / &s2 |
mul | &s1 * &s2 |
mod | &s1 % &s2 |
eq | s1.series_equal(s2) |
DataFrameとSeries
| 演算名 | 操作 |
|---|---|
add | &df + &s |
sub | &df - &s |
div | &df / &s |
mul | &df * &s |
mod | &df % &s |
Seriesの演算
| 演算名 | 操作 |
|---|---|
| sum | s.sum<T>() |
| max | s.max<T>() |
| min | s.min<T>() |
| mean | s.mean<T>() |
Seriesの比較
Series同士、Seriesとnumberを比較できる
| 演算 | vs Series | vs number |
|---|---|---|
= | s1.equal(s2) | s1.equal(1) |
!= | s1.not_equal(s2) | s1.not_equal(1) |
> | s1.gt(s2) | s1.gt(1) |
=> | s1.gt_eq(s2) | s1.gt_eq(1) |
< | s1.lt(s2) | s1.lt(1) |
<= | s1.lt_eq(s2) | s1.lt_eq(1) |
DataFrameの演算
| 演算名 | 操作 |
|---|---|
| sum | df.sum() |
| max | df.max() |
| min | df.min() |
| median | df.median() |
| mean | df.mean() |
| var | df.var() |
| std | df.std() |
ChunckedArrayの演算
基本的に殆どの演算ができます。できる演算子は
- +
- -
- /
- *
- %
- pow
あたりです。また、ChunkedArray<BooleanType>は&と|のbit演算ができます。
比較はSeriesと同じ感じでやる必要があります。
あとはIteratorとかVectorに処理する感じのものはできるものがあります。
- map
- fold
- is_empty
- contains
- len
など。
また、ChunkedArray<Utf8Type>はto_lowercaseやto_upper_case、replaceなんかが使えます。
default featureのtemporalがあれば、時間のパースもできます。
Seriesの作成
nameは任意。
newを使う場合は名前指定が必須です。collectのときは空文字列が名前になります。
DataFrameの作成
マクロが便利です。
列選択
selectで選ぶと、Result<DataFrame>が返ってきます。
columnで選ぶと、Result<Series>が返ってきます。
条件に応じた列選択
どちらもcolumnsをとってきてfilterなりなんなりをすればよい。多分strメソッドを使うのがpandasっぽくて好きです。 Rustはget_columnsでcolumnsをもって来ることができます。もう少し何とかならないかな...
列の入れ替え
列追加
polarsのcolumの追加はwith_column関数やreplace_or_add関数で行える。
assignみたいないい感じの関数が見つからなかった。四則演算や簡単な演算はSeriesにして計算すればいける。to_owned()2回やってるの解消できる気がするけどできなかった。
無名関数を使いたい際には、一端ChunkedArrayに変換してからapplyやmapを使う。Seriesは型を持たないが、ChunkedArrayは型があるので演算ができる。
DataFrame構造体にはapplyが存在しているが、&mut selfなので、本体が変わってしまう。なのでselectかcloneしてからみたいな処理になるけどどっちが早いのだろうか。
条件による行選択
単独条件
複数条件
ChunkedArrayはbit演算ができます。
含まれているかなどの演算
たぶんChunkedArrayに変換してやる方法しか見つかりませんでした。applyはSelfを返すので、ChunkedArray<Int32Type>からChunkedArray<BooleanType>に変換はできない。なので、mapを使った後collectする必要がある。
GroupBy
Groupby用にデータフレームを準備する。
build-inの演算
polarsでは
- count
- first
- last
- sum
- min
- max
- mean
- median
- var
- std
- count
- quantile
- n_unique
ができる。使い方は
- 特定の列でGroupby
- 演算したい列を指定 (指定なしなら全部)
- 演算
単一の演算
複数の演算
任意の演算
applyの返り値はResult<DataFrame>である必要がある。
hstack, vstack (concat)
pandasのconcat。pandasのstackとは機能が違うので注意が必要。pandasは合わない行があればNaNで埋めるがpolarsはエラーする。
データフレームを準備する。
hstack
vstack
Join
pandasはDataframeのjoinメソッドもありますが、mergeのほうがよく使うのでこちらで。
polarsでは、Dataframeのjoinメソッドが使えます。inner_join, left_join, outer_joinはjoinのラッパーです。
引数のS: Selectionは&strと、&[&str], Vec<&str>あたりを取れます。
注意点
pandasとpolarsの異なる点として、完全に要素が一致のcolumnがどうなるかの挙動が変わります。 polarsでは、列名が異なっていても左側の列名で統合されます。pandasのmergeでは列名が異なっていれば、要素が同じでも統合はされません。
重複行の抽出
重複行の削除
両方ともsubsetを選ぶことで、同じように特定の列の重複行を削除できる。
numpy / ndarrayへの変換
型を指定する必要があります。
io
csvはdefaultで読むことができます。featuresを指定することで、json, parquet, ipcなども読むことができるようになります。
read csv
csv以外ならsep = "\t"とかしてください。
csv以外ならwith_delimiterの引数を好きに変えてください。なくても動きます。
あとparalellのfeatureがあると、daskみたいな感じでCPUの上限コア数を使って読み込みます。いやな場合は、.with_n_threads(Some(2))とかしてください。with_n_threadsはfrom_pathを使ってCsvReaderを作った時しか使えないようです。
write csv
readと同様。
TODO
- pivot
- melt
- fillna系
- sample_n
- io系
気長に埋めていきます。
感想
できることは多い感じがします。pandasみたいに柔軟な処理をする用途では使いにくそうですが、決まりきった処理ならpolarsで記述すると生産効率向上に寄与する可能性があります。