見出し画像

よく使うPolarsのExpressionsのまとめ | DataFrameを加工・集計・抽出するための知識

Polarsを使ってデータを加工・集計するためには、PolarsのExpressionsの使い方を理解する必要があります。

Expressionsには膨大な種類があることもあり、「Polarsでは何ができて何ができないのか」が分かっていなかったので、Polarsの公式リファレンスの中から、個人的に使う機会が多そうなExpressionsをまとめています。


Expressionsの基本的な使い方

  • ExpressionsはDataFrame.select()や、DataFrame.filter()などの引数に渡す

  • 変数に代入してから渡すことも可能

df = pl.DataFrame(
    {
        "A": [1, 2, 3],
        "B": [4, 5, 6]
    }
)

# Exprを直接指定するパターン
df.select(pl.col("A"))
print(df.select(pl.col("A")))
"""
shape: (3, 1)
┌─────┐
│ A   │
│ --- │
│ i64 │
╞═════╡
│ 1   │
│ 2   │
│ 3   │
└─────┘
"""

# Exprを変数に格納して指定するパターン
expr = pl.col("A")
print(df.select(expr))

"""
shape: (3, 1)
┌─────┐
│ A   │
│ --- │
│ i64 │
╞═════╡
│ 1   │
│ 2   │
│ 3   │
└─────┘
"""
# Exprをfilterで使用するパターン
print(df.filter(pl.col("A") > 1))

"""
shape: (2, 2)
┌─────┬─────┐
│ A   ┆ B   │
│ --- ┆ --- │
│ i64 ┆ i64 │
╞═════╪═════╡
│ 2   ┆ 5   │
│ 3   ┆ 6   │
└─────┴─────┘
"""

数値の計算・加工に使うExpressions

polars.Expr.abs(絶対値を返す)

  • 指定したカラムの値の絶対値を返す

df = pl.DataFrame({"A": [-1, -2, 3]})
print(df.select(pl.col('A').abs()))

"""
shape: (3, 1)
┌─────┐
│ A   │
│ --- │
│ i64 │
╞═════╡
│ 1   │
│ 2   │
│ 3   │
└─────┘
"""

polars.Expr.floor(小数点以下を切り捨てる)

  • データタイプが整数の場合はエラーになるので注意

df = pl.DataFrame({"A": [0.2, 1.2, 0.8]})
print(df.select(pl.col("A").floor()))
"""
shape: (3, 1)
┌─────┐
│ A   │
│ --- │
│ f64 │
╞═════╡
│ 0.0 │
│ 1.0 │
│ 0.0 │
└─────┘
"""

# 指定する列のデータタイプが整数の場合はエラーになる
df = pl.DataFrame({"B": [5, 8, 9]})
print(df.select(pl.col("B").floor()))

# InvalidOperationError: `floor` operation not supported for dtype `i64`


polars.Expr.mod(引数の数字で割った余りを取得する)

  • 指定したカラムのそれぞれの値に対して、引数の数字で割った余りを取得する(剰余演算って言うんですね)

df = pl.DataFrame({
    "a": [1,5,100]
})

print(df.select(pl.col("a").mod(3)))

"""
shape: (3, 1)
┌─────┐
│ a   │
│ --- │
│ i64 │
╞═════╡
│ 1   │
│ 2   │
│ 1   │
└─────┘
"""

カラム内の数値の集計に使うExpressions

polars.Expr.max(最大値を取得する)

  • 指定したカラムのなかから最大の値を取得する

  • pl.all().maxの場合は、それぞれのカラムの最大値を取得する

# カラムを指定すると、その列内の最大値を取得する
df = pl.DataFrame({"A": [1,5,100]})
print(df.select(pl.col("A").max()))

# shape: (1, 1)
# ┌─────┐
# │ A   │
# │ --- │
# │ i64 │
# ╞═════╡
# │ 100 │
# └─────┘

# 複数列を指定すると、各列ごとに最大値を取得する
df = pl.DataFrame({"A": [1,5,100], "B": [2, 300, 4]})
print(df.select(pl.all().max()))

# shape: (1, 2)
# ┌─────┬─────┐
# │ A   ┆ B   │
# │ --- ┆ --- │
# │ i64 ┆ i64 │
# ╞═════╪═════╡
# │ 100 ┆ 300 │
# └─────┴─────┘

  • データタイプがlistの場合はnullが返される

