Polars チートシート

cheatsheet

1. データ読み込み/書き込み 💾

様々なフォーマットのデータを効率的に読み書きします。

目的 コード例 説明
CSV読み込み
import polars as pl

# 基本的な読み込み
df_csv = pl.read_csv("data.csv")

# オプション指定
df_csv_opts = pl.read_csv(
    "data.csv",
    separator=";",          # 区切り文字指定
    has_header=True,        # ヘッダー行の有無
    ignore_errors=True,     # エラー行を無視
    skip_rows=1,            # 先頭からスキップする行数
    n_rows=100,             # 読み込む最大行数
    columns=["col_a", "col_c"], # 読み込む列を指定
    new_columns=["A", "C"], # 読み込み時に列名を変更
    dtype={"col_a": pl.Int64, "col_b": pl.Utf8}, # データ型指定
    low_memory=True,       # メモリ使用量を抑える
    comment_prefix="#",     # コメント行の接頭辞
    null_values=["NA", "-999"], # NULLとみなす値
)

# LazyFrameとして読み込み (メモリ効率が良い)
lf_csv = pl.scan_csv("data.csv")
CSVファイルをDataFrameまたはLazyFrameとして読み込みます。多数のオプションで読み込みプロセスを制御できます。scan_csvは遅延評価のため、大規模データに適しています。
Parquet読み込み
# 基本的な読み込み
df_parquet = pl.read_parquet("data.parquet")

# 列選択と行フィルタリング (Predicate Pushdown)
df_parquet_filtered = pl.read_parquet(
    "data.parquet",
    columns=["id", "value"],
    row_index_name="row_num",
    row_index_offset=100
)

# LazyFrameとして読み込み
lf_parquet = pl.scan_parquet("data.parquet")

# 複数ファイル読み込み (Globパターン)
lf_multi_parquet = pl.scan_parquet("data_*.parquet")
Parquetファイルを効率的に読み込みます。列指向フォーマットのため、列選択やフィルタリング (Predicate Pushdown) が高速です。scan_parquetでLazyFrameとして扱えます。
JSON読み込み
# JSON Lines (行区切りJSON)
df_jsonl = pl.read_json("data.jsonl", json_lines=True)

# 標準的なJSON配列
df_json = pl.read_json("data.json")

# スキーマ指定読み込み
custom_schema = {"name": pl.Utf8, "age": pl.Int32, "city": pl.Categorical}
df_json_schema = pl.read_json("data.json", schema=custom_schema)
JSONまたはJSON Lines形式のファイルを読み込みます。json_lines=Trueで行区切りJSONに対応します。スキーマを事前に定義することも可能です。
Excel読み込み
# 単一シート読み込み
df_excel = pl.read_excel(
    "data.xlsx",
    sheet_name="Sheet1",
    # engine='xlsx2csv', # 代替エンジン (オプション)
    # engine='calamine', # Rust製高速エンジン (デフォルト)
    read_options={"header_row": 2, "skip_rows": 5} # エンジン固有オプション
)

# 全シート読み込み (辞書で返る)
all_sheets = pl.read_excel("data.xlsx", sheet_name=None)
df_sheet2 = all_sheets["Sheet2"]
Excelファイル (.xlsx) を読み込みます。シート名やエンジン、読み込みオプションを指定できます。sheet_name=Noneで全シートを読み込めます。
Arrow IPC読み込み
# Arrow IPC (Feather) ファイル読み込み
df_arrow = pl.read_ipc("data.arrow")

# メモリマップドファイルとして読み込み (Lazy)
lf_arrow_mmap = pl.scan_ipc("data.arrow", memory_map=True)
Apache Arrow IPC (Feather V1/V2) ファイルを高速に読み込みます。scan_ipcでLazyFrameとして、特にmemory_map=Trueでメモリ効率良く扱えます。
データベース読み込み
# ADBC経由 (例: PostgreSQL)
# pip install adbc_driver_postgresql
conn_str = "postgresql://user:password@host:port/database"
query = "SELECT id, name, value FROM large_table WHERE category = 'A'"

# 全件読み込み
df_db = pl.read_database(query=query, connection=conn_str)

# パーティション指定読み込み (並列化)
# (注意: partition_on は整数または日付/日時型である必要あり)
df_db_partitioned = pl.read_database(
    query="SELECT * FROM sales",
    connection=conn_str,
    partition_on="sale_date", # パーティション分割する列
    partition_num=10,        # パーティション数
)

# connectorx経由 (より多くのDBに対応)
# pip install connectorx
# conn_str_cx = "postgresql://user:password@host:port/database"
# query_cx = "SELECT * FROM events"
# df_cx = pl.read_sql(sql=query_cx, connection_uri=conn_str_cx) # 古いAPI, read_database を推奨
ADBC (Arrow Database Connectivity) または connectorx を利用してデータベースからデータを読み込みます。ADBCは比較的新しい標準で、Polarsとの親和性が高いです。read_databaseではパーティション指定による並列読み込みも可能です。
データ書き込み (各種フォーマット)
df = pl.DataFrame({"a": [1, 2, 3], "b": ["x", "y", "z"]})

# CSV書き込み
df.write_csv("output.csv", separator=",")

# Parquet書き込み
df.write_parquet(
    "output.parquet",
    compression="zstd", # 圧縮形式 (snappy, gzip, lz4, zstd, none)
    compression_level=6,
    statistics=True     # 統計情報の書き込み
)

