Series和表达式
最近我收到一个Polars新用户的问题:在Polars中,Series和表达式之间有什么区别?
嗯,Series是一个一维数据结构,其中所有元素都具有相同的数据类型(例如,整数、字符串等),Series可以被视为数据的列,类似于pandas 的column,它代表了数据表中的一列。
而表达式是一个函数,它作用于Series以产生一个新的Series。即表达式是从列(Series)到列的映射(或从数学角度上说是Fn(Series) -> Series),并且可以在各种上下文中使用。
所以我们使用表达式来转换Series。
在Polars中有很多表达式的例子:
- 最简单的例子是恒等表达式,它仅返回给定的
Series
,即pl.col("id")
。恒等表达式(identity expression)在这里指的是没有改变或修改原始数据的表达式,它只是简单地返回了引用的列。 - 我们可以使用表达式如pl.col("id").str.to_uppercase()来转换Series的内容
- 我们可以使用表达式如pl.col("value") + 1在Series上进行算术运算
- 我们可以使用表达式如pl.col("value").sum()来聚合Series
- 我们可以使用表达式如pl.col("value").alias("double_value")来改变输出Series的名称
- 我们可以使用表达式如pl.col("value").sum().over("id")在分组上应用表达式
什么是表达式 API?
下一个问题:Polars文档经常提到的表达式API是什么?
表达式API是Polars中接受表达式作为参数的方法以及表达式本身的总称。
1. 接受表达式作为参数的方法
接受表达式作为参数的DataFrame方法示例有:
df.filter(pl.col("id") == 1)
df.select(pl.col("id").str.to_uppercase())
df.with_columns(double_value=pl.col("value") * 2)
df.groupby(pl.col("id")).agg(pl.col("value").sum())
2. 表达式本身
表达式API 的另一个组成部分是表达式本身。这些是我们用来转换Series和聚合Series的函数。
这些函数在Polars API文档中有列出。这些函数被分成几类,如:
- 聚合(Aggregation)用于聚合(显然)
- 计算(Computation)用于计算(显然)
- 列/名称(Columns / names)用于处理列名
- 窗口(Window)用于在分组上应用表达式
还有一些仅适用于特定数据类型的表达式类别,如:
- 字符串(Strings)用于字符串操作和匹配
- 时间(Temporal)用于处理日期和时间
- 数组或列表(Array or List)用于处理数组和列表
3. 上下文
当我们使用这些带表达式的方法时,有一个重要的概念需要理解:上下文。上下文告诉我们哪些实际数据将被用作表达式的输入。
例如,当我们执行df.filter或df.select时,我们处于选择(select)上下文。这个上下文意味着DataFrame的整列是表达式的输入。
当我们执行df.groupby时,我们处于分组(groupby)上下文。这个上下文意味着表达式的输入是具有分组列相同值的行组。
另外,在Polars中,"上下文"的概念不是直接指一个特定的编程结构或对象,应该从数据处理的视角来理解它。在Polars中,当你对一个DataFrame进行操作时,你实际上是在一个特定的数据处理环境中工作,这个环境可以理解为一种"上下文"。
以下是如何理解Polars中"上下文"的几个关键点:
- 数据范围:当你对一个DataFrame调用一个方法或应用一个表达式时,你正在对该DataFrame的当前状态进行操作。这个DataFrame及其内容就是你当前操作的上下文。
- 链式操作:Polars支持链式方法调用,这意味着你可以连续地在同一个DataFrame上应用多个操作。每一个后续的操作都是基于前一个操作的结果,从而构建了一个操作链或上下文流。
例如:
filtered_df = df.filter(pl.col('age') > 30).select(['name', 'age'])
在这个例子中,df.filter(pl.col('age') > 30)
首先过滤出'age'
大于30的行,然后.select(['name', 'age'])
在过滤后的结果上选择'name'
和'age'
列。这两个操作都是在同一个DataFrame的上下文中进行的。
3. 状态不变性:虽然Polars支持链式操作,但它也遵循状态不变性原则。这意味着当你对一个DataFrame应用一个操作时,原始DataFrame不会被修改;相反,会返回一个新的DataFrame,其中包含操作的结果。这种设计有助于避免在数据处理过程中引入意外的副作用或错误。
4. 表达式和计算:在Polars中,表达式通常用于描述你希望对数据进行的操作或转换。这些表达式在DataFrame的上下文中被评估和执行。Polars的优化引擎会尝试以最高效的方式执行这些表达式,无论是通过矢量化计算还是其他技术。
总的来说,在Polars中,"上下文"可以理解为你在其上执行操作的数据集(如DataFrame)及其当前状态。你的操作(如过滤、选择、转换等)都是在这个上下文中进行的,并且通常会返回一个新的数据集作为结果。
表达式API的好处是什么?
表达式API允许您发挥Polars的全部功能。
首先,当我们在同一上下文中应用多个表达式时,Polars可以并行运行它们。
比如:
import polars as pl
# 创建一个示例DataFrame
df = pl.DataFrame({
'name': ['Alice', 'Bob', 'Charlie', 'David'],
'age': [25, 35, 20, 40],
'city': ['New York', 'Paris', 'London', 'New York']
})
# 在同一上下文中应用多个表达式
# 首先,筛选出age大于30的行
# 然后,选择name和city列
filtered_and_selected_df = (
df
.filter(pl.col('age') > 30) # 第一个表达式:筛选
.select(['name', 'city']) # 第二个表达式:选择
)
# 显示结果
print(filtered_and_selected_df)
其次,表达式API允许您以惰性模式工作。一个表达式实际上是对Polars查询引擎的指令,指示您想做什么。在惰性模式下,您可以构建复杂的数据处理管道,然后Polars可以在执行之前应用查询优化。
最后,表达式API允许您处理大于内存的数据集。Polars可以通过分块处理数据来处理太大而无法装入内存的数据集。这被称为分块处理,是表达式API的一个关键特性。