df = pl.DataFrame({
    "a": [[1,2,3],[1,2,8],[1,200,100]]
})
print(df.select(pl.col("a").max()))

# shape: (1, 1)
# ┌───────────┐
# │ a         │
# │ ---       │
# │ list[i64] │
# ╞═══════════╡
# │ null      │
# └───────────┘
  • group_byと併用すると、グループごとの最大値を取れる

df = pl.DataFrame({
    "group": ["a", "a", "b"],
    "value": [1,5,100]
})

print(df.group_by("group").max())

"""
shape: (2, 2)
┌───────┬───────┐
│ group ┆ value │
│ ---   ┆ ---   │
│ str   ┆ i64   │
╞═══════╪═══════╡
│ a     ┆ 5     │
│ b     ┆ 100   │
└───────┴───────┘
"""

polars.Expr.min(最小値の取得)

  • 指定したカラムの値から、最小値を取得する

df = pl.DataFrame({
    "a": [1,5,100]
})
print(df.select(pl.col("a").min()))

"""
shape: (1, 1)
┌─────┐
│ a   │
│ --- │
│ i64 │
╞═════╡
│ 1   │
└─────┘
"""

polars.Expr.mean(平均値の取得)

  • 指定したカラムの平均値を取得する

df = pl.DataFrame({
    "a": [1,5,100]
})

print(df.select(pl.col("a").mean()))

"""
shape: (1, 1)
┌──────────┐
│ a        │
│ ---      │
│ f64      │
╞══════════╡
│ 35.33333 │
└──────────┘
"""

polars.Expr.median(中央値の取得)

  • 指定したカラムから中央値を取得する

df = pl.DataFrame({
    "a": [1,5,100]
})
print(df.select(pl.col("a").median()))

"""
shape: (1, 1)
┌─────┐
│ a   │
│ --- │
│ f64 │
╞═════╡
│ 5.0 │
└─────┘
"""

polars.Expr.count(個数を数える)

  • 指定したカラムのNull以外の個数を数えるとドキュメントには書かれているが、Noneもカウントされてるっぽい?

df = pl.DataFrame({"A": [-1, -2, None, None]})
print(df.select(pl.col("A").count()))

# shape: (1, 1)
# ┌─────┐
# │ A   │
# │ --- │
# │ u32 │
# ╞═════╡
# │ 4   │
# └─────┘

polars.Expr.unique_counts(ユニークな個数を取得する)

  • 指定したカラム内の値について、それぞれのユニークな個数を取得する

  • 個数が返される順番は、カラム内の値の順番と同じ

  • ただしカラムの値が表示されないので、unique_counts単体だとよく分からないデータになる

df = pl.DataFrame({
    "group": ["banana", "banana", "apple", "grape", "grape"]
})

print(df.select(
    pl.col("group").unique_counts()
))

"""
shape: (3, 1)
┌─────┐
│ id  │
│ --- │
│ u32 │
╞═════╡
│ 2   │  bananaの個数
│ 1   │  appleの個数
│ 2   │  grapeの個数
└─────┘
"""
  • それぞれのユニークな個数を数えるなら、以下のgroup_byとcountを組み合わせる方法のほうがわかりやすい

df = pl.DataFrame({
    "group": ["banana", "banana", "apple", "grape", "grape"]
})
print(df.group_by("group").agg(
    count = pl.col("group").count()
))

"""
shape: (3, 2)
┌────────┬───────┐
│ group  ┆ count │
│ ---    ┆ ---   │
│ str    ┆ u32   │
╞════════╪═══════╡
│ banana ┆ 2     │
│ apple  ┆ 1     │
│ grape  ┆ 2     │
└────────┴───────┘
"""

polars.Expr.value_counts(値と個数の組み合わせを取得する)

  • カウント対象の値と、個数の組み合わせを取得できる

  • 取得した結果は、1つのカラム内にstructで格納される

  • データタイプがstructのカラムはDataFrame.unnest()で、カラムごとに展開できる

df = pl.DataFrame({
    "group": ["banana", "banana", "apple", "grape", "grape"]
})

print(df.select(pl.col("group").value_counts()))