# JSON Lines書き込み
df.write_json("output.jsonl", row_oriented=False) # row_oriented=False でJSON Lines

# Excel書き込み
df.write_excel("output.xlsx", worksheet="DataSheet")

# Arrow IPC書き込み
df.write_ipc("output.arrow", compression="zstd")

# LazyFrameの書き込み (Streaming)
lf = pl.scan_csv("large_data.csv")
lf.sink_parquet("output_from_lazy.parquet", compression="snappy")
lf.sink_ipc("output_from_lazy.arrow")
lf.sink_csv("output_from_lazy.csv")
# lf.sink_database(...) # ADBC経由でのDB書き込みも将来的に期待
DataFrameの内容を各種ファイルフォーマットで書き出します。LazyFrameの場合はsink_*メソッドを使用し、ストリーミング処理により大規模データもメモリ効率良く書き出せます。

2. データフレーム/シリーズの基本操作 🛠️

データフレームやシリーズの構造を確認したり、基本的な属性を変更します。

目的 コード例 説明
データフレーム作成
import numpy as np

# 辞書から作成
data_dict = {"colA": [1, 2, 3], "colB": ["a", "b", "c"]}
df_from_dict = pl.DataFrame(data_dict)

# リストのリストから作成 (スキーマ指定推奨)
data_list = [[1, "a"], [2, "b"], [3, "c"]]
df_from_list = pl.DataFrame(data_list, schema=["colA", "colB"])

# NumPy配列から作成
data_np = np.array([[1.0, 2.0], [3.0, 4.0]])
df_from_numpy = pl.DataFrame(data_np, schema=["f1", "f2"])

# Pandas DataFrameから作成
# import pandas as pd
# pdf = pd.DataFrame(...)
# df_from_pandas = pl.from_pandas(pdf)

# Seriesから作成
s1 = pl.Series("nums", [1, 2, 3])
s2 = pl.Series("strs", ["x", "y", "z"])
df_from_series = pl.DataFrame([s1, s2])
様々なデータソースからPolars DataFrameを作成します。辞書、リスト、NumPy配列、Pandas DataFrame、Polars Seriesなどから生成できます。
情報表示
# 先頭/末尾を表示
print(df.head(5))
print(df.tail(3))

# DataFrameの形状 (行数, 列数)
print(df.shape)

# 各列のデータ型
print(df.dtypes)

# スキーマ (列名とデータ型)
print(df.schema)

# 基本統計量
print(df.describe())

# null値の数
print(df.null_count())

# DataFrame全体の情報 (メモリ使用量など)
print(df) # または print(df.glimpse()) も便利
DataFrameの基本的な情報を表示します。データの中身、サイズ、データ型、統計量などを確認できます。
列名変更
# 特定の列名を変更
df_renamed = df.rename({"old_name": "new_name", "another_old": "another_new"})

# 全ての列名を変更 (関数を適用)
df_lower_case = df.rename(str.lower)
列名を変更します。辞書で個別に指定するか、関数を適用して一括で変更できます。
データ型変換
# 単一列の型変換
df_casted = df.with_columns(pl.col("colA").cast(pl.Float64))

# 複数列の型変換 (辞書を使用)
df_multi_casted = df.cast({"colA": pl.Int32, "colB": pl.Categorical})

# 条件付き型変換 (あまり一般的ではない)
# df_cond_cast = df.with_columns(
#     pl.when(pl.col("colC") == "X")
#       .then(pl.col("value").cast(pl.Int64))
#       .otherwise(pl.col("value").cast(pl.Float64))
#       .alias("value_casted")
# )

# 文字列を日時に変換 (strict=FalseでエラーをNoneに)
df_dt_casted = df.with_columns(
    pl.col("date_str").str.strptime(pl.Date, "%Y-%m-%d", strict=False)
)
列のデータ型を変換します。castメソッドや式 (Expression) 内のcastを使用します。文字列から日時への変換など、特定の変換用メソッドもあります。

3. データ選択 (Selection) 👀

DataFrameから特定の列や行を選択します。

目的 コード例 説明
列選択
# 単一列選択 (Seriesとして取得)
series_a = df["colA"]
series_b = df.get_column("colB")

# 複数列選択 (DataFrameとして取得)
df_subset1 = df.select(["colA", "colC"])
df_subset2 = df[["colA", "colC"]] # 短縮形

# 式を使った列選択/作成
df_selected_expr = df.select(
    pl.col("colA"),                         # 既存列
    (pl.col("colB") * 2).alias("colB_doubled"), # 計算結果を新しい列として
    pl.lit("constant").alias("info")        # 定数列を追加
)

# 正規表現で列選択
df_regex = df.select(pl.col("^col.*")) # "col"で始まる列

# データ型で列選択
df_by_dtype = df.select(pl.col(pl.Int64, pl.Float64)) # 整数と浮動小数点数

# 除外する列を指定
df_excluded = df.select(pl.exclude("colB", "colD")) # colBとcolD以外を選択
特定の列を選択します。列名を文字列やリストで指定する他、式 (Expression) を使って計算結果や定数、正規表現、データ型による選択も可能です。pl.excludeで除外指定もできます。
行選択 (フィルタリング)
# 条件に基づく行選択 (詳細はフィルタリングの章へ)
df_filtered = df.filter(pl.col("colA") > 10)

# インデックス番号で行選択 (Slice)
df_sliced = df.slice(offset=10, length=20) # 10行目から20行分

