表达式扩展
表达式扩展是一项功能, 能够编写一个可以扩展为多个不同表达式的表达式, 这可能取决于上下文
下面来展示一下如何使用
数据准备
1import polars as pl
2import polars.selectors as cs
3
4df = pl.DataFrame(
5 {
6 "name":["n1","n2","n3","n4"],
7 "v1":[1,2,3,4],
8 "v2":[5,6,7,8],
9 "v3":[1.1,2.2,3.3,4.4],
10 },
11 schema={"name":pl.String,"v1":pl.UInt16,"v2":pl.Int32,"v3":pl.Float32}
12
13)
14
15print(df)
1shape: (4, 4)
2┌──────┬─────┬─────┬─────┐
3│ name ┆ v1 ┆ v2 ┆ v3 │
4│ --- ┆ --- ┆ --- ┆ --- │
5│ str ┆ u16 ┆ i32 ┆ f32 │
6╞══════╪═════╪═════╪═════╡
7│ n1 ┆ 1 ┆ 5 ┆ 1.1 │
8│ n2 ┆ 2 ┆ 6 ┆ 2.2 │
9│ n3 ┆ 3 ┆ 7 ┆ 3.3 │
10│ n4 ┆ 4 ┆ 8 ┆ 4.4 │
11└──────┴─────┴─────┴─────┘
col
函数
col
函数用来引用(表示) DataFrame
中的一列
向col函数提供多个列名时, 会发生最简单的表达式扩展形式
按列名显式扩展
对列名为"v1"和"v2"使用扩展, 每列的值+10
单个列名无扩展
1res = df.select(
2 pl.col("v1")+10,
3 pl.col("v2")+10
4)
5
6print(res)
1shape: (4, 2)
2┌─────┬─────┐
3│ v1 ┆ v2 │
4│ --- ┆ --- │
5│ i64 ┆ i64 │
6╞═════╪═════╡
7│ 11 ┆ 15 │
8│ 12 ┆ 16 │
9│ 13 ┆ 17 │
10│ 14 ┆ 18 │
11└─────┴─────┘
传递多个列名进行扩展
1res = df.select(
2 pl.col("v1","v2")+10
3)
4
5print(res)
1shape: (4, 2)
2┌─────┬─────┐
3│ v1 ┆ v2 │
4│ --- ┆ --- │
5│ u16 ┆ i32 │
6╞═════╪═════╡
7│ 11 ┆ 15 │
8│ 12 ┆ 16 │
9│ 13 ┆ 17 │
10│ 14 ┆ 18 │
11└─────┴─────┘
按数据类型扩展
向col
函数传递Polars
的数据类型
1# 所有数据类型为Int32的列的值+100
2res = df.select(
3 pl.col(pl.Int32)+100
4)
5print(res)
1shape: (4, 1)
2┌─────┐
3│ v2 │
4│ --- │
5│ i32 │
6╞═════╡
7│ 105 │
8│ 106 │
9│ 107 │
10│ 108 │
11└─────┘
通过模式匹配进行扩展
可以使用正则表达式来指定用于匹配列名的模式
为了区分常规列名和通过模式匹配扩展的列名, 正则表达式以^
开头, 以$
结尾, 正则表达式可以与常规列名混合使用
1df.select(
2 pl.col("name","^v[0-9]$")
3)
name这一列使用常规列名, v1和v2使用正则表达式进行扩展, ^v[0-9]$
表示"v开头, 数字0-9结尾", 正好可以匹配到v1和v2
1shape: (4, 4)
2┌──────┬─────┬─────┬─────┐
3│ name ┆ v1 ┆ v2 ┆ v3 │
4│ --- ┆ --- ┆ --- ┆ --- │
5│ str ┆ u16 ┆ i32 ┆ f32 │
6╞══════╪═════╪═════╪═════╡
7│ n1 ┆ 1 ┆ 5 ┆ 1.1 │
8│ n2 ┆ 2 ┆ 6 ┆ 2.2 │
9│ n3 ┆ 3 ┆ 7 ┆ 3.3 │
10│ n4 ┆ 4 ┆ 8 ┆ 4.4 │
11└──────┴─────┴─────┴─────┘
参数中不能同时有列名和数据类型
选择所有列
pl.all()
是pl.col("*")的语法糖, 不过all语义化更强, 所以推荐使用all
1shape: (4, 4)
2┌──────┬─────┬─────┬─────┐
3│ name ┆ v1 ┆ v2 ┆ v3 │
4│ --- ┆ --- ┆ --- ┆ --- │
5│ str ┆ u16 ┆ i32 ┆ f32 │
6╞══════╪═════╪═════╪═════╡
7│ n1 ┆ 1 ┆ 5 ┆ 1.1 │
8│ n2 ┆ 2 ┆ 6 ┆ 2.2 │
9│ n3 ┆ 3 ┆ 7 ┆ 3.3 │
10│ n4 ┆ 4 ┆ 8 ┆ 4.4 │
11└──────┴─────┴─────┴─────┘
排除列
选择所有列, 但排除某些列
Polars
提供了一种从表达式扩展中排除某些列的机制, 可以使用exclude
, 接收与col
完全相同类型的参数
1res = df.select(
2 pl.all().exclude("name","^v[1]$")
3)
4print(res)
选择所有列, 但排除列名"name"和列名"v1"的列
1shape: (4, 2)
2┌─────┬─────┐
3│ v2 ┆ v3 │
4│ --- ┆ --- │
5│ i32 ┆ f32 │
6╞═════╪═════╡
7│ 5 ┆ 1.1 │
8│ 6 ┆ 2.2 │
9│ 7 ┆ 3.3 │
10│ 8 ┆ 4.4 │
11└─────┴─────┘
只排除某些列
select
上下文默认选择所有列, 可以使用pl.exclude()
来排除某些列
1# 排除name这一列
2res = df.select(
3 pl.exclude("name")
4)
5print(res)
1shape: (4, 3)
2┌─────┬─────┬─────┐
3│ v1 ┆ v2 ┆ v3 │
4│ --- ┆ --- ┆ --- │
5│ u16 ┆ i32 ┆ f32 │
6╞═════╪═════╪═════╡
7│ 1 ┆ 5 ┆ 1.1 │
8│ 2 ┆ 6 ┆ 2.2 │
9│ 3 ┆ 7 ┆ 3.3 │
10│ 4 ┆ 8 ┆ 4.4 │
11└─────┴─────┴─────┘
对列重命名
默认情况下, 讲表达式应用于列时, 其结果列名保持不变, 这可能会导致错误, 我们来看代码
1from polars.exceptions import DuplicateError
2try:
3 res = df.select(
4 pl.col("v1")+1,
5 pl.col("v1")+2,
6 )
7except DuplicateError as err:
8 print("column name duplicate error: ", err)
1column name duplicate error: the name 'v1' is duplicate
2
3It's possible that multiple expressions are returning the same default column name.
4
5If this is the case, try renaming the columns with `.alias("new_name")` to avoid duplicate column names.
上面的错误提示非常清晰, 提示我们v1
重复了, 然后如果是这种情况, 考虑使用.alias()
来重命名列, 来看下面的解决办法
使用别名重命名单个列
使用.alias()
来重命名列, 但是.alias()
不能应用于表达式扩展, 因为.alias()
被设计用于单个列
1res = df.select(
2 (pl.col("v1")+1).alias("v1+1"),
3 (pl.col("v1")+2).alias("v1+2"),
4)
5print(res)
1shape: (4, 2)
2┌──────┬──────┐
3│ v1+1 ┆ v1+2 │
4│ --- ┆ --- │
5│ u16 ┆ u16 │
6╞══════╪══════╡
7│ 2 ┆ 3 │
8│ 3 ┆ 4 │
9│ 4 ┆ 5 │
10│ 5 ┆ 6 │
11└──────┴──────┘
为列名添加前缀和后缀
使用.prefix()
和.suffix()
来为列名添加前缀和后缀
1# 通过name属性的方法, 可以增加前缀或后缀
2res = df.select(
3 (pl.col("v1")+1).name.prefix("add_1_"),
4 (pl.col("v1")+2).name.suffix("_add_2"),
5)
6print(res)
1shape: (4, 2)
2┌──────────┬──────────┐
3│ add_1_v1 ┆ v1_add_2 │
4│ --- ┆ --- │
5│ u16 ┆ u16 │
6╞══════════╪══════════╡
7│ 2 ┆ 3 │
8│ 3 ┆ 4 │
9│ 4 ┆ 5 │
10│ 5 ┆ 6 │
11└──────────┴──────────┘
动态名称替换
如果以上都不能满足需求, 命名空间name还提供了map
方法, 该方法接收一个函数
, 该函数
接受旧的列名返回新的列名
下面的例子展示了如何使用map
方法, 我们对v1
使用了自定义函数, v2
使用了标准库函数str.upper
1def new_name_example(old_name:str) -> str:
2 return old_name+old_name
3
4res = df.select(
5 pl.col("v1").name.map(new_name_example),
6 pl.col("v2").name.map(str.upper)
7)
8
9print(res)
1shape: (4, 2)
2┌──────┬─────┐
3│ v1v1 ┆ V2 │
4│ --- ┆ --- │
5│ u16 ┆ i32 │
6╞══════╪═════╡
7│ 1 ┆ 5 │
8│ 2 ┆ 6 │
9│ 3 ┆ 7 │
10│ 4 ┆ 8 │
11└──────┴─────┘
以编程的方式生成表达式(动态生成)
如果需求稍微有些复杂, 可以使用动态生成表达式的方式来实现自己的需求, 我们这里使用简单的示例进行演示
1def gen_expr(l: list[int]):
2 for x in l:
3 yield pl.col(f"v{x}")
4res = df.select(gen_expr([1,2]))
5print(res)
1shape: (4, 2)
2┌─────┬─────┐
3│ v1 ┆ v2 │
4│ --- ┆ --- │
5│ u16 ┆ i32 │
6╞═════╪═════╡
7│ 1 ┆ 5 │
8│ 2 ┆ 6 │
9│ 3 ┆ 7 │
10│ 4 ┆ 8 │
11└─────┴─────┘
更灵活的列选择
Polars
附带子模块, selectors
模块提供了许多函数, 允许编写更灵活的列表达式扩展
可以根据类型选择列, 也可以根据模式选择列
选择所有字符串列和模式选择
使用string()函数和ends_with()函数选择所有字符串列或者以"me"结尾的列
同理还有start_with()
等实用函数
1import polars.selectors as cs
2
3res = df.select(cs.string()|cs.ends_with("me"))
4
5print(res)
1shape: (4, 1)
2┌──────┐
3│ name │
4│ --- │
5│ str │
6╞══════╡
7│ n1 │
8│ n2 │
9│ n3 │
10│ n4 │
11└──────┘
选择所有的数值列
使用numeric()
函数选择所有的数值列, 无论是整数还是浮点数, 省去我们通过col手动指定了
1# 通过schema显式指定类型
2res = df.select(cs.numeric())
3
4print(res)
1shape: (4, 3)
2┌─────┬─────┬─────┐
3│ v1 ┆ v2 ┆ v3 │
4│ --- ┆ --- ┆ --- │
5│ u16 ┆ i32 ┆ f32 │
6╞═════╪═════╪═════╡
7│ 1 ┆ 5 ┆ 1.1 │
8│ 2 ┆ 6 ┆ 2.2 │
9│ 3 ┆ 7 ┆ 3.3 │
10│ 4 ┆ 8 ┆ 4.4 │
11└─────┴─────┴─────┘
将选择器与集合操作组合
A-B
: 只在A不在B中, 集合的差集
选择所有包含"v"的列, 但是排除所有浮点数列, 结果为v1和v2列
1res = df.select(cs.contains("v")-cs.float())
2
3print(res)
1shape: (4, 2)
2┌─────┬─────┐
3│ v1 ┆ v2 │
4│ --- ┆ --- │
5│ u16 ┆ i32 │
6╞═════╪═════╡
7│ 1 ┆ 5 │
8│ 2 ┆ 6 │
9│ 3 ┆ 7 │
10│ 4 ┆ 8 │
11└─────┴─────┘
A|B
: 满足表达式A或者B的列, 集合的并集
1res = df.select(cs.string() | cs.float())
2print(res)
1shape: (4, 2)
2┌──────┬─────┐
3│ name ┆ v3 │
4│ --- ┆ --- │
5│ str ┆ f32 │
6╞══════╪═════╡
7│ n1 ┆ 1.1 │
8│ n2 ┆ 2.2 │
9│ n3 ┆ 3.3 │
10│ n4 ┆ 4.4 │
11└──────┴─────┘
A&B
: 满足表达式A和B的列, 集合的交集
选择包含"v"并且是整数的列(排除了浮点数)
1res = df.select(cs.contains("v") & cs.integer())
2print(res)
~A
: 集合的补集
1res = df.select(~cs.integer())
2print(res)
1shape: (4, 2)
2┌──────┬─────┐
3│ name ┆ v3 │
4│ --- ┆ --- │
5│ str ┆ f32 │
6╞══════╪═════╡
7│ n1 ┆ 1.1 │
8│ n2 ┆ 2.2 │
9│ n3 ┆ 3.3 │
10│ n4 ┆ 4.4 │
11└──────┴─────┘
解决运算符歧义
~
符号针对布尔表达式含义是取反, 针对选择器是取补集, 为了解决歧义, 可以使用方法: as_expr
, 看下面官方代码
我们想把所有以"has"开头的列的值取反, 但结果是选择了所有不以"has"开头的列
1import polars as pl
2import polars.selectors as cs
3people = pl.DataFrame(
4 {
5 "name": ["Anna", "Bob"],
6 "has_partner": [True, False],
7 "has_kids": [False, False],
8 "has_tattoos": [True, False],
9 "is_alive": [True, True],
10 }
11)
12
13wrong_result = people.select((~cs.starts_with("has_")))
14print(wrong_result)
1shape: (2, 2)
2┌──────┬──────────┐
3│ name ┆ is_alive │
4│ --- ┆ --- │
5│ str ┆ bool │
6╞══════╪══════════╡
7│ Anna ┆ true │
8│ Bob ┆ true │
9└──────┴──────────┘
正确的写法是使用as_expr()
方法, as_expr
方法将选择器转换为表达式, 然后取反, 我们看下面代码
1res = people.select((~cs.starts_with("has_").as_expr()))
2print(res)
可以细致对比一下结果, 是否按照我们的预期, 针对布尔值进行取反了
1shape: (2, 3)
2┌─────────────┬──────────┬─────────────┐
3│ has_partner ┆ has_kids ┆ has_tattoos │
4│ --- ┆ --- ┆ --- │
5│ bool ┆ bool ┆ bool │
6╞═════════════╪══════════╪═════════════╡
7│ false ┆ true ┆ false │
8│ true ┆ true ┆ true │
9└─────────────┴──────────┴─────────────┘
调试选择器
当不确定是否有一个Polars选择器时, 可以使用函数: cs.is_selector
来检测
print(cs.is_selector(~cs.starts_with("has_").as_expr()))
: False
print(cs.is_selector(~cs.starts_with("has_")))
: True
, 这是一个选择器
另一个有用的调试器函数是: expand_selector
, 对于给定的目标DataFrame
或者选择器, 可以检查扩展为哪些列
1print(
2 cs.expand_selector(
3 people,
4 cs.starts_with("has_"),
5 )
6)
('has_partner', 'has_kids', 'has_tattoos')
完整参考