"""
shape: (3, 1)
┌──────────────┐
│ group        │
│ ---          │
│ struct[2]    │
╞══════════════╡
│ {"grape",2}  │
│ {"apple",1}  │
│ {"banana",2} │
└──────────────┘
"""


# struct内のそれぞれの値は、unnestで展開できる
print(df.select(pl.col("group").value_counts()).unnest("group"))

"""
shape: (3, 2)
┌────────┬────────┐
│ group  ┆ counts │
│ ---    ┆ ---    │
│ str    ┆ u32    │
╞════════╪════════╡
│ banana ┆ 2      │
│ apple  ┆ 1      │
│ grape  ┆ 2      │
└────────┴────────┘
"""

polars.Expr.sum(合計する)

  • 指定したカラムの値を合計する

df = pl.DataFrame({"A": [0.2, 1.2, 0.8]})
print(df.select(pl.col("A").sum()))

"""
shape: (1, 1)
┌─────┐
│ A   │
│ --- │
│ f64 │
╞═════╡
│ 2.2 │
└─────┘
"""

カラム名の加工に使うExpressions

polars.Expr.alias(カラム名を変更する)

  • 指定したカラムの名前を、引数で指定した文字列に変更する

df = pl.DataFrame({
    "group": ["banana", "banana", "apple", "grape", "grape"]
})

print(df.select(pl.col("group").alias("fruit")))

"""
shape: (5, 1)
┌────────┐
│ fruit  │
│ ---    │
│ str    │
╞════════╡
│ banana │
│ banana │
│ apple  │
│ grape  │
│ grape  │
└────────┘
"""
  • selectやwith_columns内で、カラム名=Exprと記載しても同じ結果を得られる

  • 列数が多くなると、こちらのほうがわかりやすい

df = pl.DataFrame({
    "group": ["banana", "banana", "apple", "grape", "grape"]
})

print(df.select(
    fruit = pl.col("group")
))

polars.Expr.name.prefix(接頭語をつける)

  • 指定したカラム名に接頭語をつける

df = pl.DataFrame({
    "group": ["banana", "banana", "apple", "grape", "grape"]
})

print(df.select(
    pl.col("group").name.prefix("fruit_")
))

"""
shape: (5, 1)
┌─────────────┐
│ fruit_group │
│ ---         │
│ str         │
╞═════════════╡
│ banana      │
│ banana      │
│ apple       │
│ grape       │
│ grape       │
└─────────────┘
"""

polars.Expr.name.suffix(接尾語をつける)

  • 指定したカラム名に接尾語をつける

df = pl.DataFrame({
    "group": ["banana", "banana", "apple", "grape", "grape"]
})

print(df.select(
    pl.col("group").name.suffix("_fruit")
))

"""
shape: (5, 1)
┌─────────────┐
│ group_fruit │
│ ---         │
│ str         │
╞═════════════╡
│ banana      │
│ banana      │
│ apple       │
│ grape       │
│ grape       │
└─────────────┘
"""

文字列の操作に使うExpressions

  • データタイプが文字列(str)の場合でも、polars.expr.str.◯◯というようにstrを挟む必要がある(pl.col()だけでは文字列の操作ができない)

polars.Expr.str.replace(文字列の置換)

  • 1つ目の引数の文字列を、2つ目の引数の文字列に置換する

  • 正規表現での指定もできる

  • ただしreplaceでは、1個目にマッチした文字列しか置換しない(JavaScriptと一緒)

  • 複数個を置換するにはreplace_allを使う

df = pl.DataFrame({
    "group": ["banana", "banana", "apple", "grape", "grape"]
})

print(df.select(
    pl.col("group").str.replace("a", "_")
))
"""
shape: (5, 1)
┌────────┐
│ group  │
│ ---    │
│ str    │
╞════════╡
│ b_nana │
│ b_nana │
│ _pple  │
│ gr_pe  │
│ gr_pe  │
└────────┘
"""
  • 正規表現で指定するパターン

df = pl.DataFrame({
    "group": ["banana", "banana", "apple", "grape", "grape"]
})

print(df.select(
    pl.col("group").str.replace(r"a|r", "_")
))
"""
shape: (5, 1)
┌────────┐
│ group  │
│ ---    │
│ str    │
╞════════╡
│ b_nana │
│ b_nana │
│ _pple  │
│ g_ape  │
│ g_ape  │
└────────┘
"""

