Skip to content

コンテキスト

Polarsは、データ変換のための独自のドメイン固有言語(DSL)を開発しました。このDSLは非常に使いやすく、人間が読みやすい上に、複雑なクエリを可能にします。この言語の2つの主要な構成要素は、コンテキストとエクスプレッションです。後者については次のセクションで説明します。

名前が示すように、コンテキストは、エクスプレッションを評価するために考慮すべき条件や関係を表します。主なコンテキストは以下の3 つです1

  1. 選択: df.select(...), df.with_columns(...)
  2. フィルタリング: df.filter()
  3. グループ化 / 集計: df.group_by(...).agg(...)

以下の例は、次の DataFrame で実行されます:

DataFrame

df = pl.DataFrame(
    {
        "nrs": [1, 2, 3, None, 5],
        "names": ["foo", "ham", "spam", "egg", None],
        "random": np.random.rand(5),
        "groups": ["A", "A", "B", "C", "B"],
    }
)
print(df)

DataFrame

use rand::{thread_rng, Rng};

let mut arr = [0f64; 5];
thread_rng().fill(&mut arr);

let df = df! (
    "nrs" => &[Some(1), Some(2), Some(3), None, Some(5)],
    "names" => &[Some("foo"), Some("ham"), Some("spam"), Some("eggs"), None],
    "random" => &arr,
    "groups" => &["A", "A", "B", "C", "B"],
)?;

println!("{}", &df);

shape: (5, 4)
┌──────┬───────┬──────────┬────────┐
│ nrs  ┆ names ┆ random   ┆ groups │
│ ---  ┆ ---   ┆ ---      ┆ ---    │
│ i64  ┆ str   ┆ f64      ┆ str    │
╞══════╪═══════╪══════════╪════════╡
│ 1    ┆ foo   ┆ 0.154163 ┆ A      │
│ 2    ┆ ham   ┆ 0.74005  ┆ A      │
│ 3    ┆ spam  ┆ 0.263315 ┆ B      │
│ null ┆ egg   ┆ 0.533739 ┆ C      │
│ 5    ┆ null  ┆ 0.014575 ┆ B      │
└──────┴───────┴──────────┴────────┘

選択

選択コンテキストは、カラムにエクスプレッションを適用します。selectは、集計、式の組み合わせ、または新しいリテラルのカラムの生成を行います。

選択コンテキストのエクスプレッションは、すべて同じ長さの Series を生成するか、長さが 1 の Series を生成する必要があります。リテラルは長さ 1 の Series として扱われます。

一部のエクスプレッションが長さ 1 の Series を生成し、他のエクスプレッションが長さ 1 ではない場合、長さ 1 の Series は残りの Series の長さにブロードキャストされます。 ブロードキャストは、式内部でも発生することに注意してください。例えば、pl.col.value() / pl.col.value.sum() では、value カラムの各要素がカラムの合計で除されます。

select

out = df.select(
    pl.sum("nrs"),
    pl.col("names").sort(),
    pl.col("names").first().alias("first name"),
    (pl.mean("nrs") * 10).alias("10xnrs"),
)
print(out)

select

let out = df
    .clone()
    .lazy()
    .select([
        sum("nrs"),
        col("names").sort(Default::default()),
        col("names").first().alias("first name"),
        (mean("nrs") * lit(10)).alias("10xnrs"),
    ])
    .collect()?;
println!("{}", out);

shape: (5, 4)
┌─────┬───────┬────────────┬────────┐
│ nrs ┆ names ┆ first name ┆ 10xnrs │
│ --- ┆ ---   ┆ ---        ┆ ---    │
│ i64 ┆ str   ┆ str        ┆ f64    │
╞═════╪═══════╪════════════╪════════╡
│ 11  ┆ null  ┆ foo        ┆ 27.5   │
│ 11  ┆ egg   ┆ foo        ┆ 27.5   │
│ 11  ┆ foo   ┆ foo        ┆ 27.5   │
│ 11  ┆ ham   ┆ foo        ┆ 27.5   │
│ 11  ┆ spam  ┆ foo        ┆ 27.5   │
└─────┴───────┴────────────┴────────┘

クエリから分かるように、選択コンテキストは非常に強力で、お互いに独立して(そして並行して)任意のエクスプレッションを評価できます。

select ステートメントと同様に、with_columns ステートメントも選択コンテキストに入ります。with_columnsselect の主な違いは、with_columns は元のカラムを保持し新しいカラムを追加するのに対し、select は元のカラムを削除することです。

with_columns

df = df.with_columns(
    pl.sum("nrs").alias("nrs_sum"),
    pl.col("random").count().alias("count"),
)
print(df)

