有一位需要处理数百Gb CSV文件的客户在首次使用Polars时,他们按照某种模式从CSV文件中读取了数据。如下面所示(展示的是一个简化版):
# 创建一个列表来保存每个CSV的LazyFrame
queries = []
# 使用glob匹配CSV文件
csv_files = glob.glob("data_files/*.csv")
# 遍历CSV文件
for csv_file in csv_files:
# 将每个CSV文件的LazyFrame添加到列表中
queries.append(pl.scan_csv(csv_file))
# 评估列表中的所有查询
queries = pl.collect_all(queries)
# 将DataFrames合并成一个单一的DataFrame
polars_df = pl.concat(queries)
# 选择列的子集
polars_df.select(["date","temperature","humdity"])
...
他们的关键操作在于以下几个方面:
- 使用glob迭代其CSV文件
- 通过执行pl.scan_csv使用惰性模式
- 将pl.scan_csv中的每个LazyFrame添加到名为查询的列表中
- 对查询运行pl.collect_all以评估每个LazyFrame并创建一个DataFrames列表
- 将数据帧连接成单个数据帧
- 继续进行进一步的转换,例如选择列的子集
不幸的是,使用这种模式,将失去Polars在流模式下进行查询优化和运行大型数据集的能力。
那么,该如何做才能更好呢?
要惰性模式!!
改进的第一种方法是延迟调用collect(或collect_all)。通常尽可能长时间地保持惰性模式(最好是整个查询),这样Polars就可以应用查询优化。
懒加载的方式在处理大数据集时非常有用,因为它允许用户在不占用过多内存和计算资源的情况下,定义复杂的数据处理流程。只有当用户真正需要结果时,Polars才会根据用户定义的计算图(Computation Graph)来优化和执行这些操作,从而大大提高处理大数据集时的性能和效率。
在这种情况下,我们在最后一行中选择列的子集。然而,我们是在用pl.collect_all评估CSV读取后进行此操作的。这意味着Polars不能利用查询优化只从CSV中读取列的子集。
如果我们在与pl.scan_csv相同的惰性查询中进行选择,那么Polars只从csv中读取我们需要的列的子集——这样就会大大加快速度并节省内存。
通过流处理的方式来实现大规模数据处理
为了使用大于内存的数据集,我们希望在流参数设置为True的情况下评估延迟查询,因此我们在一个LazyFrame上运行
pl.collect(streaming = True)
或者在一系列的LazyFrame上运行
pl.collect_all(streaming = True)
在这种流模式下,Polars将以块的形式处理每个CSV文件中的数据,并允许我们处理比可用内存大得多的数据集。
将其整合在一起
以下例子显示如何将这些整合在一起
polars_df = (
pl.scan_csv("data_files/*.csv")
# 选择列的子集
.select(["date","temperature","humdity"])
...
.collect(streaming=True)
这样一来,不仅速度更快、可扩展性更强,而且读写也更容易!
让我们来分解一下:
- 在pl.scan_csv中使用了glob字符串模式来输出单个LazyFrame。即Polars负责扫描所有CSV文件,并将它们连接到一个LazyFrame中
- 在LazyFrame上调用了select,这使Polars可以通过只读取CSV中的日期、温度和湿度列来优化查询
- 在调用collect时使用streaming=True参数,告诉Polars 希望它按块评估数据集
这里有一些要注意的事项:
- 流式处理并不适用于所有操作(但适用于filter、groupby和join等核心操作)。如果流式处理无法用于某些操作,Polars将默认为非流式处理,可能会导致在使用大型数据集时耗尽内存
- 当在使用polars处理数据时,最终的输出DataFrame的大小必须能够在你的计算机内存中容纳。如果原始数据太大,无法一次性装入内存,可能需要在处理过程中对数据进行某种形式的过滤(选择一部分数据)或聚合(对数据进行总结以减少其大小),以确保最终的输出DataFrame能够装入内存。
最后,总结一下,使用polars的惰性模式好处至少有以下几点:
- 提高性能:延迟计算可以显著提高性能,特别是在处理大型数据集时。由于数据没有立即加载到内存中,因此可以构建复杂的计算图,而不必担心内存不足。此外,Polars可以利用这种延迟计算模式来优化计算过程,例如通过并行处理或重新排序操作来减少不必要的中间数据转换。
- 构建复杂的数据处理管道:使用
lazy
API,可以构建包含多个步骤的数据处理管道。每个步骤都是计算图中的一个节点,可以将它们链接在一起以形成复杂的处理流程。这种灵活性使得Polars非常适合用于数据清洗、转换和分析等任务。 - 优化物理执行计划:当请求结果时,Polars会分析所构建的计算图,并生成一个优化的物理执行计划。这个执行计划描述了如何在实际硬件上执行计算以获取所需的结果。通过优化执行计划,Polars可以确保数据以最高效的方式被处理。
- 支持复杂的查询:由于
lazy
API允许构建复杂的计算图,因此可以编写复杂的查询来处理和分析数据。这些查询可以包含多个过滤条件、聚合操作、连接操作等,并且可以通过链式调用和函数式编程风格来轻松组合它们。 - 与Pandas的对比:与Pandas相比,Polars的
lazy
API提供了一种更灵活和高效的数据处理方式。Pandas主要基于立即执行模式,即当调用一个函数时,它会立即执行计算并返回结果。虽然Pandas也提供了一些优化和并行处理功能,但Polars的lazy
API在处理大型数据集时通常具有更好的性能和扩展性。