polars.Expr.str.replace_all(文字列の全置換)

  • polars.Expr.str.replaceの全置換バージョン

  • replace同様に正規表現でもOK

df = pl.DataFrame({
    "group": ["banana", "banana", "apple", "grape", "grape"]
})

print(df.select(
    pl.col("group").str.replace_all("a", "_")
))
"""
shape: (5, 1)
┌────────┐
│ group  │
│ ---    │
│ str    │
╞════════╡
│ b_n_n_ │
│ b_n_n_ │
│ _pple  │
│ gr_pe  │
│ gr_pe  │
└────────┘
"""

論理式系のExpressions

polars.Expr.any(値がひとつでもTrueかを判定する)

  • カラム内のどれかがTrueの場合に、Trueを返す

  • 値の型がbooleanもしくはNone以外の場合はエラーになる

  • Noneはデフォルトでは無視される

df = pl.DataFrame({
    "banana": [True, False, True, False, True],
    "apple": [False, False, False, False, False],
})

print(df.select(
    pl.all().any()
))

"""
shape: (1, 2)
┌────────┬───────┐
│ banana ┆ apple │
│ ---    ┆ ---   │
│ bool   ┆ bool  │
╞════════╪═══════╡
│ true   ┆ false │
└────────┴───────┘
"""

polars.Expr.is_duplicated(重複している値があるかを判定する)

  • 指定したカラムのそれぞれの値について、同じカラム内に重複している値があるかを判定する

df = pl.DataFrame({
    "fruit": ["banana", "apple", "grape", "grape"]
})

print(df.with_columns(
    is_duplicated = pl.col("fruit").is_duplicated()
))

"""
shape: (4, 2)
┌────────┬───────────────┐
│ fruit  ┆ is_duplicated │
│ ---    ┆ ---           │
│ str    ┆ bool          │
╞════════╪═══════════════╡
│ banana ┆ false         │
│ apple  ┆ false         │
│ grape  ┆ true          │
│ grape  ┆ true          │
└────────┴───────────────┘
"""

polars.Expr.is_in(値が別のカラムやlistに含まれるかを判定する)

  • is_inで指定したカラムの値に、pl.col()で指定したカラムの値が含まれているかを判定する

df = pl.DataFrame({
    "fruits": ["banana", "banana", "apple"],
    "finding_fruits": ["banana", "grape", "apple"]
})

print(df.with_columns(
    is_in_fruits_sets = pl.col("finding_fruits").is_in("fruits")
))

"""
shape: (3, 3)
┌────────┬────────────────┬───────────────────┐
│ fruits ┆ finding_fruits ┆ is_in_fruits_sets │
│ ---    ┆ ---            ┆ ---               │
│ str    ┆ str            ┆ bool              │
╞════════╪════════════════╪═══════════════════╡
│ banana ┆ banana         ┆ true              │
│ banana ┆ grape          ┆ false             │
│ apple  ┆ apple          ┆ true              │
└────────┴────────────────┴───────────────────┘
"""
  • is_inで指定するカラムのデータタイプがlistの場合は、各list内に存在するかを判定できる

df = pl.DataFrame({
    "fruits": ["banana", "banana", "apple"],
    "fruits_sets": [
        ["banana", "apple"],
        ["grape", "grape"],
        ["banana"]
    ]
})

print(df.with_columns(
    is_in_fruits_sets = pl.col("fruits").is_in("fruits_sets")
))

"""
shape: (3, 3)
┌────────┬─────────────────────┬───────────────────┐
│ fruits ┆ fruits_sets         ┆ is_in_fruits_sets │
│ ---    ┆ ---                 ┆ ---               │
│ str    ┆ list[str]           ┆ bool              │
╞════════╪═════════════════════╪═══════════════════╡
│ banana ┆ ["banana", "apple"] ┆ true              │
│ banana ┆ ["grape", "grape"]  ┆ false             │
│ apple  ┆ ["banana"]          ┆ false             │
└────────┴─────────────────────┴───────────────────┘
"""
  • is_inの引数には、DataFrame外のリストを指定することもできる

