Claude Media
Claude CodeでPolarsのデータ処理を書かせる勘所

Claude CodeでPolarsのデータ処理を書かせる勘所

Claude CodeにPolarsのデータ処理を書かせるときの勘所をまとめます。expressionとcontextの使い分け、lazy APIでの大規模データ対応、CLAUDE.mdでの規約指定を扱います。

Claude CodeにPolarsのコードを書かせると、pandasの感覚をそのまま持ち込んだコードが返ってくることがあります。構文としては動いても、Polars本来の速さを引き出せていないケースです。本稿では、expressionとcontextの使い分け、大きいデータでのlazy API、CLAUDE.mdでの規約指定という3つの軸で、Claude CodeにPolarsのデータ処理を書かせるときの勘所を扱います。

Claude CodeにPolarsを使わせる前に何を伝えておくか

Polarsは、Rust実装のクエリエンジンを持つDataFrameライブラリです。Pythonではpip install polarsだけで、lazy APIを含む機能一式が既定で有効になります。

pip install polars

Rust版は事情が違います。Cargo.tomlfeatureslazyを明示しないとlazy APIが使えず、Python版のように黙って全機能が入るわけではありません。ClaudeにRustコードを書かせる場合はこの差を伝えておかないと、Python向けの説明をそのまま流用したコードが依存関係エラーで止まります。

CLAUDE.mdは、セッションのたびに説明し直したくない事実を書いておく場所です。公式ドキュメントは、Claudeが同じ間違いを2度したときや、コードレビューでClaudeが知っておくべきだった点を指摘されたときを、CLAUDE.mdへの追記タイミングとして挙げています。Polarsを使うプロジェクトなら、pandas由来の書き方を避ける指示をあらかじめ書いておくと、依頼のたびに前提を説明し直す手間が減ります。

モノレポの一部だけがPolarsを使っている場合、規約をリポジトリ直下のCLAUDE.mdへ書くと他のディレクトリの作業にも常に読み込まれてしまいます。特定のパス配下にだけ規約を効かせたいなら、.claude/rules/でファイル種別ごとにルールを分ける方法が公式ドキュメントで案内されています。Polars関連のスクリプトを置くディレクトリだけに絞って規約を読み込ませたいときに使える選択肢です。

## データ処理の規約
 
- DataFrame処理はPolarsのlazy API(`pl.scan_*``.collect()`)を既定にする
- pandasの`df[df["col"] > 0]`のような書き方はしない。`.filter(pl.col("col") > 0)`で書く
- 10万行を超えるファイルは`.collect(engine="streaming")`を使う

生成したスクリプトをBashで実行させる場面では、SQLを書かせるときと同じ権限設計の注意が要ります。許可ルールとフックの使い分けはClaude Code settings.json完全ガイドにまとめてあります。

expressionとcontextの使い分けをClaude Codeに指示する

PolarsのAPIは、expressionとcontextという2つの概念で組み立てます。expressionは列に対する変換の手順そのもので、contextはそれを実際に計算させる場所です。この区別を伝えずに「Polarsでデータ処理を書いて」とだけ依頼すると、Claudeがどのcontextを選ぶかは指示の文脈次第でぶれます。

例として、ECサイトの注文データを使います。

import polars as pl
import datetime as dt
 
orders = pl.DataFrame(
    {
        "order_id": [1001, 1002, 1003, 1004, 1005],
        "customer": ["田中", "佐藤", "田中", "鈴木", "佐藤"],
        "category": ["食品", "雑貨", "食品", "食品", "雑貨"],
        "amount": [2400, 5800, 1200, 3600, 4200],
        "ordered_at": [
            dt.date(2026, 1, 15),
            dt.date(2026, 2, 3),
            dt.date(2026, 2, 20),
            dt.date(2026, 3, 5),
            dt.date(2026, 3, 18),
        ],
    }
)

selectは列を選び直したり計算したりするcontextです。

result = orders.select(
    pl.col("customer"),
    pl.col("ordered_at").dt.month().alias("month"),
    (pl.col("amount") * 1.1).round(0).alias("amount_with_tax"),
)

with_columnsselectと似ていますが、元の列を残したまま新しい列を追加します。既存の列に税込金額や月の列を足したいだけなら、こちらを使うようClaudeに指示したほうが結果が扱いやすくなります。

orders_enriched = orders.with_columns(
    month=pl.col("ordered_at").dt.month(),
    amount_with_tax=(pl.col("amount") * 1.1).round(0),
)