# 先頭/末尾の行を選択
df_head = df.head(10)
df_tail = df.tail(5)

# ランダムに行選択
df_sampled_n = df.sample(n=100) # 100行をランダム抽出
df_sampled_frac = df.sample(fraction=0.1) # 10%をランダム抽出
特定の行を選択します。条件式 (filter)、インデックス範囲 (slice)、先頭/末尾 (head/tail)、ランダムサンプリング (sample) などがあります。
特定の要素へのアクセス
# 特定の行を取得 (インデックス指定) - DataFrameとして返る
row_df = df.row(index=5)

# 特定の行を取得 (負のインデックスも可)
last_row_df = df.row(index=-1)

# 特定の要素 (セル) の値を取得
# 注意: パフォーマンスが重要なループ内での使用は非推奨
value = df.item(row=2, column="colA") # 3行目、'colA'列の値
value_by_idx = df.item(row=0, column=1) # 1行目、2列目の値
特定の行やセルの値にアクセスします。row()は指定したインデックスの行をDataFrameとして返します。item()は特定のセルの値を直接取得しますが、頻繁な呼び出しは遅くなる可能性があります。

4. データフィルタリング (Filtering) 🔍

条件に基づいてDataFrameの行を抽出します。

目的 コード例 説明
単一条件
# 数値比較
df_gt = df.filter(pl.col("value") > 100)
df_le = df.filter(pl.col("age") <= 30)
df_eq = df.filter(pl.col("id") == "abc")
df_ne = df.filter(pl.col("status") != "completed")

# 真偽値列によるフィルタリング
df_bool = df.filter(pl.col("is_active")) # is_activeがTrueの行
単一の条件式に基づいて行をフィルタリングします。pl.col("列名")で列を参照し、比較演算子 (>, <=, ==, !=) を使用します。
複数条件 (AND, OR, NOT)
# AND条件 (&)
df_and = df.filter((pl.col("value") > 100) & (pl.col("category") == "A"))

# OR条件 (|)
df_or = df.filter((pl.col("status") == "pending") | (pl.col("priority") > 5))

# NOT条件 (~)
df_not = df.filter(~pl.col("is_processed")) # is_processedがFalseの行

# 複雑な組み合わせ
df_complex = df.filter(
    (pl.col("age") > 18) &
    ((pl.col("city") == "Tokyo") | (pl.col("city") == "Osaka")) &
    ~(pl.col("department") == "HR")
)
複数の条件を組み合わせてフィルタリングします。& (AND), | (OR), ~ (NOT) を使用します。括弧 () を使って評価順序を制御します。
リスト内/外の要素
target_cities = ["Tokyo", "Kyoto", "Fukuoka"]
df_is_in = df.filter(pl.col("city").is_in(target_cities))

excluded_ids = [101, 205, 300]
df_is_not_in = df.filter(pl.col("user_id").is_not_in(excluded_ids))
指定したリストに列の値が含まれるか (is_in)、含まれないか (is_not_in) でフィルタリングします。
Null値のチェック
# Nullである行を抽出
df_is_null = df.filter(pl.col("email").is_null())

# Nullでない行を抽出
df_is_not_null = df.filter(pl.col("phone_number").is_not_null())
列の値がNullか (is_null)、Nullでないか (is_not_null) でフィルタリングします。
文字列の部分一致など
# 特定の文字列を含む
df_contains = df.filter(pl.col("product_name").str.contains("Special"))

# 特定の文字列で始まる
df_starts_with = df.filter(pl.col("code").str.starts_with("PROD-"))

# 特定の文字列で終わる
df_ends_with = df.filter(pl.col("file_name").str.ends_with(".csv"))

# 正規表現に一致
df_regex_match = df.filter(pl.col("comment").str.contains(r"error|failed", literal=False))
文字列型の列に対して、部分一致 (str.contains)、前方一致 (str.starts_with)、後方一致 (str.ends_with)、正規表現一致 (str.containsliteral=Falseまたはstr.extract) などでフィルタリングします。

5. データ変換 (Transformation) 🔄

列の追加、変更、削除、値の置換、欠損値処理など、データを加工します。

目的 コード例 説明
新しい列の追加/既存列の変更
# 新しい列を追加 (既存列から計算)
df_with_new_col = df.with_columns(
    (pl.col("price") * pl.col("quantity")).alias("total_sales")
)

# 複数の新しい列を追加
df_with_multi_cols = df.with_columns([
    (pl.col("colA") + pl.col("colB")).alias("sum_AB"),
    (pl.col("colC") / 10).alias("colC_div_10"),
    pl.lit(1).alias("constant_one")
])

# 既存の列を上書き (同じ列名を指定)
df_overwrite_col = df.with_columns(
    (pl.col("value") * 1.1).alias("value") # value列を1.1倍して上書き
)

# 条件に基づき新しい列を追加 (when/then/otherwise)
df_with_conditional = df.with_columns(
    pl.when(pl.col("score") >= 60)
      .then(pl.lit("Pass"))
      .otherwise(pl.lit("Fail"))
      .alias("result")
)
with_columnsを使用して新しい列を追加したり、既存の列の値を変更したりします。複数の式をリストで渡すことで、一度に複数の列を操作できます。既存の列名と同じエイリアスを指定すると、列が上書きされます。when/then/otherwise構文で条件分岐も可能です。
列の削除
# 単一列の削除
df_dropped_single = df.drop("temporary_col")

