よく使う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に随時追記していこうと思います。