filterは条件に合う行だけを残すcontextです。

result = orders.filter(
    (pl.col("category") == "食品") & (pl.col("amount") > 2000)
)

group_byはグループごとの集計に使い、aggと組み合わせます。

result = orders.group_by("customer", maintain_order=True).agg(
    pl.len().alias("注文件数"),
    pl.col("amount").sum().alias("合計金額"),
)

集計した結果をファイルへ書き出す場面もよくあります。Polarsはcsv・json・parquetといった主要な形式の読み書きに対応しており、write_csvで書き出した内容はread_csvで読み返せます。

result.write_csv("summary.csv")
summary = pl.read_csv("summary.csv", try_parse_dates=True)

行数の多いデータを繰り返し読み書きするなら、csvより列指向のparquet形式(write_parquet / read_parquet)のほうが読み込みが速く、ファイルサイズも小さくなりやすい形式です。形式を指定せずに依頼すると結果はcsvになることが多いため、繰り返し使うデータならparquetを使うよう明示しておくと後工程が楽になります。

expression単体は抽象的な計算の手順にすぎず、contextに置いて初めて結果が計算されますpl.col("amount") * 1.1という式をそのままprintしても、計算済みのSeriesは返ってきません。Claudeがこの式だけを出力して「計算しました」と報告してきたら、contextが抜けている可能性を疑う価値があります。

大きいデータではlazy APIを使うようClaude Codeに指示する

Polarsのlazy APIは、コードを1行ずつ即座に実行せず、クエリ全体を最適化してから実行する仕組みです。公式ドキュメントは、lazy APIを使う理由として、クエリオプティマイザによる自動最適化、メモリに載らないデータをストリーミングで扱える点、データ処理前にスキーマの誤りを検出できる点の3つを挙げています。

pl.read_csvで読み込んで加工するeagerな書き方は、小さいファイルの確認にはそのままで十分です。ファイルが大きくなる、あるいは複数の変換を重ねるなら、pl.scan_csvで始めるlazyな書き方に切り替えるようClaudeに伝えます。

result = (
    pl.scan_csv("orders.csv")
    .filter(pl.col("category") == "食品")
    .group_by("customer")
    .agg(pl.col("amount").sum().alias("合計金額"))
    .collect(engine="streaming")
)

collect(engine="streaming")を指定すると、Polarsはデータをバッチに分けて処理し、メモリに一度に載らない量のファイルも扱えます。既定のcollect()はデータ全体を1バッチとして処理するため、ピーク時のメモリ使用量が手元の環境に収まるかを先に確認する必要があります。

クエリを開発している最中は、全件処理を待たずに構文の間違いへ早く気づきたい場面が多くなります。.head(n)を挟んでから.collect()する書き方を指示しておくと、Claudeが書いたクエリの検証が速くなります。

q = (
    pl.scan_csv("orders.csv")
    .head(100)
    .filter(pl.col("category") == "食品")
    .collect()
)

同じLazyFrameから複数の集計結果がほしいときは、pl.collect_allにまとめて渡す方法があります。個別に.collect()を呼ぶと、共通する前段の計算がそのたびにやり直されるためです。

lf = pl.scan_csv("orders.csv")
by_customer = lf.group_by("customer").agg(pl.col("amount").sum())
by_category = lf.group_by("category").agg(pl.col("amount").sum())
 
pl.collect_all([by_customer, by_category])

Polarsはcsvやparquetのようなファイル形式だけでなく、postgresやmysqlといったデータベースからも直接読み込めます。Claude CodeからMCP経由でデータベースに接続し、読み取り専用の権限で処理を任せたい場合の設定はデータベースMCPサーバーの読み取り専用設定を5製品で比較するで扱っています。

複数のデータフレームを組み合わせる

Polarsは複数のデータフレームを結合する仕組みも備えています。ここでは、結合(join)と連結(concat)という性格の違う2つの操作を扱います。

注文データに顧客マスタを結合する例です。

customers = pl.DataFrame(
    {
        "customer": ["田中", "佐藤", "鈴木"],
        "prefecture": ["東京都", "大阪府", "愛知県"],
        "member_since": [2021, 2019, 2023],
    }
)
 
joined = orders.join(customers, on="customer", how="left")

結合キーに指定した列の値が相手側で重複していると、結合後の行数がその分だけ増えます。集計の前にjoined.heightで行数を確認し、結合前のorders.heightと比較しておくと、意図しない行の増殖に実行後ではなく実行前後の差分で気づけます。