# 複数列の削除
df_dropped_multi = df.drop(["col_to_remove1", "col_to_remove2"])

# 存在しない列を削除しようとしてもエラーにならない (デフォルト)
# df.drop("non_existent_col", strict=False) # strict=Trueにするとエラー
dropメソッドで不要な列を削除します。列名を文字列またはリストで指定します。
欠損値 (Null) 処理
# 特定の値でNullを埋める (列ごと)
df_filled_specific = df.with_columns(
    pl.col("numeric_col").fill_null(0),
    pl.col("string_col").fill_null("Unknown")
)

# 全ての数値列を平均値で埋める
df_filled_mean = df.with_columns(
    pl.col(pl.NUMERIC_DTYPES).fill_null(pl.mean(pl.col(pl.NUMERIC_DTYPES)))
)

# 前/後の値でNullを埋める (Forward/Backward Fill)
df_ffill = df.with_columns(pl.col("sensor_reading").forward_fill())
df_bfill = df.with_columns(pl.col("sensor_reading").backward_fill())

# Nullを含む行を削除
df_dropped_nulls_any = df.drop_nulls() # いずれかの列にNullがあれば削除

# 特定の列にNullがある行を削除
df_dropped_nulls_subset = df.drop_nulls(subset=["user_id", "email"])
欠損値 (Null) を特定の値 (fill_null)、前後の値 (forward_fill, backward_fill)、または統計量 (平均値など) で補完します。drop_nullsでNullを含む行を削除することもできます。
文字列操作
df_str_ops = df.with_columns([
    pl.col("text").str.to_lowercase().alias("lower_text"),
    pl.col("text").str.replace("old", "new").alias("replaced_text"),
    pl.col("full_name").str.split(" ").alias("name_parts"), # List(Utf8)になる
    pl.col(" padded ").str.strip_chars().alias("stripped_text"),
    pl.col("log_message").str.extract(r"user_id=(\d+)", group_index=1).cast(pl.Int64).alias("user_id_from_log"),
    pl.col("url").str.count_matches(r"/").alias("slash_count")
])
str名前空間以下のメソッドを使って文字列を操作します。小文字化、置換、分割、空白除去、正規表現による抽出、パターンカウントなどが可能です。
日時操作
df_dt_ops = df.with_columns([
    pl.col("timestamp").dt.year().alias("year"),
    pl.col("timestamp").dt.month().alias("month"),
    pl.col("timestamp").dt.weekday().alias("weekday"), # 1=月曜, 7=日曜
    pl.col("timestamp").dt.strftime("%Y-%m-%d %H:%M").alias("formatted_dt"),
    pl.col("date").dt.offset_by("3d").alias("date_plus_3d"), # 3日加算
    pl.col("datetime").dt.truncate("1h").alias("hourly_trunc"), # 時間単位で切り捨て
    (pl.col("end_time") - pl.col("start_time")).alias("duration") # 時間差 (Duration型)
])
dt名前空間以下のメソッドを使って日時データを操作します。年、月、曜日などの要素抽出、フォーマット変換、日付/時間の加減算 (offset_by)、丸め処理 (truncate)、時間差計算などが可能です。
リスト操作
# 前提: dfに 'list_col': [[1, 2], [3, 4, 5], [6]] のような列がある場合
df_list_ops = df.with_columns([
    pl.col("list_col").list.len().alias("list_length"),
    pl.col("list_col").list.sum().alias("list_sum"),
    pl.col("list_col").list.mean().alias("list_mean"),
    pl.col("list_col").list.get(0).alias("first_element"), # インデックスで要素取得
    pl.col("list_col").list.join("-").alias("joined_str"), # 文字列リストの場合
    pl.col("list_col").list.contains(3).alias("contains_3"),
    pl.col("list_col").list.unique().alias("unique_elements")
])
list名前空間以下のメソッドを使ってリスト型の列を操作します。リストの長さ、合計、平均、要素へのアクセス、結合、要素の存在確認、ユニークな要素の取得などが可能です。explodeでリスト要素を行に展開することもよく使われます。
適用関数 (Apply/Map) ⚠️
# apply: Seriesを受け取りSeriesを返す関数 (比較的遅い)
def custom_function(series: pl.Series) -> pl.Series:
    # 何らかの複雑な処理
    return series * 2 + 5

df_apply = df.with_columns(
    pl.col("numeric_col").apply(custom_function).alias("applied_col")
)

# map_elements: 要素ごとに関数を適用 (さらに遅いが柔軟)
def element_wise_func(x):
    if x > 10:
        return "high"
    elif x > 5:
        return "medium"
    else:
        return "low"

df_map = df.with_columns(
    pl.col("numeric_col").map_elements(element_wise_func, return_dtype=pl.Utf8).alias("mapped_col")
)
Pythonのカスタム関数を適用します。applyはSeries全体に、map_elementsは要素ごとに適用します。Polarsの組み込み関数や式で表現できる場合はそちらの方が圧倒的に高速なため、これらの使用は最終手段と考えるべきです。パフォーマンスへの影響に注意してください。

6. データ集約 (Aggregation) 📊

データをグループ化し、各グループに対して集約計算(合計、平均、カウントなど)を行います。

目的 コード例 説明
グループ化と集約 (GroupBy)
# 単一列でグループ化し、単一の集約
df_grouped_sum = df.group_by("category").agg(
    pl.sum("value").alias("total_value")
)

# 複数列でグループ化
df_grouped_multi_keys = df.group_by(["category", "region"]).agg(
    pl.mean("sales").alias("average_sales")
)

