表达式和上下文
Polars 开发了自己的领域特定语言(DSL)来转换数据. 该语言非常易于使用, 并允许进行复杂的查询, 同时保持人类可读性.
表达式和上下文(将在此处介绍)对于实现这种可读性非常重要, 同时还允许Polars
查询引擎优化您的查询以使其尽可能快地运行
表达式 : 表达式返回一个值
Polars底层由Rust实现, Rust是基于表达式的语言, 所以Polars也是基于表达式的
在Polars中,表达式是数据转换的惰性表示形式. 表达式是模块化且灵活的, 这意味着可以将它们用作构建块来构建更复杂的表达式.
基本示例:
1import polars as pl
2pl.col("foo") + 1
pl.col("foo")
是一个表达式, 表示列名为"foo"的这一列值, +1
表示列名为"foo"的这一列中的每个元素都加1
上下文
- Polars表达式需要一个上下文, 在其中执行它们以生成一个结果. 根据使用的上下文, 相同的Polars表达式可以产生不同的结果
- 上下文决定了表达式的语义和行为, 决定了表达式在不同操作中如何被解释和执行
- 表达式本身只是返回值, 不知道自己要干嘛, 只有放在某个上下文中时Polars才会知道怎么使用这个表达式的结果
- 常见的上下文有:
select
with_columns
filter
group_by
准备数据
1import polars as pl
2from datetime import date
3
4df = pl.DataFrame(
5 {
6 "name": ["Alice Archer", "Ben Brown", "Chloe Cooper", "Daniel Donovan"],
7 "birthdate": [
8 date(1997, 1, 10),
9 date(1985, 2, 15),
10 date(1983, 3, 22),
11 date(1981, 4, 30),
12 ],
13 "weight": [57.9, 72.5, 53.6, 83.1], # (kg)
14 "height": [1.56, 1.77, 1.65, 1.75], # (m)
15 }
16)
17
18print(df)
1shape: (4, 4)
2┌────────────────┬────────────┬────────┬────────┐
3│ name ┆ birthdate ┆ weight ┆ height │
4│ --- ┆ --- ┆ --- ┆ --- │
5│ str ┆ date ┆ f64 ┆ f64 │
6╞════════════════╪════════════╪════════╪════════╡
7│ Alice Archer ┆ 1997-01-10 ┆ 57.9 ┆ 1.56 │
8│ Ben Brown ┆ 1985-02-15 ┆ 72.5 ┆ 1.77 │
9│ Chloe Cooper ┆ 1983-03-22 ┆ 53.6 ┆ 1.65 │
10│ Daniel Donovan ┆ 1981-04-30 ┆ 83.1 ┆ 1.75 │
11└────────────────┴────────────┴────────┴────────┘
select
多种方式, 任君挑选
select上下文中的表达式必须生成长度相同的序列, 或者使用标量, 标量可以广播(pl.col("weight")+1, +1 会被广播到这一列的所有值中)
1def select(self, *exprs: IntoExpr | Iterable[IntoExpr], **named_exprs: IntoExpr) -> DataFrame
select
上下文将表达式应用于列, 表达式可以是: "聚合","列的组合","字面量"
选择一列
- 直接使用字符串:
df.select("name")
- 使用列表:
df.select(["name"])
- 使用元组:
df.select(("name",))
- 使用
pl.col()
: df.select(pl.col("name"))
1shape: (4, 1)
2┌────────────────┐
3│ name │
4│ --- │
5│ str │
6╞════════════════╡
7│ Alice Archer │
8│ Ben Brown │
9│ Chloe Cooper │
10│ Daniel Donovan │
11└────────────────┘
选择多列
- 使用列表:
df.select(["name","weight"])
- 使用元组:
df.select(("name","weight"))
- 使用
pl.col(str)
: df.select([pl.col("name"),pl.col("weight")])
- 使用
pl.col(str)
: df.select(pl.col("name"),pl.col("weight"))
- 使用
pl.col(tuple)
: df.select(pl.col("name","weight"))
- 使用
pl.col(list)
: df.select(pl.col(["name","weight"]))
1shape: (4, 2)
2┌────────────────┬────────┐
3│ name ┆ weight │
4│ --- ┆ --- │
5│ str ┆ f64 │
6╞════════════════╪════════╡
7│ Alice Archer ┆ 57.9 │
8│ Ben Brown ┆ 72.5 │
9│ Chloe Cooper ┆ 53.6 │
10│ Daniel Donovan ┆ 83.1 │
11└────────────────┴────────┘
选择列, 重命名
WARNING
下面的new_name不能使用""包裹, 这是Python的语法要求, 使用第一种方式请确保value必须是一列!
df.select(new_name=pl.col("name"))
df.select(new_name="name")
df.select(pl.col("name").alias("new_name"))
1shape: (4, 1)
2┌────────────────┐
3│ new_name │
4│ --- │
5│ str │
6╞════════════════╡
7│ Alice Archer │
8│ Ben Brown │
9│ Chloe Cooper │
10│ Daniel Donovan │
11└────────────────┘
对列做运算
使用pl.col()指定列, 然后对列做运算
df.select('weight', add_weight=pl.col("weight") + 1)
1shape: (4, 2)
2┌────────┬────────────┐
3│ weight ┆ add_weight │
4│ --- ┆ --- │
5│ f64 ┆ f64 │
6╞════════╪════════════╡
7│ 57.9 ┆ 58.9 │
8│ 72.5 ┆ 73.5 │
9│ 53.6 ┆ 54.6 │
10│ 83.1 ┆ 84.1 │
11└────────┴────────────┘
``
with_columns
用来新增一列或多列
NOTE
- with_columns与select非常相似, 主要区别在于: with_columns会返回一个新的DataFrame
- with_columns中的表达式的长度必须和原始DataFrame的长度相同, 而select只需要多个表达式之间返回的长度相同即可
- 如果新增的列和原来的列重名, 则会覆盖原来的列
新增一列
df.with_columns(height_cm=pl.col("height") *100)
1shape: (4, 5)
2┌────────────────┬────────────┬────────┬────────┬───────────┐
3│ name ┆ birthdate ┆ weight ┆ height ┆ height_cm │
4│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
5│ str ┆ date ┆ f64 ┆ f64 ┆ f64 │
6╞════════════════╪════════════╪════════╪════════╪═══════════╡
7│ Alice Archer ┆ 1997-01-10 ┆ 57.9 ┆ 1.56 ┆ 156.0 │
8│ Ben Brown ┆ 1985-02-15 ┆ 72.5 ┆ 1.77 ┆ 177.0 │
9│ Chloe Cooper ┆ 1983-03-22 ┆ 53.6 ┆ 1.65 ┆ 165.0 │
10│ Daniel Donovan ┆ 1981-04-30 ┆ 83.1 ┆ 1.75 ┆ 175.0 │
11└────────────────┴────────────┴────────┴────────┴───────────┘
新增时, 转换类型
使用case方法: df.with_columns(height_cm=(pl.col("height") *100).cast(pl.UInt16))
1shape: (4, 5)
2┌────────────────┬────────────┬────────┬────────┬───────────┐
3│ name ┆ birthdate ┆ weight ┆ height ┆ height_cm │
4│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
5│ str ┆ date ┆ f64 ┆ f64 ┆ u16 │
6╞════════════════╪════════════╪════════╪════════╪═══════════╡
7│ Alice Archer ┆ 1997-01-10 ┆ 57.9 ┆ 1.56 ┆ 156 │
8│ Ben Brown ┆ 1985-02-15 ┆ 72.5 ┆ 1.77 ┆ 177 │
9│ Chloe Cooper ┆ 1983-03-22 ┆ 53.6 ┆ 1.65 ┆ 165 │
10│ Daniel Donovan ┆ 1981-04-30 ┆ 83.1 ┆ 1.75 ┆ 175 │
11└────────────────┴────────────┴────────┴────────┴───────────┘
使用alias
1df.with_columns(
2 pl.col("name").alias("full_name")
3)
1shape: (4, 6)
2┌────────────────┬────────────┬────────┬────────┬───────────┬────────────────┐
3│ name ┆ birthdate ┆ weight ┆ height ┆ height_cm ┆ full_name │
4│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
5│ str ┆ date ┆ f64 ┆ f64 ┆ u16 ┆ str │
6╞════════════════╪════════════╪════════╪════════╪═══════════╪════════════════╡
7│ Alice Archer ┆ 1997-01-10 ┆ 57.9 ┆ 1.56 ┆ 156 ┆ Alice Archer │
8│ Ben Brown ┆ 1985-02-15 ┆ 72.5 ┆ 1.77 ┆ 177 ┆ Ben Brown │
9│ Chloe Cooper ┆ 1983-03-22 ┆ 53.6 ┆ 1.65 ┆ 165 ┆ Chloe Cooper │
10│ Daniel Donovan ┆ 1981-04-30 ┆ 83.1 ┆ 1.75 ┆ 175 ┆ Daniel Donovan │
11└────────────────┴────────────┴────────┴────────┴───────────┴────────────────┘
使用标量
print(df.with_columns(1))
1shape: (4, 5)
2┌────────────────┬────────────┬────────┬────────┬─────────┐
3│ name ┆ birthdate ┆ weight ┆ height ┆ literal │
4│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
5│ str ┆ date ┆ f64 ┆ f64 ┆ i32 │
6╞════════════════╪════════════╪════════╪════════╪═════════╡
7│ Alice Archer ┆ 1997-01-10 ┆ 57.9 ┆ 1.56 ┆ 1 │
8│ Ben Brown ┆ 1985-02-15 ┆ 72.5 ┆ 1.77 ┆ 1 │
9│ Chloe Cooper ┆ 1983-03-22 ┆ 53.6 ┆ 1.65 ┆ 1 │
10│ Daniel Donovan ┆ 1981-04-30 ┆ 83.1 ┆ 1.75 ┆ 1 │
11└────────────────┴────────────┴────────┴────────┴─────────┘
df.with_columns(pl.lit("Male",dtype=pl.String).alias("gender"))
1shape: (4, 5)
2┌────────────────┬────────────┬────────┬────────┬────────┐
3│ name ┆ birthdate ┆ weight ┆ height ┆ gender │
4│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
5│ str ┆ date ┆ f64 ┆ f64 ┆ str │
6╞════════════════╪════════════╪════════╪════════╪════════╡
7│ Alice Archer ┆ 1997-01-10 ┆ 57.9 ┆ 1.56 ┆ Male │
8│ Ben Brown ┆ 1985-02-15 ┆ 72.5 ┆ 1.77 ┆ Male │
9│ Chloe Cooper ┆ 1983-03-22 ┆ 53.6 ┆ 1.65 ┆ Male │
10│ Daniel Donovan ┆ 1981-04-30 ┆ 83.1 ┆ 1.75 ┆ Male │
11└────────────────┴────────────┴────────┴────────┴────────┘
filter
filter表达式接受多个布尔表达式, 输出满足条件的行
1result = df.filter(
2 pl.col("birthdate").is_between(date(1982, 12, 31), date(1996, 1, 1)),
3 pl.col("height") > 1.7,
4)
5print(result)
1shape: (1, 4)
2┌───────────┬────────────┬────────┬────────┐
3│ name ┆ birthdate ┆ weight ┆ height │
4│ --- ┆ --- ┆ --- ┆ --- │
5│ str ┆ date ┆ f64 ┆ f64 │
6╞═══════════╪════════════╪════════╪════════╡
7│ Ben Brown ┆ 1985-02-15 ┆ 72.5 ┆ 1.77 │
8└───────────┴────────────┴────────┴────────┘
group_by 和 aggregations
- group_by根据分组表达式的唯一值对行进行分组. 然后可以将表达式应用于生成的各个分组, 这些组的可能长度是可变的.
- 可以使用表达式动态计算分组
- agg函数用于对每个分组进行聚合操作
1# by: 要作为分组依据的列, 接受表达式输入, 字符串被解析为列名
2# 返回值GroupBy可以用于执行聚合操作
3def group_by(
4 self,
5 *by: IntoExpr | Iterable[IntoExpr],
6 maintain_order: bool = False,
7 **named_by: IntoExpr,
8 ) -> GroupBy
1df = pl.DataFrame(
2 {
3 "a": ["a", "b", "a", "b", "c"],
4 "b": [1, 2, 1, 3, 3],
5 "c": [5, 4, 3, 2, 1],
6 }
7 )
1shape: (5, 3)
2┌─────┬─────┬─────┐
3│ a ┆ b ┆ c │
4│ --- ┆ --- ┆ --- │
5│ str ┆ i64 ┆ i64 │
6╞═════╪═════╪═════╡
7│ a ┆ 1 ┆ 5 │
8│ b ┆ 2 ┆ 4 │
9│ a ┆ 1 ┆ 3 │
10│ b ┆ 3 ┆ 2 │
11│ c ┆ 3 ┆ 1 │
12└─────┴─────┴─────┘
df.group_by("a").agg(pl.col("b").sum())
按照a列分为3组, 然后分别对每组的b列求和, 以下是示意
1shape: (3, 2)
2┌─────┬─────┐
3│ a ┆ b │
4│ --- ┆ --- │
5│ str ┆ i64 │
6╞═════╪═════╡
7│ c ┆ 3 │
8│ b ┆ 5 │
9│ a ┆ 2 │
10└─────┴─────┘
示例
df.group_by("a", maintain_order=True).agg(pl.col("c"))
对a这一列分组, 然后对c这一列聚合(pl.col("c")返回一个列, 所以下面可以看到收集为一个列表了)
maintain_order=True
表示按照按照输入顺序进行排序
1shape: (3, 2)
2┌─────┬───────────┐
3│ a ┆ c │
4│ --- ┆ --- │
5│ str ┆ list[i64] │
6╞═════╪═══════════╡
7│ a ┆ [5, 3] │
8│ b ┆ [4, 2] │
9│ c ┆ [1] │
10└─────┴───────────┘
示例
使用agg方法进行聚合
1import polars as pl
2
3df = pl.DataFrame({
4 "team": ["A", "A", "B", "B", "B"],
5 "score": [10, 20, 7, 14, 21]
6})
7
8result = df.group_by("team").agg([
9 pl.col("score").sum().alias("total_score"),
10 pl.col("score").mean().alias("avg_score")
11])
12
13print(result)
按照team分为2组, 然后对score列进行求和和求均值, 拼接到一个DataFrame中
1shape: (2, 3)
2┌──────┬─────────────┬───────────┐
3│ team ┆ total_score ┆ avg_score │
4│ --- ┆ --- ┆ --- │
5│ str ┆ i64 ┆ f64 │
6╞══════╪═════════════╪═══════════╡
7│ B ┆ 42 ┆ 14.0 │
8│ A ┆ 30 ┆ 15.0 │
9└──────┴─────────────┴───────────┘
示例
df.group_by("team").agg(pl.max("score").alias("max_score"))
按照team分为2组, 然后对score列进行求最大值, 拼接到一个DataFrame中
1shape: (2, 2)
2┌──────┬───────────┐
3│ team ┆ max_score │
4│ --- ┆ --- │
5│ str ┆ i64 │
6╞══════╪═══════════╡
7│ A ┆ 20 │
8│ B ┆ 21 │
9└──────┴───────────┘
表达式扩展
列名增加前缀
给每一列增加前缀, 高亮部分是等价的
1import polars as pl
2
3df = pl.DataFrame({
4 "team": ["A", "A", "B", "B", "B"],
5 "score": [10, 20, 7, 14, 21]
6})
7
8df.select(pl.col("team","score").name.prefix("polars_"))
9df.select([
10 pl.col("team").name.prefix("polars_"),
11 pl.col("score").name.prefix("polars_")
12])
1shape: (5, 2)
2┌─────────────┬──────────────┐
3│ polars_team ┆ polars_score │
4│ --- ┆ --- │
5│ str ┆ i64 │
6╞═════════════╪══════════════╡
7│ A ┆ 10 │
8│ A ┆ 20 │
9│ B ┆ 7 │
10│ B ┆ 14 │
11│ B ┆ 21 │
12└─────────────┴──────────────┘
根据列的类型进行计算
- 高亮的第11行选择所有数据类型是int64的列, 乘以10, 可以观察下面的输出结果
- 这里可以注意下第10行, 我们使用了通配选择符选择了所有列用于展示对比
1import polars as pl
2
3df = pl.DataFrame({
4 "team": ["A", "A", "B", "B", "B"],
5 "score1": [10, 20, 7, 14, 21],
6 "score2": [20, 18, 7, 15, 32],
7})
8
9print(df.select(
10 pl.col("*"),
11 (pl.col(pl.Int64) * 10).name.suffix("*10")
12))
1shape: (5, 5)
2┌──────┬────────┬────────┬───────────┬───────────┐
3│ team ┆ score1 ┆ score2 ┆ score1*10 ┆ score2*10 │
4│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
5│ str ┆ i64 ┆ i64 ┆ i64 ┆ i64 │
6╞══════╪════════╪════════╪═══════════╪═══════════╡
7│ A ┆ 10 ┆ 20 ┆ 100 ┆ 200 │
8│ A ┆ 20 ┆ 18 ┆ 200 ┆ 180 │
9│ B ┆ 7 ┆ 7 ┆ 70 ┆ 70 │
10│ B ┆ 14 ┆ 15 ┆ 140 ┆ 150 │
11│ B ┆ 21 ┆ 32 ┆ 210 ┆ 320 │
12└──────┴────────┴────────┴───────────┴───────────┘
其他
下面了解惰性API和更多表达式的用法