透视

pivot函数的作用是透视表转换, 把某一列的列值, 转换为列名, 然后取其它的列, 聚合成新的单元格, 变成一个宽表

及时模式

我们通过几个例子来学习一下什么是pivot

Example1

在下面代码中我们想得到不同产品每个月的销量

1import polars as pl
2
3df = pl.DataFrame({
4    "month": ["Jan", "Jan", "Feb", "Feb"],
5    "product": ["apple", "banana", "apple", "banana"],
6    "sales": [100, 200, 150, 250]
7})
8
9print(df)
10res = df.pivot("product",index="month",values="sales")
11print(res)

通过第二个表格可以看到, product列的值转换为了列名, sales列的值转换为了列值, 此时我们就可以统计每个产品的一些数据了, 这就是pivot的作用.

为了更清晰地表达, 可以看下面的图

1shape: (4, 3)
2┌───────┬─────────┬───────┐
3│ month ┆ product ┆ sales │
4│ ---   ┆ ---     ┆ ---   │
5│ str   ┆ str     ┆ i64   │
6╞═══════╪═════════╪═══════╡
7│ Jan   ┆ apple   ┆ 100   │
8│ Jan   ┆ banana  ┆ 200   │
9│ Feb   ┆ apple   ┆ 150   │
10│ Feb   ┆ banana  ┆ 250   │
11└───────┴─────────┴───────┘
12shape: (2, 3)
13┌───────┬───────┬────────┐
14│ month ┆ apple ┆ banana │
15│ ---   ┆ ---   ┆ ---    │
16│ str   ┆ i64   ┆ i64    │
17╞═══════╪═══════╪════════╡
18│ Jan   ┆ 100   ┆ 200    │
19│ Feb   ┆ 150   ┆ 250    │
20└───────┴───────┴────────┘

Example2

Example1中的例子正好是2个月份, 每个月份都有apple/banana, 如果不是这种情况呢, 我们来看下面的代码

1import polars as pl
2
3df = pl.DataFrame({
4    "month": ["Jan","Jan","May", "Feb", "Feb"],
5    "product": ["apple","peach","banana", "apple", "banana"],
6    "sales": [100, 200, 400,150, 250]
7})
8
9print(df)
10res = df.pivot("product",index="month",values="sales")
11print(res)

可以看下面的结果, 第二个表中缺失的值使用null进行填充

1shape: (5, 3)
2┌───────┬─────────┬───────┐
3│ month ┆ product ┆ sales │
4│ ---   ┆ ---     ┆ ---   │
5│ str   ┆ str     ┆ i64   │
6╞═══════╪═════════╪═══════╡
7│ Jan   ┆ apple   ┆ 100   │
8│ Jan   ┆ peach   ┆ 200   │
9│ May   ┆ banana  ┆ 400   │
10│ Feb   ┆ apple   ┆ 150   │
11│ Feb   ┆ banana  ┆ 250   │
12└───────┴─────────┴───────┘
13shape: (3, 4)
14┌───────┬───────┬───────┬────────┐
15│ month ┆ apple ┆ peach ┆ banana │
16│ ---   ┆ ---   ┆ ---   ┆ ---    │
17│ str   ┆ i64   ┆ i64   ┆ i64    │
18╞═══════╪═══════╪═══════╪════════╡
19│ Jan   ┆ 100   ┆ 200   ┆ null   │
20│ May   ┆ null  ┆ null  ┆ 400    │
21│ Feb   ┆ 150   ┆ null  ┆ 250    │
22└───────┴───────┴───────┴────────┘

聚合参数

上面的例子略显简单, 因为每个月份都只有一个apple/banana, 那如果有多个呢, 不可能变成多个apple列和多个banana列, 此时可以指定怎么选择

Polars提供了以下几种聚合方式:

  • first
  • last
  • sum
  • min
  • max
  • mean
  • median
  • len

我们使用sum来说明如何使用

1import polars as pl
2
3df = pl.DataFrame({
4    "month": ["Jan", "Jan","Jan", "Feb", "Feb","Feb"],
5    "product": ["apple", "banana","apple", "apple", "banana","banana"],
6    "sales": [100, 200,300, 150, 250,400]
7})
8
9print(df)

可以看到Jan有两个apple, Feb有两个banana