月ごとに分かれたCSVを1つにまとめるような場面では、結合ではなく連結を使います。

orders_march = pl.DataFrame(
    {
        "order_id": [1006, 1007],
        "customer": ["鈴木", "田中"],
        "category": ["雑貨", "食品"],
        "amount": [3100, 2800],
        "ordered_at": [dt.date(2026, 3, 25), dt.date(2026, 3, 29)],
    }
)
 
pl.concat([orders, orders_march], how="vertical")

Polarsは縦方向・横方向・対角方向の3種類の連結を用意しています。列構成が同じ複数ファイルを積み上げるなら縦方向、同じ行数のデータフレームを横に並べて列を増やすなら横方向というように、Claudeへの依頼では組み合わせたい向きを明示すると狙った形の結果になります。

Claude Codeが書いたPolarsコードで踏みやすいつまずき

Claude Codeが書いたPolarsコードには、pandas経験者が読むと違和感を覚えにくい分、見落としやすいつまずきがいくつかあります。構文エラーにならず実行できてしまうため、レビューで気づかないまま次の工程に進みやすい点が共通しています。

つまずき起きること避け方
expressionをcontextの外で評価しようとする起きることprintしても計算済みの値が出ない避け方selectfilterなどのcontextに必ず入れる
LazyFrameを複数箇所で使い回す起きること参照するたびに前段の計算がやり直される避け方pl.collect_allでまとめて評価する
group_byの順序をあてにする起きること実行のたびに行の並びが変わりうる避け方maintain_order=Trueを明示する
RustでPython向けの説明をそのまま使う起きることlazy API関連のビルドが通らない避け方Cargo.tomlfeatureslazyを足す

group_byの結果順序は既定では保証されません。集計結果を毎回同じ並びで確認したい場合や、後続の処理が行の順序に依存する場合は、maintain_order=Trueを付けるようClaudeへの指示に含めておくと事故を防げます。ただしこの引数はグルーピングの速度を落とすため、順序が問題にならない集計にまで機械的に付けさせる必要はありません。ダッシュボード表示のように並び順が結果に見える用途か、合計値だけを使う用途かで判断が分かれます。

使い分け早見表

どの書き方を選ぶべきかは、扱うデータの大きさと開発の段階によって変わります。

状況Claudeに書かせる形理由
小さいCSVをすぐ確認したいClaudeに書かせる形pl.read_csv(eager)理由クエリ最適化の恩恵が小さく、即座に結果を見たい場面に向く
メモリに載らない大きいファイルClaudeに書かせる形pl.scan_csv + .collect(engine="streaming")理由データをバッチ処理し、メモリ使用量を抑えられる
クエリを開発・デバッグしている最中Claudeに書かせる形.head(n)を挟んで.collect()理由全件処理を待たずに構文ミスへ気づける
同じLazyFrameから複数の集計がほしいClaudeに書かせる形pl.collect_all([...])理由共通の前段計算を1回にまとめ、二重計算を避けられる
順序が結果に影響する集計Claudeに書かせる形group_by(..., maintain_order=True)理由既定では保証されない行の並びを固定する

生成したPolarsコードを保存後にどう確認するか

生成されたコードをその場で信用せず、保存直後に自動でテストやlintを走らせる仕組みを作っておくと、Claudeが書いたPolarsコードにも同じ安心感が持てます。ファイル保存のたびに指定のコマンドを走らせるPostToolUseフックの設定手順はPostToolUse hookでツール実行後の後処理を自動化するにまとめています。

実行経路によって確認すべきポイントが変わるという考え方は、SQLや正規表現をClaudeに書かせるときと同じです。特定領域のコードをClaude Codeに書かせるときの勘所はClaude Code SQLと正規表現を安全に書かせる勘所でも扱っています。

まとめ

Claude CodeにPolarsのデータ処理を書かせるときの勘所は、pandasの感覚をそのまま持ち込まないことに集約されます。expressionはcontextに置いて初めて計算され、大きいデータはpl.scan_*.collect(engine="streaming")の組み合わせで扱います。結合や連結は向きを指定し、書き出す形式は繰り返し使うかどうかで選びます。この前提をCLAUDE.mdに書いておけば、依頼のたびに説明し直す手間が減り、Claudeが書くコードも狙った形に近づきます。

この記事を共有:XはてブLinkedIn