df = pl.DataFrame({
    "fruits": ["banana", "grape"]
})

checklist = ["banana", "orange", "apple"]

print(df.with_columns(
    is_in_checklist = pl.col("fruits").is_in(checklist)
))

"""
shape: (2, 2)
┌────────┬─────────────────┐
│ fruits ┆ is_in_checklist │
│ ---    ┆ ---             │
│ str    ┆ bool            │
╞════════╪═════════════════╡
│ banana ┆ true            │
│ grape  ┆ false           │
└────────┴─────────────────┘
"""


カラムから値を取得するときに使うExpressions

polars.Expr.unique(カラムからユニークな値を取得する)

df = pl.DataFrame({
    "fruit": ["banana", "banana", "apple", "grape", "grape", "grape"],
    "price": [None,300,500,100,80, 100]
})

print(df.select(
    pl.col("fruit").unique()
))
"""
shape: (3, 1)
┌────────┐
│ fruit  │
│ ---    │
│ str    │
╞════════╡
│ apple  │
│ banana │
│ grape  │
└────────┘
"""

polars.Expr.get(インデックスを指定してカラム内の値をひとつ取得する)

  • getで取得できるのは単一の値だけ

  • 複数の値を取得したい場合は、.gatherを使う

df = pl.DataFrame({
    "apple": [1, 2, 3, 4, 5]
})

print(df.select(
    pl.col("apple").get(1)
))

"""
shape: (1, 1)
┌───────┐
│ apple │
│ ---   │
│ i64   │
╞═══════╡
│ 2     │
└───────┘
"""
  • list型のデータから、list内の値を取得するには.listを挟む必要がある

  • pl.col("hoge").get(1).list.get(1)が正解

  • pl.col("hoge").get(1).get(1)のように書くとエラーになる

df = pl.DataFrame({
    "apple": [[1,2,3], [4,5,6], [7,8,9]]
})

print(df.select(
    pl.col("apple").get(1).list.get(2)
))

"""
shape: (1, 1)
┌───────┐
│ apple │
│ ---   │
│ i64   │
╞═══════╡
│ 6     │
└───────┘
"""

polars.Expr.gather(インデックスを指定してカラム内の値を複数取得する)

  • getは単一の値の取得だが、gatherは複数の値を取得できる

df = pl.DataFrame({
    "apple": [1, 2, 3, 4, 5]
})

print(df.select(
    pl.col("apple").gather([0,2])
))

"""
shape: (2, 1)
┌───────┐
│ apple │
│ ---   │
│ i64   │
╞═══════╡
│ 1     │
│ 3     │
└───────┘
"""
  • group_byと組み合わせて使うと、list型のカラムに値を格納できる

df = pl.DataFrame({
    "fruit": ["banana", "banana", "apple", "grape", "grape",  "apple"],
    "price": [200,300,500,100,80, 400]
})

print(df.group_by("fruit").agg(
    pl.col("price").gather([0,1])
))

"""
shape: (3, 2)
┌────────┬────────────┐
│ fruit  ┆ price      │
│ ---    ┆ ---        │
│ str    ┆ list[i64]  │
╞════════╪════════════╡
│ banana ┆ [200, 300] │
│ grape  ┆ [100, 80]  │
│ apple  ┆ [500, 400] │
└────────┴────────────┘
"""
  • ただし指定したインデックスが存在しない場合はエラーになる

  • 以下の場合はgatherで[0, 1]を指定しているが、"apple"がひとつしかないのでエラーになってしまう

df = pl.DataFrame({
    "fruit": ["banana", "banana", "apple", "grape", "grape"],
    "price": [200,300,500,100,80]
})

print(df.group_by("fruit").agg(
    pl.col("price").gather([0,1])
))

polars.Expr.slice(指定した範囲の行の値を取得する)

  • 引数はoffset(開始位置)と、length(取得する行数)

df = pl.DataFrame({
    "fruit": ["banana", "banana", "apple", "grape", "grape"],
    "price": [200,300,500,100,80]
})
print(df.select("price").slice(1, 3))

"""
shape: (3, 1)
┌───────┐
│ price │
│ ---   │
│ i64   │
╞═══════╡
│ 300   │
│ 500   │
│ 100   │
└───────┘
"""
  • 事前にsortしておいたDataFameに対して、group_byと併用することで、「グループごとで値が大きい上位3つを取得する」などができる