with_columns

let out = df
    .clone()
    .lazy()
    .with_columns([
        sum("nrs").alias("nrs_sum"),
        col("random").count().alias("count"),
    ])
    .collect()?;
println!("{}", out);

shape: (5, 6)
┌──────┬───────┬──────────┬────────┬─────────┬───────┐
│ nrs  ┆ names ┆ random   ┆ groups ┆ nrs_sum ┆ count │
│ ---  ┆ ---   ┆ ---      ┆ ---    ┆ ---     ┆ ---   │
│ i64  ┆ str   ┆ f64      ┆ str    ┆ i64     ┆ u32   │
╞══════╪═══════╪══════════╪════════╪═════════╪═══════╡
│ 1    ┆ foo   ┆ 0.154163 ┆ A      ┆ 11      ┆ 5     │
│ 2    ┆ ham   ┆ 0.74005  ┆ A      ┆ 11      ┆ 5     │
│ 3    ┆ spam  ┆ 0.263315 ┆ B      ┆ 11      ┆ 5     │
│ null ┆ egg   ┆ 0.533739 ┆ C      ┆ 11      ┆ 5     │
│ 5    ┆ null  ┆ 0.014575 ┆ B      ┆ 11      ┆ 5     │
└──────┴───────┴──────────┴────────┴─────────┴───────┘

フィルタリング

フィルタリングコンテキストは、Boolean データ型に評価される 1 つ以上のエクスプレッションに基づいて DataFrame をフィルタリングします。

filter

out = df.filter(pl.col("nrs") > 2)
print(out)

filter

let out = df.clone().lazy().filter(col("nrs").gt(lit(2))).collect()?;
println!("{}", out);

shape: (2, 6)
┌─────┬───────┬──────────┬────────┬─────────┬───────┐
│ nrs ┆ names ┆ random   ┆ groups ┆ nrs_sum ┆ count │
│ --- ┆ ---   ┆ ---      ┆ ---    ┆ ---     ┆ ---   │
│ i64 ┆ str   ┆ f64      ┆ str    ┆ i64     ┆ u32   │
╞═════╪═══════╪══════════╪════════╪═════════╪═══════╡
│ 3   ┆ spam  ┆ 0.263315 ┆ B      ┆ 11      ┆ 5     │
│ 5   ┆ null  ┆ 0.014575 ┆ B      ┆ 11      ┆ 5     │
└─────┴───────┴──────────┴────────┴─────────┴───────┘

グループ化 / 集計

group_byコンテキストでは、エクスプレッションはグループを単位として機能するため、結果のデータ長は様々です(各グループのメンバー数に依存します)。

group_by

out = df.group_by("groups").agg(
    pl.sum("nrs"),  # sum nrs by groups
    pl.col("random").count().alias("count"),  # count group members
    # sum random where name != null
    pl.col("random").filter(pl.col("names").is_not_null()).sum().name.suffix("_sum"),
    pl.col("names").reverse().alias("reversed names"),
)
print(out)

group_by

let out = df
    .lazy()
    .group_by([col("groups")])
    .agg([
        sum("nrs"),                           // sum nrs by groups
        col("random").count().alias("count"), // count group members
        // sum random where name != null
        col("random")
            .filter(col("names").is_not_null())
            .sum()
            .name()
            .suffix("_sum"),
        col("names").reverse().alias("reversed names"),
    ])
    .collect()?;
println!("{}", out);

shape: (3, 5)
┌────────┬─────┬───────┬────────────┬────────────────┐
│ groups ┆ nrs ┆ count ┆ random_sum ┆ reversed names │
│ ---    ┆ --- ┆ ---   ┆ ---        ┆ ---            │
│ str    ┆ i64 ┆ u32   ┆ f64        ┆ list[str]      │
╞════════╪═════╪═══════╪════════════╪════════════════╡
│ A      ┆ 3   ┆ 2     ┆ 0.894213   ┆ ["ham", "foo"] │
│ C      ┆ 0   ┆ 1     ┆ 0.533739   ┆ ["egg"]        │
│ B      ┆ 8   ┆ 2     ┆ 0.263315   ┆ [null, "spam"] │
└────────┴─────┴───────┴────────────┴────────────────┘

結果から分かるように、group_by コンテキストで定義されたグループに対してすべての式が適用されます。標準の group_by の他に、group_by_dynamic および group_by_rolling もグループ化コンテキストです。


  1. このガイドの後の部分で説明されている List コンテキストと SQL コンテキストもありますが、簡単のため、ここでは対象外とします。