スキップしてメイン コンテンツに移動

Polarsの味見

ここのところ読書か仕事に直接関係のある学習が多くて気付けばブログからかなり足が遠のいていた。
が、Polarsで遊ぶ時間をとったので、触った感触の走り書きを残すことにする。

GitHub - pola-rs/polars: Fast multi-threaded, hybrid-out-of-core DataFrame library in Rust | Python | Node.js https://github.com/pola-rs/polars/

まずはインストール。
Polarsをインストールしてはじめの一歩を試してみようとしている。
けど、最初で早速つまづく。

cargo add polars

### 下に記載のサンプルをmain.rsにコピー

cargo build

   Compiling arrow2 v0.16.0
error[E0658]: generic associated types are unstable
  --> /home/tkojima/.cargo/registry/src/github.com-1ecc6299db9ec823/arrow2-0.16.0/src/array/dictionary/typed_iterator.rs:9:5
   |
9  | /     type IterValue<'this>
10 | |     where
11 | |         Self: 'this;
   | |____________________^
   |
   = note: see issue #44265 <https://github.com/rust-lang/rust/issues/44265> for more information

error[E0658]: where clauses on associated types are unstable
  --> /home/tkojima/.cargo/registry/src/github.com-1ecc6299db9ec823/arrow2-0.16.0/src/array/dictionary/typed_iterator.rs:9:5
   |
9  | /     type IterValue<'this>
10 | |     where
11 | |         Self: 'this;
   | |____________________^
   |
   = note: see issue #44265 <https://github.com/rust-lang/rust/issues/44265> for more information

error[E0658]: generic associated types are unstable
  --> /home/tkojima/.cargo/registry/src/github.com-1ecc6299db9ec823/arrow2-0.16.0/src/array/dictionary/typed_iterator.rs:24:5
   |
24 |     type IterValue<'a> = &'a str;
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: see issue #44265 <https://github.com/rust-lang/rust/issues/44265> for more information

For more information about this error, try `rustc --explain E0658`.
error: could not compile `arrow2` due to 3 previous errors

Getting started - Polars - User Guide https://pola-rs.github.io/polars-book/user-guide/quickstart/intro.html

use color_eyre::{Result};
use polars::prelude::*;
use reqwest::blocking::Client;
use std::io::Cursor;

fn main() -> Result<()> { 
    let data: Vec<u8> = Client::new()
        .get("https://j.mp/iriscsv")
        .send()?
        .text()?
        .bytes()
        .collect();

    let df = CsvReader::new(Cursor::new(data))
        .has_header(true)
        .finish()?
        .lazy()
        .filter(col("sepal_length").gt(5))
        .groupby([col("species")])
        .agg([col("*").sum()])
        .collect()?;

    println!("{:?}", df);

    Ok(())
}

サポートバージョンは1.62 or laterで自分が使っているのは1.64。
試しに、次のようにしてみたけど、ちゃんとエラーになったので、期待されている手順やバージョンとしては正しそう。
User Guideの記述自体が古い?(みてみると、記述は0.24.3の頃のもの。最新は0.27.2)

rustup install 1.61
rustup default 1.61
cargo build

error: package `comfy-table v6.1.4` cannot be built because it requires rustc 1.62 or newer, while the currently active rustc version is 1.61.0

また、Quickstartに載っている次のようなサンプルも、LazyFrameが見つからずに動かないので、ドキュメントとしてはかなり荒い感じがする。

let lf2 = LazyFrame::scan_parquet("myfile_2.parquet", Default::default())?
    .select([col("ham"), col("spam")]);

で、`lazyframe polars rust`で探してみると、`polars_lazy`がヒットする。
が、それを追加しても `LazyFrame`は見つからないし、エラーも解決されない。

それから、`arrow2`自体のインストールに失敗する点を解決するべく、arrow2のリポジトリをチェック。どうやら、ここをみる限り、Nightlyを使わないといけないように見える。