df = pl.DataFrame({
    "fruit": ["banana", "banana", "apple", "grape", "grape", "grape", "apple", "banana"],
    "price": [200, 300, 500, 100, 80, 1000, 2000, 3000]
})
print(
    df.sort(by="price", descending=True)
    .group_by("fruit")
    .agg(
        pl.col("price").slice(0, 2)
    )
    
)

"""
shape: (3, 2)
┌────────┬─────────────┐
│ fruit  ┆ price       │
│ ---    ┆ ---         │
│ str    ┆ list[i64]   │
╞════════╪═════════════╡
│ apple  ┆ [2000, 500] │
│ grape  ┆ [1000, 100] │
│ banana ┆ [3000, 300] │
└────────┴─────────────┘
"""



polasr.Expr.last(カラム内の最後の値を取得する)

  • 値がNoneだった場合は、Nullが返される

df = pl.DataFrame({
    "apple": [1, 2, 3, 4, 5],
    "banana": [1, 2, 3, 4, None],
})

print(df.select(
    pl.col("apple").last()
))

"""
shape: (1, 1)
┌───────┐
│ apple │
│ ---   │
│ i64   │
╞═══════╡
│ 5     │
└───────┘
"""



その他のよく使いそうなExpressions

polars.Expr.cast(別のデータタイプに変換する)

  • 数値→文字列 などの変換ができる

  • castの引数には、PolarsのDataTypeを指定する必要がある

  • よく使うのは以下のあたり

    • 整数…pl.Int32、pl.Int64

    • 浮動小数点…pl.Float32、pl.Float64

    • 日付・時間…pl.Date、pl.Datetime

    • 配列…pl.Array(固定長)、pl.List(可変長)

    • ブール型…pl.Boolean

    • 文字列…pl.Utf8(Stringはなぜかエラーになる)

df = pl.DataFrame({
    "fruit": ["banana", "banana", "apple", "grape", "grape"],
    "price": [200,300,500,100,80]
})
print(df.select("price").cast(pl.Utf8))

"""
shape: (5, 1)
┌───────┐
│ price │
│ ---   │
│ str   │ ← strに変換されている
╞═══════╡
│ 200   │
│ 300   │
│ 500   │
│ 100   │
│ 80    │
└───────┘
"""

polars.Expr.implode(カラム内の値をlistに変換する)

df = pl.DataFrame({
    "fruit": ["banana", "banana", "apple", "grape", "grape"],
    "price": [200,300,500,100,80]
})
print(df.select(
    pl.all().implode()
))

"""
shape: (1, 2)
┌─────────────────────────────────┬──────────────────┐
│ fruit                           ┆ price            │
│ ---                             ┆ ---              │
│ list[str]                       ┆ list[i64]        │
╞═════════════════════════════════╪══════════════════╡
│ ["banana", "banana", … "grape"] ┆ [200, 300, … 80] │
└─────────────────────────────────┴──────────────────┘
"""

polars.Expr.expload(リストを分解して新しいカラムを作る)

  • 入れ子になっているlist型のデータを展開して、新しいカラムを作る

df = pl.DataFrame({
    "values": [[1, 100],[4, 400], [5, 500]]
})
print(df.select(
    pl.col("values").explode()
))

"""
shape: (6, 1)
┌────────┐
│ values │
│ ---    │
│ i64    │
╞════════╡
│ 1      │
│ 100    │
│ 4      │
│ 400    │
│ 5      │
│ 500    │
└────────┘
"""
  • 入れ子のlist内が複数のデータタイプだった場合は展開されない

df = pl.DataFrame({
    "fruit": [["banana", 100],["banana", 400], ["apple", 500]]
})
print(df.select(
    pl.col("fruit").explode()
))

"""
shape: (3, 1)
┌─────────────────┐
│ fruit           │
│ ---             │
│ object          │
╞═════════════════╡
│ ['banana', 100] │
│ ['banana', 400] │
│ ['apple', 500]  │
└─────────────────┘
"""