# 複数の集約
df_grouped_multi_aggs = df.group_by("product_id").agg([
    pl.sum("quantity").alias("total_quantity"),
    pl.mean("price").alias("average_price"),
    pl.count().alias("num_transactions"), # グループごとの行数
    pl.n_unique("customer_id").alias("unique_customers"),
    pl.first("timestamp").alias("first_transaction"),
    pl.last("timestamp").alias("last_transaction"),
    pl.quantile("rating", 0.5).alias("median_rating"),
    pl.std("score").alias("std_dev_score"),
    pl.col("tags").flatten().unique().alias("all_unique_tags") # list列を集約
])

# 全ての列に対して集約 (例: 数値列の合計)
df_agg_all_numeric = df.agg(pl.sum(pl.col(pl.NUMERIC_DTYPES)))
group_by()でグループ化するキーを指定し、agg()で集約処理を定義します。agg()には単一の式または式のリストを渡します。pl.sum, pl.mean, pl.count, pl.n_unique, pl.first, pl.last, pl.median, pl.quantile, pl.std, pl.var など、多数の集約関数が利用可能です。グループ化キーを指定しないagg()は、DataFrame全体に対して集約を行います。
条件付き集約
# 条件を満たす行のみを集約対象にする
df_conditional_agg = df.group_by("user_id").agg(
    pl.sum("amount").filter(pl.col("type") == "purchase").alias("total_purchase_amount"),
    pl.count().filter(pl.col("status") == "completed").alias("completed_count")
)

# when/thenを使った集約 (例: 特定カテゴリの合計、それ以外は0)
# df_when_agg = df.group_by("id").agg(
#     pl.sum(
#         pl.when(pl.col("category") == "A")
#           .then(pl.col("value"))
#           .otherwise(0)
#     ).alias("value_sum_cat_A")
# ) # この方法は非効率な場合あり。filterの方が推奨されることが多い。
集約式内でfilter()を使うことで、特定の条件を満たす行だけを集約対象とすることができます。
ピボット (Pivot)
# 指定した列の値を行から列に変換
df_pivoted = df.pivot(
    index="date",      # 行に残す列
    columns="product", # 列に展開する値を持つ列
    values="sales",    # 集約する値の列
    aggregate_function="sum" # 集約関数 (sum, first, min, max, mean, median, countなど)
)
pivot()メソッドは、指定した列 (columns) のユニークな値を新しい列名とし、対応する値 (values) を集約関数 (aggregate_function) で集計して、データを整形します。いわゆる「ロング形式」から「ワイド形式」への変換です。
動的/ローリング集約 (時系列)
# 時間ベースのウィンドウ集約 (Dynamic GroupBy)
# 前提: dfは日時列 'ts' でソート済み
df_dynamic_agg = df.group_by_dynamic(
    index_column="ts",        # 時間インデックス列
    every="1h",               # ウィンドウの頻度 (1時間ごと)
    period="3h",              # ウィンドウの期間 (過去3時間)
    offset="-1h",             # ウィンドウの開始オフセット
    group_by=["category"]     # 追加のグループ化キー (オプション)
).agg(
    pl.sum("value").alias("sum_value_3h_window")
)

# 行ベースのローリングウィンドウ集約 (Rolling GroupBy)
# 前提: dfは 'id' でグループ化し、'order' でソート済みと仮定
df_rolling_agg = df.group_by("id").agg(
    pl.col("value").rolling_mean(window_size=3).alias("rolling_mean_3")
    # rolling_sum, rolling_std, rolling_min, rolling_max, etc.
)
# または window関数 (over) を使用
df_rolling_over = df.with_columns(
    pl.col("value").rolling_mean(window_size=3).over("id").alias("rolling_mean_3_over")
)
group_by_dynamicは時系列データに対して、時間ベースのウィンドウ(例: 1時間ごと、過去3時間分)で集約を行います。group_by_rolling(またはover()と組み合わせたローリング関数)は、行数ベースの移動ウィンドウ(例: 直近3行)で集約を行います。時系列分析や特徴量エンジニアリングで頻繁に使用されます。

7. データ結合 (Joining) 🔗

複数のDataFrameを特定のキーに基づいて結合します。

結合の種類 コード例 説明
内部結合 (Inner Join)
df_left = pl.DataFrame({"key": ["a", "b", "c"], "val_l": [1, 2, 3]})
df_right = pl.DataFrame({"key": ["b", "c", "d"], "val_r": [4, 5, 6]})

