HN 日本語サマリー

← 一覧へ戻る
プログラミング

Polars 2.0 のリリース候補版について

Pre-Release of Polars 2.0 (pola.rs)

366 pointsby komape121 コメント

要約

データフレームライブラリPolarsのバージョン2.0のリリース候補版が公開されました。このメジャーバージョンアップでは、大きな機能追加ではなく、過去の設計上の制約を取り除き、デフォルト設定をより合理的で多くのユーザーに有益なものに変更することに重点が置かれています。最も重要な変更点は、LazyFrameクエリがデフォルトでストリーミングエンジンを使用するようになり、メモリ使用量とパフォーマンスが大幅に向上する見込みです。

全文翻訳

Polars 2.0 のプレリリース版

本日、Polars 2.0 の最初のリリース候補版を公開します。正式な 2.0 リリースは数週間以内に予定されています。Polars 2.0 では、大きな機能追加を目指すのではなく、むしろ皆さんにとって「退屈な」体験となることを目指しています。メジャーバージョンを上げる理由は、過去の設計上の決定で現在私たちを妨げているものを排除し、より多くのユーザーに利益をもたらす、より合理的な設定にデフォルトを変更したいからです。最大のデフォルト変更は、すべての LazyFrame クエリがストリーミングエンジン上で実行されるようになることです。これにより、カジュアルな Polars ユーザーはメモリ使用量とパフォーマンスで大幅な改善を期待できます。全体として、ストリーミングエンジンは簡単に 5倍高速 になると予想しています。

ユーザーが 2.0 に移行するのを支援するため、完全な 移行ガイド を公開しました。この記事では、そのハイライトの一部を紹介します。

ストリーミングエンジンをデフォルトに

これは 2.0 における最も影響の大きい変更です。LazyFrame に対して collect を呼び出すと、デフォルトでストリーミングエンジンが使用されるようになり、ほとんどのクエリでメモリとパフォーマンスが劇的に向上します。この変更でメジャーバージョンを上げる必要があったのは、ストリーミングエンジンがデフォルトでは特定の操作(join、group_by、unpivot など)で行の順序を保証しないためです。これらの操作で観測可能な行順序が必要な場合は、maintain_order=True を設定することでオプトインできます。

デフォルトで「インメモリ」エンジンを使用し続けたいユーザーは、エンジンアフィニティを設定することで対応できます。

lf = pl.LazyFrame({"k": [2, 1, 0], "v": ["a", "b", "c"]})
other = pl.LazyFrame({"k": [0, 1, 2], "r": ["x", "y", "z"]})

# 2.0: engine="auto" はストリーミングエンジンに解決されます。
# 行順序は join, group_by, unpivot, ... では保証されなくなります。
( 
lf
    .join(other, on="k", how="left")
    .collect()
)
# ┌─────┬─────┬─────┐
# │ k   ┆ v   ┆ r   │   <- `lf` の元の行順序と一致しない場合があります
# └─────┴─────┴─────┘

# このクエリで観測可能な順序をオプトインする:
( 
lf
    .join(other, on="k", how="left", maintain_order="left")
    .collect()
)

# または、プロセス全体で古いインメモリエンジンをデフォルトとして維持する:
pl.Config.set_engine_affinity("in-memory")

# ...またはクエリごと:
( 
lf
    .join(other, on="k", how="left")
    .collect(engine="in-memory")
)

より厳格な Polars

Polars は厳格であり、早期に失敗することを目指しています。エラーは理想的には、パイプラインの実行から20分後に発生するのではなく、すぐに表面化すべきです。データ不一致に対する暗黙の動作は、デフォルトではなくオプトインであるべきです。なぜなら、そのような不一致はバグを隠す可能性があるからです。この厳格さは、AI駆動開発の台頭により、さらに価値が高まっています。エージェントは、collect_schema() を呼び出すことでクエリの構造を早期に検証でき、データの実体化なしに型を解決し、スキーマレベルの不一致を検出できます。これにより、エージェントは迅速なフィードバックを得られ、より速くイテレーションできるようになります。すべてのエラーがクエリプランのコンパイル中に捕捉できるわけではなく、一部はデータに依存します。これらの場合、Polars は不整合をサイレントに生成するのではなく、一貫性を確実に捕捉するために、より厳格な動作をデフォルトとします。

以下に、Polars がより厳格になった例をいくつか示します。

is_in における損失のない型変換

異なるデータ型に対して is_in 式を実行すると、Polars は以前は、その変換が損失を伴う場合でも、両方の型を共通のスーパータイプにキャストしていました。以下は、サイレントなデータ型不一致によって問題が発生する可能性のあるユーザー ID の例です。

# ユーザー ID が「フラグ付けされた」アカウント ID のリストに一致するかどうかを確認する
# (flagged_ids は JSON エクスポートからロードされ、大きな ID は浮動小数点数になっていました)
flagged_ids = pl.Series([9007199254740992.0])
user_id = pl.Series([9007199254740993])  # Int64 -> 異なる ID、1 だけずれている
user_id.is_in(flagged_ids)