polars.Expr.rank(値をカラム内でランク付けする)

  • デフォルトは小さい値ほど、rankの値も小さくなる

  • 引数にdescending=Trueを指定すると、値が大きいほど、rankの値を小さくできる

  • rankの値が浮動小数点になるのがイヤな場合は、method="ordinal"を引数に渡す

df = pl.DataFrame({
    "fruit": ["banana", "banana", "apple", "grape", "grape", "grape"],
    "price": [200,300,500,100,80, 100]
})
print(df.with_columns(
    rank = pl.col("price").rank(descending=True)
))
"""
shape: (6, 3)
┌────────┬───────┬──────┐
│ fruit  ┆ price ┆ rank │
│ ---    ┆ ---   ┆ ---  │
│ str    ┆ i64   ┆ f64  │
╞════════╪═══════╪══════╡
│ banana ┆ 200   ┆ 3.0  │
│ banana ┆ 300   ┆ 2.0  │
│ apple  ┆ 500   ┆ 1.0  │
│ grape  ┆ 100   ┆ 4.5  │
│ grape  ┆ 80    ┆ 6.0  │
│ grape  ┆ 100   ┆ 4.5  │
└────────┴───────┴──────┘
"""
  • グループごとにrankをつけたい場合は、rankの後にoverを使う

df = pl.DataFrame({
    "fruit": ["banana", "banana", "apple", "grape", "grape", "grape"],
    "price": [200,300,500,100,80, 100]
})

print(df.with_columns(
    rank = pl.col("price").rank(method="ordinal", descending=True).over("fruit")
))
"""
shape: (6, 3)
┌────────┬───────┬──────┐
│ fruit  ┆ price ┆ rank │
│ ---    ┆ ---   ┆ ---  │
│ str    ┆ i64   ┆ u32  │
╞════════╪═══════╪══════╡
│ banana ┆ 200   ┆ 2    │
│ banana ┆ 300   ┆ 1    │
│ apple  ┆ 500   ┆ 1    │
│ grape  ┆ 100   ┆ 1    │
│ grape  ┆ 80    ┆ 3    │
│ grape  ┆ 100   ┆ 2    │
└────────┴───────┴──────┘
"""
  • overでグループごとのランクをつけたあとでfilterを使えば、「グループごとに上位2つだけを抽出する」などもできる

df = pl.DataFrame({
    "fruit": ["banana", "banana", "apple", "grape", "grape", "grape"],
    "price": [200,300,500,100,80, 100]
})

print(df.with_columns(
    rank = pl.col("price").rank(method="ordinal", descending=True).over("fruit")
).filter(
    pl.col("rank") <= 2
))
"""
shape: (5, 3)
┌────────┬───────┬──────┐
│ fruit  ┆ price ┆ rank │
│ ---    ┆ ---   ┆ ---  │
│ str    ┆ i64   ┆ u32  │
╞════════╪═══════╪══════╡
│ banana ┆ 200   ┆ 2    │
│ banana ┆ 300   ┆ 1    │
│ apple  ┆ 500   ┆ 1    │
│ grape  ┆ 100   ┆ 1    │
│ grape  ┆ 100   ┆ 2    │
└────────┴───────┴──────┘
"""

polars.Expr.sort(指定したカラムの値を並び替える)

  • nulls_last=Trueを引数に渡すと、Noneを最後に回せる

df = pl.DataFrame({
    "fruit": ["banana", "banana", "apple", "grape", "grape", "grape"],
    "price": [None,300,500,100,80, 100]
})

print(df.select(
    pl.col("price").sort(descending=True, nulls_last=True)
))
"""
shape: (6, 1)
┌───────┐
│ price │
│ ---   │
│ i64   │
╞═══════╡
│ 500   │
│ 300   │
│ 100   │
│ 100   │
│ 80    │
│ null  │
└───────┘
"""
  • with_columnsでsortしたカラムを追加する場合は、順番が入れ替わってしまうので注意

df = pl.DataFrame({
    "fruit": ["banana", "banana", "apple", "grape", "grape", "grape"],
    "price": [None,300,500,100,80, 100]
})