# 単一キーでの結合
inner_join = df_left.join(df_right, on="key", how="inner")
# 結果: key | val_l | val_r
#       b   | 2     | 4
#       c   | 3     | 5
両方のDataFrameに存在するキーの行のみを結合します。
左結合 (Left Join)
left_join = df_left.join(df_right, on="key", how="left")
# 結果: key | val_l | val_r
#       a   | 1     | null
#       b   | 2     | 4
#       c   | 3     | 5
左側のDataFrameのすべての行を保持し、右側のDataFrameから一致するキーのデータを結合します。一致しない場合はNullが入ります。
外部結合 (Outer Join)
outer_join = df_left.join(df_right, on="key", how="outer")
# 結果: key | val_l | val_r
#       a   | 1     | null
#       b   | 2     | 4
#       c   | 3     | 5
#       d   | null  | 6
両方のDataFrameのすべての行を保持し、一致するキーでデータを結合します。どちらか一方にしか存在しないキーの行も含まれ、対応する値はNullになります。
クロス結合 (Cross Join)
cross_join = df_left.join(df_right, how="cross")
# 結果: 左の各行と右の各行の全ての組み合わせ (3行 * 3行 = 9行)
#       key | val_l | key_right | val_r
#       a   | 1     | b         | 4
#       a   | 1     | c         | 5
#       ...
左側のDataFrameの各行と右側のDataFrameの各行のすべての組み合わせを作成します (デカルト積)。結合キーは使用しません。結果の行数は M * N になります。
半結合 (Semi Join)
semi_join = df_left.join(df_right, on="key", how="semi")
# 結果: 左のDFのうち、右のDFにキーが存在するものだけ
#       key | val_l
#       b   | 2
#       c   | 3
左側のDataFrameから、右側のDataFrameに一致するキーが存在する行のみを選択します。右側のDataFrameの列は結果に含まれません。フィルタリングのように機能します。
反結合 (Anti Join)
anti_join = df_left.join(df_right, on="key", how="anti")
# 結果: 左のDFのうち、右のDFにキーが存在しないものだけ
#       key | val_l
#       a   | 1
左側のDataFrameから、右側のDataFrameに一致するキーが存在しない行のみを選択します。右側のDataFrameの列は結果に含まれません。
非等価時間結合 (As-of Join)
trades = pl.DataFrame({
    "time": pl.datetime_range(start=..., end=..., interval="3s", eager=True),
    "ticker": ["A", "B", "A", "C", "B"],
    "price": [10.1, 20.0, 10.2, 30.5, 20.1]
}).sort("time")
quotes = pl.DataFrame({
    "time": pl.datetime_range(start=..., end=..., interval="5s", eager=True),
    "ticker": ["A", "B", "A", "B", "C"],
    "bid": [10.0, 19.9, 10.1, 20.0, 30.4],
    "ask": [10.2, 20.1, 10.3, 20.2, 30.6]
}).sort("time")

# 各trade時点での直近のquoteを結合 (前方一致)
asof_join = trades.join_asof(quotes, on="time", by="ticker", strategy="forward")
# strategy='backward' (デフォルト) で直前のquote
# strategy='nearest' で最も近いquote
# tolerance="10s" で時間差の許容範囲を指定
主に時系列データで使用され、キー列(通常は時間)が完全一致しなくても、指定された許容範囲 (tolerance) や戦略 (strategy: ‘forward’, ‘backward’, ‘nearest’) に基づいて最も近いレコードを結合します。byで追加の完全一致キーを指定できます。
複数キー/異なるキー名での結合
df_l_multi = pl.DataFrame({"id1": [1, 1, 2], "id2": ["a", "b", "a"], "val_l": [10, 11, 12]})
df_r_multi = pl.DataFrame({"id1_r": [1, 2, 2], "id2_r": ["b", "a", "c"], "val_r": [20, 21, 22]})

# 複数キーで結合 (列名が同じ場合)
# join = df_l_multi.join(df_r_multi, on=["id1", "id2"], how="inner") # 列名が違うのでエラー

# 異なる列名で結合
join_diff_names = df_l_multi.join(
    df_r_multi,
    left_on=["id1", "id2"],
    right_on=["id1_r", "id2_r"],
    how="inner"
)
onにリストで複数の列名を指定することで、複数キーでの結合が可能です。左右のDataFrameでキー列名が異なる場合は、left_onright_onを使用します。
結合後の列名重複処理
df_l = pl.DataFrame({"key": [1], "value": [10]})
df_r = pl.DataFrame({"key": [1], "value": [20]})

# デフォルトでは右側の重複列名に "_right" が付与される
join_suffix = df_l.join(df_r, on="key", how="inner")
# 結果: key | value | value_right
#       1   | 10    | 20

# suffix を指定
join_custom_suffix = df_l.join(df_r, on="key", how="inner", suffix="_other")
# 結果: key | value | value_other
#       1   | 10    | 20
結合キー以外の列名が左右のDataFrameで重複する場合、デフォルトでは右側の列名に_rightという接尾辞が付与されます。suffix引数でこの接尾辞をカスタマイズできます。

8. Lazy API 😴➡️⚡

遅延評価API (Lazy API) は、クエリの最適化とメモリ効率の高い処理を可能にします。特に大規模データセットで強力です。

目的 コード例 説明
LazyFrameの作成
# ファイルからLazyFrameを作成 (推奨)
lf_csv = pl.scan_csv("data.csv")
lf_parquet = pl.scan_parquet("data_*.parquet") # Globパターンも利用可能
lf_ipc = pl.scan_ipc("data.arrow")
# lf_delta = pl.scan_delta("path/to/delta_table") # Delta Lake 対応

# Eager DataFrameから変換
df_eager = pl.DataFrame(...)
lf_from_eager = df_eager.lazy()
scan_*系の関数を使うと、ファイルを直接読み込まずにLazyFrameを作成できます。これにより、メモリに乗り切らないデータも扱えます。既存のEager DataFrameは.lazy()メソッドでLazyFrameに変換できます。
操作の連鎖 (Chaining)
lf = pl.scan_csv("sales.csv")

