拼接DataFrame

  • 具有相同列的两个DataFrame可以垂直连接, 拼接成更长的DataFrame.
  • 两个具有不重叠列的DataFrame可以水平连接, 拼接成更宽的DataFrame.
  • 两个行数和列数不同的的DataFrame可以对联拼接, 从而形成一个可能更长或更宽的DataFrame. 列名重叠的部分将垂直连接, 列名不重叠的部分将添加新的行和列. 缺失值设置为null

垂直拼接

使用concat函数和how="vertical"参数即可, 但是要注意, 如果没有相同的列名, 则会报错(比如一个3列一个2列, 或者都是2列但是列名不完全相同).

垂直拼接不要求两个DataFrame的行数相同, 因为我们的目的是垂直拼接, 只要一样宽即可.

1import polars as pl
2
3df1 = pl.DataFrame({
4	"a":[1,2],
5	"b":[11,22]
6})
7df2 = pl.DataFrame({
8	"a":[3,4,5],
9	"b":[33,44,55]
10})
11res = pl.concat(
12	[df1,df2],
13	how="vertical"
14)
15print(res)
1shape: (5, 2)
2┌─────┬─────┐
3│ a   ┆ b   │
4│ --- ┆ --- │
5│ i64 ┆ i64 │
6╞═════╪═════╡
7│ 1   ┆ 11  │
8│ 2   ┆ 22  │
9│ 3   ┆ 33  │
10│ 4   ┆ 44  │
11│ 5   ┆ 55  │
12└─────┴─────┘

如果我们只想拼接两个DataFrame中的"a"列也是可以的, 来看代码

1import polars as pl
2
3df1 = pl.DataFrame({
4	"a":[1,2],
5	"b":[11,22]
6})
7df2 = pl.DataFrame({
8	"a":[3,4,5],
9	"b":[33,44,55]
10})
11res = pl.concat(
12	[
13		df1.select(pl.col("a")),
14		df2.select(pl.col("a"))
15	],
16	how="vertical"
17)
18print(res)

水平拼接

指定参数how="horizontal"

1df_h1 = pl.DataFrame(
2    {
3        "l1": [1, 2],
4        "l2": [3, 4],
5    }
6)
7df_h2 = pl.DataFrame(
8    {
9        "r1": [5, 6],
10        "r2": [7, 8],
11        "r3": [9, 10],
12    }
13)
14res = pl.concat(
15    [
16        df_h1,
17        df_h2,
18    ],
19    how="horizontal",
20)
21print(res)
1shape: (2, 5)
2┌─────┬─────┬─────┬─────┬─────┐
3│ l1  ┆ l2  ┆ r1  ┆ r2  ┆ r3  │
4│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
5│ i64 ┆ i64 ┆ i64 ┆ i64 ┆ i64 │
6╞═════╪═════╪═════╪═════╪═════╡
7│ 1   ┆ 3   ┆ 5   ┆ 7   ┆ 9   │
8│ 2   ┆ 4   ┆ 6   ┆ 8   ┆ 10  │
9└─────┴─────┴─────┴─────┴─────┘

行数不同, 用null填充

如果两个DataFrame的高度不相同, 则会使用null来填充, 我们看下面的代码, 注意高亮的部分

1df_h1 = pl.DataFrame(
2    {
3        "l1": [1, 2, 3],
4        "l2": [3, 4, 5],
5    }
6)
7df_h2 = pl.DataFrame(
8    {
9        "r1": [5, 6],
10        "r2": [7, 8],
11        "r3": [9, 10],
12    }
13)
14res = pl.concat(
15    [
16        df_h1,
17        df_h2,
18    ],
19    how="horizontal",
20)
21print(res)
1shape: (3, 5)
2┌─────┬─────┬──────┬──────┬──────┐
3│ l1  ┆ l2  ┆ r1   ┆ r2   ┆ r3   │
4│ --- ┆ --- ┆ ---  ┆ ---  ┆ ---  │
5│ i64 ┆ i64 ┆ i64  ┆ i64  ┆ i64  │
6╞═════╪═════╪══════╪══════╪══════╡
7│ 1   ┆ 3   ┆ 5    ┆ 7    ┆ 9    │
8│ 2   ┆ 4   ┆ 6    ┆ 8    ┆ 10   │
9│ 3   ┆ 5   ┆ null ┆ null ┆ null │
10└─────┴─────┴──────┴──────┴──────┘

包含相同列名会报错

1df_h1 = pl.DataFrame(
2    {
3        "l1": [1, 2],
4        "l2": [3, 4],
5    }
6)
7df_h2 = pl.DataFrame(
8    {
9        "l1": [5, 6],
10        "r2": [7, 8],
11        "r3": [9, 10],
12    }
13)
14res = pl.concat(
15    [
16        df_h1,
17        df_h2,
18    ],
19    how="horizontal",
20)
21print(res)
1省略
2DuplicateError: column with name 'l1' has more than one occurrence

对角拼接

指定参数how="diagonal", 使新的DataFrame更长/更宽

1df_d1 = pl.DataFrame(
2    {
3        "a": [1],
4        "b": [3],
5    }
6)
7df_d2 = pl.DataFrame(
8    {
9        "a": [2],
10        "d": [4],
11    }
12)
13
14df_diagonal_concat = pl.concat(
15    [
16        df_d1,
17        df_d2,
18    ],
19    how="diagonal",
20)
21print(df_diagonal_concat)
1shape: (2, 3)
2┌─────┬──────┬──────┐
3│ a   ┆ b    ┆ d    │
4│ --- ┆ ---  ┆ ---  │
5│ i64 ┆ i64  ┆ i64  │
6╞═════╪══════╪══════╡
7│ 1   ┆ 3    ┆ null │
8│ 2   ┆ null ┆ 4    │
9└─────┴──────┴──────┘

rechunk

我们来说下rechunk参数, 默认为False, 下面有几点来阐述这个是干嘛的

  1. 数据存储方式: Polars中, DataFrame的每一列数据可能被存储在一个或多个独立的"数据块"中. 可以想象成, 一列很长的数据不是一次性存放在一个大块里, 而是分成好几个小块存放

  2. 拼接时的默认行为: 新版本下, Polars不会讲生成的新的DataFrame的列 合并为一个连续的新的数据块, 只是简单的拼接

  3. 好处: 速度更快, 内存占用更少

  4. 潜在问题: 后续操作可能会变慢, 因为数据被分成很多小的, 不连续的块儿. 那么在进行一些需要连续内存访问的操作(比如某些计算、过滤、排序等)时, 性能可能会收到影响, 因为数据不连续, 数据的局部性不好

  5. 重新分块什么意思: 将分散的不连续的合并到一个新的,连续的内存中, 耗费资源,因为涉及到实际的数据复制, 但后续性能会更好