print(df.with_columns(
    price = pl.col("price").sort(descending=True, nulls_last=True)
))
"""
shape: (6, 2)
┌────────┬───────┐
│ fruit  ┆ price │
│ ---    ┆ ---   │
│ str    ┆ i64   │
╞════════╪═══════╡
│ banana ┆ 500   │ ← 500は本来はappleの値
│ banana ┆ 300   │
│ apple  ┆ 100   │ ← 100は本来はgrapeの値
│ grape  ┆ 100   │
│ grape  ┆ 80    │ 
│ grape  ┆ null  │ ← nullは本来はbananaの値
└────────┴───────┘
"""
  • 複数行のデータを入れ替えたい場合は、DataFame.sortを使うほうが良い

df = pl.DataFrame({
    "fruit": ["banana", "banana", "apple", "grape", "grape", "grape"],
    "price": [None,300,500,100,80, 100]
})

print(df.sort("price", descending=True, nulls_last=True))
"""
shape: (6, 2)
┌────────┬───────┐
│ fruit  ┆ price │
│ ---    ┆ ---   │
│ str    ┆ i64   │
╞════════╪═══════╡
│ apple  ┆ 500   │
│ banana ┆ 300   │
│ grape  ┆ 100   │
│ grape  ┆ 100   │
│ grape  ┆ 80    │
│ banana ┆ null  │
└────────┴───────┘
"""

polars.Expr.map_elements(ユーザー定義関数を実行した結果を返す)

  • 事前に定義しておいた関数を渡したり、lambda式を渡したりできる

  • ただし公式リファレンス曰く、map_elementsは低速らしいので使い方に注意したほうが良さそう

  • 以下のコードを実行するとエラー文で「map_elementsは遅いから、こういう書き方したほうが良いよ」って教えてくれる

def square_mod_7(n):
    return (n ** 2) % 7
    
df = pl.DataFrame({
    "fruit": ["banana", "banana", "apple", "grape", "grape", "grape"],
    "price": [200,300,500,100,80, 100]
})

print(df.with_columns(
    square = pl.col("price") ** 2,
    square_mod_7 = pl.col("price").map_elements(square_mod_7)
))

"""
shape: (6, 4)
┌────────┬───────┬──────────┬──────────────┐
│ fruit  ┆ price ┆ square   ┆ square_mod_7 │
│ ---    ┆ ---   ┆ ---      ┆ ---          │
│ str    ┆ i64   ┆ f64      ┆ i64          │
╞════════╪═══════╪══════════╪══════════════╡
│ banana ┆ 200   ┆ 40000.0  ┆ 2            │
│ banana ┆ 300   ┆ 90000.0  ┆ 1            │
│ apple  ┆ 500   ┆ 250000.0 ┆ 2            │
│ grape  ┆ 100   ┆ 10000.0  ┆ 4            │
│ grape  ┆ 80    ┆ 6400.0   ┆ 2            │
│ grape  ┆ 100   ┆ 10000.0  ┆ 4            │
└────────┴───────┴──────────┴──────────────┘
"""
  • lambda式を渡す場合はこんな感じ

df = pl.DataFrame({
    "fruit": ["banana", "banana", "apple", "grape", "grape", "grape"],
    "price": [200,300,500,100,80, 100]
})

print(df.with_columns(
    square_mod_7 = pl.col("price").map_elements(lambda x: x ** 2 % 7)
))

"""
shape: (6, 3)
┌────────┬───────┬──────────────┐
│ fruit  ┆ price ┆ square_mod_7 │
│ ---    ┆ ---   ┆ ---          │
│ str    ┆ i64   ┆ i64          │
╞════════╪═══════╪══════════════╡
│ banana ┆ 200   ┆ 2            │
│ banana ┆ 300   ┆ 1            │
│ apple  ┆ 500   ┆ 2            │
│ grape  ┆ 100   ┆ 4            │
│ grape  ┆ 80    ┆ 2            │
│ grape  ┆ 100   ┆ 4            │
└────────┴───────┴──────────────┘
"""

まとめ

調べ始めてみると、当初に想定してた以上に有用そうなExpressionsがたくさんあり、このnoteもすごく長くなってしまいました…。

私はSEOという職業柄、このnoteにまとめたものを使う機会が多そうですが、Polarsの公式リファレンスには統計よりのExpressionsなど、さらに多くの項目が記載されています。

公式リファレンスはまだ一部しか読めていないので、他にも便利そうな項目があれば、このnoteに随時追記していこうと思います。

いいなと思ったら応援しよう!