query = (
    lf.filter(pl.col("product_category") == "Electronics")
    .with_columns((pl.col("price") * pl.col("quantity") * (1 - pl.col("discount"))).alias("revenue"))
    .group_by("region")
    .agg(
        pl.sum("revenue").alias("total_revenue"),
        pl.count().alias("transaction_count")
    )
    .sort("total_revenue", descending=True)
    .limit(5)
)
# この時点では計算は実行されていない
LazyFrameに対して行う操作 (filter, with_columns, group_by, agg, sort, limitなど) は、すぐには実行されません。操作は論理的なクエリプランとして記録されます。
実行計画の表示
# 最適化前の論理プラン
print(query.describe_plan())

# Polarsオプティマイザによる最適化後の物理プラン
print(query.describe_optimized_plan())
describe_plan()でユーザーが記述した操作の論理プランを、describe_optimized_plan()でPolarsが最適化(例: Predicate Pushdown, Projection Pushdown, 並列化など)を行った後の実行プランを確認できます。これにより、どのような最適化が行われたかを理解できます。
計算の実行と結果取得
# 全ての計算を実行し、結果をEager DataFrameとしてメモリにロード
result_df = query.collect()

# 計算を実行し、最初のN行だけをEager DataFrameとして取得 (デバッグ等に便利)
result_fetch = query.fetch(n_rows=10)

# 計算を実行し、結果をファイルに書き出す (ストリーミング)
# メモリに全結果をロードしないため、巨大な結果も扱える
query.sink_parquet("top5_electronics_revenue.parquet")
query.sink_csv("top5_electronics_revenue.csv")
query.sink_ipc("top5_electronics_revenue.arrow")

# ストリーミング実行 (メモリ効率重視)
# collect(streaming=True) はイテレータを返す (実験的機能)
# for batch_df in query.collect(streaming=True):
#     process_batch(batch_df)
collect()を呼び出すと、記録されたクエリプランが最適化され、計算が実行されます。結果はEager DataFrameとして返されます。fetch(n)は最初のn行だけを計算して返します。sink_*()メソッドは、計算結果をメモリにロードせずに直接ファイルに書き出すため、メモリ使用量を大幅に削減できます (ストリーミング書き込み)。collect(streaming=True) は結果をバッチ処理するためのイテレータを返しますが、まだ実験的な機能です。
Lazy APIのメリット ✨
  • 🧠 **クエリ最適化:** Predicate Pushdown(データソースでのフィルタリング)、Projection Pushdown(不要な列の読み込み回避)、共通部分式の削除、並列実行計画など、自動的に効率的な実行プランが生成されます。
  • 💾 **メモリ効率:** データ全体をメモリにロードする必要がなく、ストリーミング処理(scan_*, sink_*)により、メモリサイズを超えるデータセットも処理可能です。
  • 🔗 **コードの可読性:** 操作を連鎖させて記述できるため、複雑な処理も段階的に記述しやすくなります。

9. 式 (Expressions) EXPRESS YOURSELF 😎

Polarsの操作の中心となる「式 (Expression)」は、データに対する変換、計算、条件分岐などを宣言的に記述する方法です。select, with_columns, filter, agg など多くのメソッドで使用されます。

式の種類/機能 コード例 説明
列の参照
pl.col("column_name") # 名前で参照
pl.col(pl.Int64)     # データ型で参照 (複数列にマッチする可能性あり)
pl.col("^prefix.*")  # 正規表現で参照
操作対象の列を指定します。名前、データ型、正規表現で指定できます。
リテラル (定数)
pl.lit(100)
pl.lit("hello")
pl.lit(None)
pl.lit(date(2023, 1, 1))
固定値を式として扱います。計算や新しい列の作成に使用します。
算術演算
pl.col("a") + pl.col("b")
pl.col("price") * 1.1
pl.col("score") / pl.lit(10)
pl.col("count") % 2
列やリテラルに対して四則演算や剰余演算を行います。
比較演算
pl.col("age") > 18
pl.col("status") == pl.lit("active")
pl.col("value") != 0
列やリテラルを比較し、Boolean型のSeriesを返します。filterでよく使用されます。
論理演算
(pl.col("a") > 0) & (pl.col("b") < 10) # AND
(pl.col("c") == 1) | (pl.col("d") == 2) # OR
~(pl.col("e").is_null())             # NOT
Boolean型の式を組み合わせます。filterwhen/thenで使用されます。
エイリアス (別名)
(pl.col("price") * pl.col("qty")).alias("total")
pl.sum("sales").alias("total_sales")
計算結果や集約結果の列に新しい名前を付けます。with_columnsaggで必須です。
条件分岐 (When/Then/Otherwise)
pl.when(pl.col("score") >= 60)
  .then(pl.lit("Pass"))
  .otherwise(pl.lit("Fail"))
  .alias("result")

pl.when(pl.col("value") > 100).then(pl.col("value") * 0.9) # 条件一致時のみ処理
  .when(pl.col("value") < 0).then(pl.lit(0))             # 複数のwhenを連鎖可能
  .otherwise(pl.col("value"))                           # デフォルト値
  .alias("adjusted_value")
SQLのCASE WHENに似た条件分岐を実現します。複数のwhen().then()を繋げ、最後にotherwise()でどの条件にも一致しなかった場合の値を指定します。
集約関数
pl.sum("sales")
pl.mean("temperature")
pl.count("user_id") # 列名を指定すると非Nullの数をカウント
pl.count()          # 引数なしでグループの行数をカウント
pl.median("response_time")
pl.n_unique("product_code")
aggメソッド内で使用し、グループ化されたデータまたはDataFrame全体を集約します。
ウィンドウ関数
# グループ内で順位付け
pl.col("sales").rank(method="ordinal").over("category").alias("rank_in_category")