1shape: (6, 3)
2┌───────┬─────────┬───────┐
3│ month ┆ product ┆ sales │
4│ ---   ┆ ---     ┆ ---   │
5│ str   ┆ str     ┆ i64   │
6╞═══════╪═════════╪═══════╡
7│ Jan   ┆ apple   ┆ 100   │
8│ Jan   ┆ banana  ┆ 200   │
9│ Jan   ┆ apple   ┆ 300   │
10│ Feb   ┆ apple   ┆ 150   │
11│ Feb   ┆ banana  ┆ 250   │
12│ Feb   ┆ banana  ┆ 400   │
13└───────┴─────────┴───────┘

接下来指定参数aggregate_function="sum", 可以看到最后嗯结果中400是100+300来的, 650是250+400来的

1res = df.pivot("product",index="month",values="sales", aggregate_function="sum")
2print(res)
1shape: (2, 3)
2┌───────┬───────┬────────┐
3│ month ┆ apple ┆ banana │
4│ ---   ┆ ---   ┆ ---    │
5│ str   ┆ i64   ┆ i64    │
6╞═══════╪═══════╪════════╡
7│ Jan   ┆ 400   ┆ 200    │
8│ Feb   ┆ 150   ┆ 650    │
9└───────┴───────┴────────┘

多列透视

刚才的例子有点简单, 我们只考虑了一列, 下面是多列透视

1df = pl.DataFrame({
2	"A":["a","b","a","a","b","a"],
3	"B":[1,3,5,1,3,5],
4	"C":["C","C","C","D","D","D"],
5	"value":[10,11,12,2,4,6]
6})
7print(df)
1shape: (6, 4)
2┌─────┬─────┬─────┬───────┐
3│ A   ┆ B   ┆ C   ┆ value │
4│ --- ┆ --- ┆ --- ┆ ---   │
5│ str ┆ i64 ┆ str ┆ i64   │
6╞═════╪═════╪═════╪═══════╡
7│ a   ┆ 1   ┆ C   ┆ 10    │
8│ b   ┆ 3   ┆ C   ┆ 11    │
9│ a   ┆ 5   ┆ C   ┆ 12    │
10│ a   ┆ 1   ┆ D   ┆ 2     │
11│ b   ┆ 3   ┆ D   ┆ 4     │
12│ a   ┆ 5   ┆ D   ┆ 6     │
13└─────┴─────┴─────┴───────┘

我们想把C这一列转换为列名, 然后按照A和B作为唯一行索引, 最后的值是value

1res = df.pivot(
2            on=["C"],         # on指定透视列, 就是把这一列的值给展开成列名
3            index=["A","B"],  # index指定行索引
4            values=["value"]) # values指定透视列的值
5print(res)

观察上面的数据, 有:

  • (a,1) -> (C,10),(D,2)
  • (b,3) -> (C,11),(D,4)
  • (a,5) -> (C,12),(D,6)

然后把箭头右边的数据顺时针旋转90度, 就可以得到下面的结果, 耐心比对, 就知道透视是如何工作的了

1shape: (3, 4)
2┌─────┬─────┬─────┬─────┐
3│ A   ┆ B   ┆ C   ┆ D   │
4│ --- ┆ --- ┆ --- ┆ --- │
5│ str ┆ i64 ┆ i64 ┆ i64 │
6╞═════╪═════╪═════╪═════╡
7│ a   ┆ 1   ┆ 10  ┆ 2   │
8│ b   ┆ 3   ┆ 11  ┆ 4   │
9│ a   ┆ 5   ┆ 12  ┆ 6   │
10└─────┴─────┴─────┴─────┘

惰性模式

Polars的LazyFrame在收集数据之前需要静态了解计算的模式, 但是由于数据透视表的输出模式依赖于数据(不能直接确定), 因此在不运行查询的情况下无法确定模式

如果想在惰性模式下使用pivot, 就需要先collect, 或者在知道哪些值是唯一值得情况下使用group_byagg

1res = (df
2	    .lazy().collect()
3        .pivot("product",index="month",values="sales",aggregate_function="sum")
4        .lazy()
5        .collect()
6)
7print(res)
1shape: (2, 3)
2┌───────┬───────┬────────┐
3│ month ┆ apple ┆ banana │
4│ ---   ┆ ---   ┆ ---    │
5│ str   ┆ i64   ┆ i64    │
6╞═══════╪═══════╪════════╡
7│ Jan   ┆ 400   ┆ 200    │
8│ Feb   ┆ 150   ┆ 650    │
9└───────┴───────┴────────┘