2.0 より前では、user_id は flagged_ids に一致するために Float64 にキャストされていました。しかし、9007199254740993 は 2^53 (9007199254740992) を超えており、これは float64 が正確に表現できる最大の整数です。そのため、サイレントに 9007199254740992.0 に丸められ、誤って真陽性となりました。

2.0 では、InvalidOperationError: 'is_in' cannot check for Int64 values in List(Float64) data. というエラーが発生します。ユーザーは損失を伴う型変換を扱うために、明示的にキャストする必要があります。

厳格な連結

水平連結は、サイレントに null で埋めるのではなく、長さチェックを行うようになります。

# 日ごとのトランザクション数と日ごとの不正フラグ数を結合する
transactions = pl.DataFrame({"day": [1, 2, 3, 4, 5], "count": [120, 98, 143, 87, 156]})

# 5日目のアップストリームジョブがサイレントに失敗した
fraud_flags = pl.DataFrame({"flagged": [2, 0, 5, 1]})  # 4行のみ

pl.concat([transactions, fraud_flags], how="horizontal")
shape: (5, 2)
┌─────┬───────┬─────────┐
│ day ┆ count ┆ flagged │
│ 1   ┆ 120   ┆ 2       │
│ 2   ┆ 98    ┆ 0       │
│ 3   ┆ 143   ┆ 5       │
│ 4   ┆ 87    ┆ 1       │
│ 5   ┆ 156   ┆ null    │  <- 5日目のフラグカウントがサイレントに欠落
└─────┴───────┴─────────┘

2.0 では、次のエラーが発生します。

ShapeError: cannot concat dataframes with different heights in 'strict' mode

パディングが必要な場合は、how="horizontal_extend" を使用して明示的にオプトインする必要があります。これにより、意図が読み手に明確になります。

専用メソッド/コンストラクタへのキャストの削除

もう一つ言及する価値があるのは、曖昧なキャストや、専用の解析式で適用されるべきキャストの多くが削除され、データを解析する際の明確な方法が一つになったことです。

Enum/Categorical と整数の変換
pl.Series([None, 1, 0, 2], dtype=pl.UInt32).cast(pl.Enum(["a", "b", "c"]))
# ComputeError: casting from u32 to enum is not supported.

代わりに、整数 → カテゴリカルには .cat.to(dtype) を、カテゴリカル → 整数には .cat.physical() を使用します。

文字列から時間データ型への解析
pl.Series(["2022-08-30"]).cast(pl.Date)
# InvalidOperationError: casting from string to date is not supported.

代わりに .str.to_date() / .str.to_datetime() を使用します。これらは解析フォーマットを適用でき、データの解析方法をより細かく制御できます。

これらはほんの一例ですが、さらに多くの厳格さの改善が加えられました。すべては移行ガイドで確認できます。

情報量の多いエラーの表示

ユーザーまたはエージェントが、サポートされなくなった古いパラメータを使用したとしても処理を続行できるように、多大な努力を払いました。これに対処するために、2つの新しい型付き例外 polars.exceptions.AttributeRemovedError と polars.exceptions.ArgumentRemovedError を追加しました。これらはそれぞれ、削除された属性とメソッド、および削除されたパラメータを処理します。

エラーメッセージは、新しい API を指し示すはずです。以下に 2 つの例を示します。

>>> lf.melt(id_vars="a", value_vars="b")
polars.exceptions.AttributeRemovedError: `melt` was removed in version 2.0;
use `LazyFrame.unpivot` instead, with `index` instead of `id_vars`
and `on` instead of `value_vars`
>>> df.join(df, on="a", join_nulls=True)
polars.exceptions.ArgumentRemovedError: the argument 'join_nulls' for
'DataFrame.join' was deprecated in version 1.24 and has been removed
in 2.0.0. It was renamed to 'nulls_equal' in version 2.0.

削除された機能のほとんどは長らく非推奨となっており、最新の状態を保っていればパイプラインに影響を与えなかったはずです。もし依存していた機能が削除されたと思われる場合は、私たちにご連絡ください。

最後に

Polars 2.0 は、より良いデフォルト(最も重要なのはストリーミングエンジン)と、より良い API に関するものです。このリリースが比較的退屈なものであることを願っています。新しい機能はメジャーバージョンアップで隠すことはなく、準備ができ次第リリースしています。

誤解しないでください。Polars 2.x は 1.x よりもはるかに優れています。まだ公に話していない多くのことが進行中です。ストリーミングエンジンの適切なアウトオブコアサポート、新しい IO プラグイン設計、おそらく最速となる S3 リーダー、SQL カバレッジの大幅な改善、コストベースプランナー、ジョインの並べ替え、そして mmap の削除により、パイプラインはエンドツーエンドで完全に非同期になります。

pip install polars==2.0rc1 をインストールして、リリース候補版をお試しください。ぜひ試してみて、GitHub のイシュー または Discord でご連絡ください: https://discord.gg/4UfP5cfBE7。