表达式和上下文

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才会知道怎么使用这个表达式的结果
  • 常见的上下文有:
  1. select
  2. with_columns
  3. filter
  4. 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------------5str            ┆ date       ┆ f64    ┆ f64    │
6╞════════════════╪════════════╪════════╪════════╡
7│ Alice Archer   ┆ 1997-01-1057.91.568│ Ben Brown      ┆ 1985-02-1572.51.779│ Chloe Cooper   ┆ 1983-03-2253.61.6510│ Daniel Donovan ┆ 1981-04-3083.11.7511└────────────────┴────────────┴────────┴────────┘

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列求和, 以下是示意

a b
a 1
a 1
a b
b 2
b 3
a b
c 3
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和更多表达式的用法