懒惰模式 1
在DataFrame和LazyFrame上的操作
为了展示在DataFrame和LazyFrame上操作的差异,我们使用rename方法将PassengerID列重命名为Id。
在DataFrame上,我们会看到第一列已经被重命名...
(df_eager.rename({"PassengerId":"Id"}).head(2)
)
而在LazyFrame上,我们会看到查询计划中增加了一个RENAME步骤。
(df_lazy.rename({"PassengerId":"Id"})
)
链式调用或重新赋值?
在这门课程中,我们通常像这样使用链式调用来执行操作
(pl.scan_csv(csv_file).rename({"PassengerId":"Id"})
)
然而,我们也可以在每一步中通过重新赋值给变量来执行操作
df_lazy = pl.scan_csv(csv_file)
df_lazy = df_lazy.rename({"PassengerId":"Id"})
当使用DataFrames或LazyFrames时,这两种方法是等效的。
查询优化
Polars 从您的查询中创建一个“简单查询计划”。这意味着这个查询计划没有经过优化。
Polars 将简单查询计划传递给其查询优化器。查询优化器会寻找更高效的方法来获得您想要的输出。
explain 方法展示了优化后的计划。我们使用 print 语句来正确地格式化它。
print(pl.scan_csv(csv_file).explain()
)
在这个简单的例子中,查询计划显示我们:
- 扫描CSV文件
- 选择所有12列(*/12*)
输出是一个 DataFrame。
应用了哪些查询优化?
查询优化并不是魔法。如果用户知道优化的存在、记得实现优化并且正确地实现了优化,大多数优化都可以通过用户在精心编写的查询中实现。
Polars应用的优化包括:
- projection pushdown:限制读取的列数至所需列
- predicate pushdown:尽早应用过滤条件
- combine predicates:合并多个过滤条件
- slice pushdown:当需要限制行数时,限制处理的行数
- common subplan elimination:对相同的数据只运行一次重复的转换,然后重用
- common subexpression elimination:重复的表达式被缓存并重用
我们在课程的后续部分会看到这些优化中的大部分是如何产生的。
公共子表达式消除
在这里,我们会看到公共子表达式消除优化是如何工作的。通过公共子表达式消除,Polars识别出哪些相同的表达式被计算了多次,因此Polars会缓存第一次的输出以便重用。
在这个例子中,我们有一个延迟查询,其中我们扫描了Titanic CSV文件。然后,我们:
- 使用select来输出一列或多列的子集
- 创建一个第一个表达式,它包含平均年龄减去一个标准差
- 创建一个第二个表达式,它包含平均年龄
- 创建一个第三个表达式,它包含平均年龄加上一个标准差
- 使用.collect来评估查询
(pl.scan_csv(csv_file).select((pl.col("Age").mean() - pl.col("Age").std()).alias("minus_one_std"),pl.col("Age").mean().alias("mean"),(pl.col("Age").mean() + pl.col("Age").std()).alias("plus_one_std"),).collect()
)
在这个查询中,我们重复使用了pl.col("Age").mean()和pl.col("Age").std()这两个表达式。如果我们使用.explain打印优化后的查询计划,可以看到Polars正在应用公共子表达式优化。
print(pl.scan_csv(csv_file).select((pl.col("Age").mean() - pl.col("Age").std()).alias("minus_one_std"),pl.col("Age").mean().alias("mean"),(pl.col("Age").mean() + pl.col("Age").std()).alias("minus_one_std"),).explain()
)
这个查询计划由FROM分隔为两个块。
在上面的SELECT块中,我们看到这些表达式是以__POLARS_CSER_X的形式调用的,其中均值表达式有一个代码,标准差表达式也有一个代码。我们可以看到,Polars已经识别出这些是在SELECT块中的三个表达式中相同的子表达式。
Polars还实现了其他优化,例如在排序数据上的快速路径算法(与查询优化器分开)。我们将在课程的后续部分中进一步了解这些优化。
练习
在练习中,你将增强对以下内容的理解:
- 从CSV文件创建LazyFrame
- 从LazyFrame获取元数据
- 打印查询计划
练习 1
通过扫描Titanic CSV文件来创建一个LazyFrame
df = pl.<blank>
检查你可以从LazyFrame中获取以下哪些元数据:
- 行数
- 列名
- 模式(schema)
创建一个懒加载查询,其中扫描Titanic CSV文件,然后选择Name和Age列。
(pl.scan_csv(csv_file)<blank>
)
打印出这个查询的优化后查询计划
解决方案
练习1的解决方案
通过扫描Titanic CSV文件来创建一个LazyFrame
df = pl.scan_csv(csv_file)
LazyFrame不知道CSV中的行数
df.shape
LazyFrame知道列名。正如我们将在I/O部分看到的那样,Polars通过pl.scan_csv扫描CSV文件的第一行来获取列名。
df.schema
创建一个延迟查询,其中扫描Titanic CSV文件,然后选择Name和Age列。
(pl.scan_csv(csv_file).select("Name","Age")
)
打印这个查询的优化后的查询计划
print(pl.scan_csv(csv_file).select("Name","Age").explain()
)