# グループ内の累積和
pl.sum("amount").over("user_id", mapping_strategy="explode").alias("cumulative_amount") # Polars 0.20+

# 前の行の値を取得 (ラグ)
pl.col("value").shift(1).over("group_key").alias("previous_value")

# 移動平均
pl.col("price").rolling_mean(window_size=5).over("stock_id").alias("moving_avg_5")
over()と組み合わせて使用し、グループ内での相対的な計算(ランク、累積和、移動平均など)を行います。group_by().agg()とは異なり、元の行数は保持されます。
型キャスト
pl.col("id_str").cast(pl.Int64)
pl.col("amount").cast(pl.Float32, strict=False) # strict=FalseでキャストエラーをNullに
列のデータ型を変換します。
名前空間 (str, dt, list, struct)
pl.col("text").str.to_uppercase()
pl.col("timestamp").dt.hour()
pl.col("items").list.len()
pl.col("coords").struct.field("x")
特定のデータ型(文字列、日時、リスト、構造体)に対する操作をまとめたものです。例えば.str.の後に文字列操作メソッドが続きます。

10. ユーティリティ ⚙️

メモリ管理、設定、他ライブラリとの連携など、便利な機能群です。

目的 コード例 説明
メモリ使用量確認
# DataFrameのおおよそのメモリ使用量 (バイト単位)
mem_usage = df.estimated_size("mb") # "kb", "gb" も可能
print(f"DataFrame memory usage: {mem_usage:.2f} MB")
DataFrameが消費しているおおよそのメモリ量を確認します。デバッグやパフォーマンスチューニングに役立ちます。
設定変更
# 表示する行数/列数を変更
pl.Config.set_tbl_rows(20)
pl.Config.set_tbl_cols(15)

# 文字列カラムの表示幅
pl.Config.set_fmt_str_lengths(50)

# 浮動小数点数の表示フォーマット
pl.Config.set_float_precision(4)

# テーブルの整形オプション (セル内改行など)
# pl.Config.set_tbl_formatting("ASCII_FULL_CONDENSED")

# LazyFrameの最適化設定 (上級者向け)
# pl.Config.set_streaming_chunk_size(1_000_000)
# pl.Config.set_common_subplan_elimination(False)

# 設定を一時的に変更 (with文)
with pl.Config(tbl_rows=5, tbl_cols=5):
    print(df.head(10)) # このブロック内でのみ設定が有効

# 環境変数による設定 (起動時に設定)
# POLARS_MAX_THREADS=4 # 使用する最大スレッド数
# POLARS_VERBOSE=1     # 詳細なログ出力
pl.Configを通じて、Polarsの挙動(表示設定、最適化パラメータなど)をカスタマイズします。with pl.Config(...)で一時的な設定変更も可能です。一部設定は環境変数でも制御できます。
NumPy連携
import numpy as np

# Polars Series/DataFrameをNumPy配列に変換
numpy_array_s = df["numeric_col"].to_numpy()
numpy_array_df = df.to_numpy() # 2次元配列になる

# NumPy配列からPolars DataFrame/Seriesを作成
s_from_np = pl.Series("my_series", np.arange(10))
df_from_np = pl.from_numpy(np_array, schema=["c1", "c2"])
to_numpy()でNumPy配列に変換し、pl.from_numpy()pl.Series()コンストラクタでNumPy配列からPolarsオブジェクトを作成できます。ゼロコピーが可能な場合もあります。
Pandas連携
import pandas as pd

# Polars DataFrameをPandas DataFrameに変換
pandas_df = df.to_pandas(use_pyarrow_extension_array=True)
# use_pyarrow_extension_array=True は Nullable型やネスト型を効率的に扱う

# Pandas DataFrameからPolars DataFrameを作成
df_from_pandas = pl.from_pandas(pandas_df)
to_pandas()でPandas DataFrameに、pl.from_pandas()でPolars DataFrameに相互変換できます。Arrowフォーマットを介して効率的に変換されます。
Apache Arrow連携
import pyarrow as pa

# Polars DataFrame/SeriesをArrow Table/Arrayに変換 (ゼロコピー)
arrow_table = df.to_arrow()
arrow_array = df["colA"].to_arrow()

# Arrow Table/ArrayからPolars DataFrame/Seriesを作成 (ゼロコピー)
df_from_arrow_table = pl.from_arrow(arrow_table)
s_from_arrow_array = pl.from_arrow(arrow_array)
Polarsは内部的にArrowメモリフォーマットを使用しているため、to_arrow()pl.from_arrow()による相互変換は非常に高速で、多くの場合メモリコピーが発生しません (ゼロコピー)。他のArrow対応ライブラリとの連携に便利です。

⚠️ 注意:

  • バージョン依存: Polarsは活発に開発されており、バージョンによってAPIや挙動が変更される可能性があります。最新のドキュメントを確認することをお勧めします。
  • パフォーマンス: applymap_elementsは、Polarsの組み込み関数や式で代替できない場合にのみ使用を検討してください。可能な限り、式ベースの操作を利用することで最高のパフォーマンスが得られます。
  • Lazy API推奨: 特にデータサイズが大きい場合や複雑な処理を行う場合は、メモリ効率と最適化の観点からLazy API (scan_*, .lazy(), collect()/sink_*()) の利用を強く推奨します。

コメント

タイトルとURLをコピーしました