arrow2/rust-toolchain.toml at main · jorgecarleitao/arrow2 · GitHub https://github.com/jorgecarleitao/arrow2/blob/main/rust-toolchain.toml

そこでようやく、そもそもPolarsのリポジトリ上の指定はどうだったかな?と見てみると、こちらも(当たり前だけど)nightlyの指定があった。このファイル以外では特段READMEとかドキュメント上で言及しないものなのだね。引っかかってしまった。
Historyを追っても初めから全部nightlyだね。

History for rust-toolchain.toml - pola-rs/polars https://github.com/pola-rs/polars/commits/master/rust-toolchain.toml

ということで次の内容を足してビルド成功。

rustup install nightly
rustup override set nightly
use polars_lazy::prelude::LazyFrame;

でLazyFrame自体は見つかった。

あとは、サンプルをベースに、遊んでみる。

polars::docs::lazy - Rust https://docs.rs/polars/0.27.2/polars/docs/lazy/index.html

use polars::df;
use polars::prelude::*;
use polars_lazy::dsl::col;
use polars_lazy::prelude::*;

fn main() {
    let result = call();
    println!("ok? --> {}", result.is_ok());
}

fn call() -> anyhow::Result<()> {
    let df = df![
        "a" => [1, 2, 3],
        "b" => [None, Some("a"), Some("b")]
    ]?;

    // from an eager DataFrame
    let lf: LazyFrame = df.lazy();
    println!("{}", lf.collect()?);

    // scan a csv file lazily
    let lf: LazyFrame = LazyCsvReader::new("my.csv").has_header(true).finish()?;
    let filtered = lf.clone().filter(col("id").lt(lit(10))).collect()?;
    println!("{}", filtered);

    let sum = lf
        .filter(col("id").lt(lit(10)))
        .select(&[col("score")])
        .sum()
        .collect()?;
    println!("{:?}", sum.column("score")?.i64()?.get(0));
    // the following sample is not available
    // scan a parquet file lazily
    // let lf: LazyFrame = LazyFrame::scan_parquet("some_path", Default::default())?;
    Ok(())
}

`scan_parquet()`は見つけられなかった。さらに別のuseが必要だった可能性もあるけど、今はParquetは必要ないので一旦スキップ。

ハードコードしたデータの読み出し、csvに入れたデータのフィルター読み出し、csvに入れたデータの読み出し+特定カラムの結果の合計出力を実施。
試しに使ったCSVはヘッダー付きの簡単なやつ。

id,name,age,score
1,kenji,10,87
2,takashi,22,33
999,unknown,999,999

実行結果はこんな感じ。

shape: (3, 2)
┌─────┬──────┐
│ a   ┆ b    │
│ --- ┆ ---  │
│ i32str  │
╞═════╪══════╡
│ 1   ┆ null │
│ 2   ┆ a    │
│ 3   ┆ b    │
└─────┴──────┘
shape: (2, 4)
┌─────┬─────────┬─────┬───────┐
│ id  ┆ name    ┆ age ┆ score │
│ --- ┆ ---     ┆ --- ┆ ---   │
│ i64stri64i64   │
╞═════╪═════════╪═════╪═══════╡
│ 1   ┆ kenji   ┆ 1087    │
│ 2   ┆ takashi ┆ 2233    │
└─────┴─────────┴─────┴───────┘
Some(120)
ok? --> true

仕事で使うには、正直、そもそもNightlyが必要であること、ドキュメントの整備状況、キーとして依拠しているライブラリの状況(arrow2)から照らすとリスクを感じはするけど、結構使いやすそうではある。

段々とPRとIssueが溜まってきているようには思えるから、気持ちが乗ったらContributionしてみたいなあ、なんて思ったりもする。

おしまい。

追記:同じ内容を試したけれど手元ではうまく動かなかった、というコメントをいただいたのでGitHubに置いておきました。 tkhm/polars-kumite https://github.com/tkhm/polars-kumite