Bootstrap

从 Pandas 到 Polars 五:Concat, extend 或者 vstack?

从表面上看,Polars中的concat、extend和vstack函数都可以完成相同的任务:它们可以接收两个初始的DataFrame并将它们合并成一个单一的DataFrame。在这篇文章中,我将展示这些函数在底层对你的数据执行了完全不同的操作,这可能会对你的查询性能产生显著影响。

基本配置

这是基本设置——我们想要将两个DataFrame(df1和df2)合并起来。

import polars as pl
df1 = (
    pl.DataFrame(
        {
            "id":[0,1],
            "values":["a","b"]
        }
    ))
shape: (2, 2)  
┌─────┬────────┐
│ id  ┆ values │
│ --- ┆ ---    │
│ i64 ┆ str    │
╞═════╪════════╡
│ 0   ┆ a      │
│ 1   ┆ b      │
└─────┴────────┘

df2 = (
    pl.DataFrame(
        {
            "id":[2,3],
            "values":["c","d"]
        }
    ))
shape: (2, 2)
┌─────┬────────┐
│ id  ┆ values │
│ --- ┆ ---    │
│ i64 ┆ str    │
╞═════╪════════╡
│ 2   ┆ c      │
│ 3   ┆ d      │
└─────┴────────┘

如果我们调用concat、vstack或extend中的任何一个函数,我们会得到以下输出:

shape: (4, 2)
┌─────┬────────┐
│ id  ┆ values │
│ --- ┆ ---    │
│ i64 ┆ str    │
╞═════╪════════╡
│ 0   ┆ a      │
│ 1   ┆ b      │
│ 2   ┆ c      │
│ 3   ┆ d      │
└─────┴────────┘

那有什么区别呢?

对于两个初始的DataFrame,数据位于内存中的两个不同位置。当我们将它们合并为一个新的DataFrame时,有三种选项:

  • 将所有数据复制到单个新位置
  • 让数据保留在原来的位置,并将新的DataFrame链接到内存中现有的两个位置
  • 从一个位置复制数据,并将其追加到另一个位置的数据中

请注意,在最后一种追加的情况下,必须有足够的空间来追加数据。如果没有足够的空间,那么两个DataFrame都会被复制到新的位置。

这三种方法concat、vstack或extend使用以下三种选项:

pl.concat([df_1,df_2]) 当我们使用默认的rechunk=True参数时,会将所有数据复制到单个新位置

df_1.vstack(df_2) 不会复制任何数据,只是将新的DataFrame链接到内存中现有的两个位置

df_1.extend(df_2) 会复制df_2中的数据,并将其追加到df_1的数据中

我在这篇帖子中稍微简化了这些描述,但这些都是基本范式。在底层实现中,pl.concat执行一系列.vstack操作(给定一个DataFrame列表),然后进行rechunk操作以将数据复制到单个位置。

优点和缺点是什么?

这些不同的方法显然各有优缺点:

  • 将所有数据复制到新位置是昂贵的操作。但是,将数据集中在一个位置会使后续查询更快,并且在时间方面给出更一致的结果。
  • 不复制任何数据非常快(可能是亚毫秒级),但会减慢后续查询的速度。
  • 将一个位置的数据追加到另一个位置比同时复制两者要快,但很难预测何时会出现空间不足的情况,那时两者都需要被复制到新的位置。

在这里探索了在简单查询中不同方法的相对时间。一般来说,如果你打算在一个DataFrame上执行后续操作,那么通常值得使用pl.concat将数据复制到单个位置。然而,如果你只是想将DataFrames组合起来做一些简单的事情,比如检查形状,那么vstack是更好的选择。如果你正在将一个小DataFrame添加到一个大DataFrame上,那么extend工作得很好,因为你只需要复制小DataFrame中的数据。

最佳方法非常依赖于你的问题,但我建议在数据合并在你的管道中占用大量时间时,比较这些方法中的每一种。

;