iT邦幫忙

2025 iThome 鐵人賽

DAY 16
0
Software Development

Polars熊霸天下系列 第 16

[Day16] - pl.DataFrame.pivot()、pl.DataFrame.unpivot()及pl.DataFrame.unstack()

  • 分享至 

  • xImage
  •  

今天我們來說明如何使用pl.DataFrame.pivot()pl.DataFrame.unpivot()pl.DataFrame.unstack()

本日大綱如下:

  1. 本日引入模組及準備工作
  2. pl.DataFrame.pivot()
  3. pl.DataFrame.unpivot()
  4. pl.DataFrame.unstack()
  5. codepanda

0. 本日引入模組及準備工作

import polars as pl
from polars import selectors as cs

df = pl.DataFrame(
    {
        "col1": ["A", "A", "A", "B", "B", "B"],
        "col2": ["C", "D", "C", "C", "D", "C"],
        "col3": [1, 2, 3, 4, 5, 6],
        "col4": [11, 12, 13, 14, 15, 16],
    }
)
shape: (6, 4)
┌──────┬──────┬──────┬──────┐
│ col1 ┆ col2 ┆ col3 ┆ col4 │
│ ---  ┆ ---  ┆ ---  ┆ ---  │
│ str  ┆ str  ┆ i64  ┆ i64  │
╞══════╪══════╪══════╪══════╡
│ A    ┆ C    ┆ 1    ┆ 11   │
│ A    ┆ D    ┆ 2    ┆ 12   │
│ A    ┆ C    ┆ 3    ┆ 13   │
│ B    ┆ C    ┆ 4    ┆ 14   │
│ B    ┆ D    ┆ 5    ┆ 15   │
│ B    ┆ C    ┆ 6    ┆ 16   │
└──────┴──────┴──────┴──────┘

1. pl.DataFrame.pivot()

pl.DataFrame.pivot()可以讓我們建構類似Excel的表格,其重要參數如下:

  • on=為其內各元素會做為新列名之列。
  • index=為索引列。
  • values=為填入表格之列。
  • aggregate_function=為針對values=所進行的運算。

我們直接從範例來學習其用法:

(
    df.pivot(
        on="col2", index="col1", values="col3", aggregate_function="max"
    )
)
shape: (2, 3)
┌──────┬─────┬─────┐
│ col1 ┆ C   ┆ D   │
│ ---  ┆ --- ┆ --- │
│ str  ┆ i64 ┆ i64 │
╞══════╪═════╪═════╡
│ A    ┆ 3   ┆ 2   │
│ B    ┆ 6   ┆ 5   │
└──────┴─────┴─────┘

這段程式碼可以解讀為:

  • 指定on=為「"col2"」列,其中每個元素都會是新列名(「"C"」及「"D"」)。
  • 指定index=為「"col1"」列,每一行將呈現一個其中的元素(「"A"」及「"B"」)。
  • 指定values=為「"col3"」列,且指定aggregate_function=為「"max"」,代表以「"col3"」列最大值為聚合目標。

由於aggregate_function=僅提供八種常用聚合運算,如果您有其它想做的操作,可以使用pl.element()的語法來完成。舉例來說,下面這段程式碼(註1)指定aggregate_function=pl.element().max(),其效果等同於指定為「"max"」。

df2 = df.pivot(
    on="col2",
    index="col1",
    values="col3",
    aggregate_function=pl.element().max(),
)
shape: (2, 3)
┌──────┬─────┬─────┐
│ col1 ┆ C   ┆ D   │
│ ---  ┆ --- ┆ --- │
│ str  ┆ i64 ┆ i64 │
╞══════╪═════╪═════╡
│ A    ┆ 3   ┆ 2   │
│ B    ┆ 6   ┆ 5   │
└──────┴─────┴─────┘

此外,on=index=values=都可以接受一個以上的列,而且可以經由selectors來選擇。例如,使用cs.by_name()來同時選擇「"col1"」及「"col2"」列,做為index=的輸入:

(
    df.pivot(
        on="col4",
        index=cs.by_name("col1", "col2"),
        values="col3",
        aggregate_function="max",
    )
)
shape: (4, 8)
┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐
│ col1 ┆ col2 ┆ 11   ┆ 12   ┆ 13   ┆ 14   ┆ 15   ┆ 16   │
│ ---  ┆ ---  ┆ ---  ┆ ---  ┆ ---  ┆ ---  ┆ ---  ┆ ---  │
│ str  ┆ str  ┆ i64  ┆ i64  ┆ i64  ┆ i64  ┆ i64  ┆ i64  │
╞══════╪══════╪══════╪══════╪══════╪══════╪══════╪══════╡
│ A    ┆ C    ┆ 1    ┆ null ┆ 3    ┆ null ┆ null ┆ null │
│ A    ┆ D    ┆ null ┆ 2    ┆ null ┆ null ┆ null ┆ null │
│ B    ┆ C    ┆ null ┆ null ┆ null ┆ 4    ┆ null ┆ 6    │
│ B    ┆ D    ┆ null ┆ null ┆ null ┆ null ┆ 5    ┆ null │
└──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘

2. pl.DataFrame.unpivot()

pl.DataFrame.unpivot()pl.DataFrame.pivot()的反向操作,其重要參數如下:

  • on=為需要收集為一列(預設為「"variable"」)的列名。
  • index=為索引列。
  • variable_name=為所收集的"variable"」列之新列名。
  • value_name=為所收集數值之新列名。

但是需注意由於pl.DataFrame.pivot()進行了聚合運算,我們無法完全還原進行為原先的DataFrame。舉例來說,我們可以試圖還原將df2還原為df(註2):

  • 指定on=為「"C"」及「"D"」列。
  • 指定index=為「"col1"」列。
  • 指定variable_name=為「"col2"」。
  • 指定value_name=為「"col3"」。
(
    df2.unpivot(
        on=["C", "D"],
        index="col1",
        variable_name="col2",
        value_name="col3",
    ).sort("col3")
)
shape: (4, 3)
┌──────┬──────┬──────┐
│ col1 ┆ col2 ┆ col3 │
│ ---  ┆ ---  ┆ ---  │
│ str  ┆ str  ┆ i64  │
╞══════╪══════╪══════╡
│ A    ┆ D    ┆ 2    │
│ A    ┆ C    ┆ 3    │
│ B    ┆ D    ┆ 5    │
│ B    ┆ C    ┆ 6    │
└──────┴──────┴──────┘

可以看出還原後的DataFrame,除了本來就沒有被選擇到的「"col4"」外,還少了因為聚合操作而「消失」的兩行。

pl.DataFrame.unpivot()on=index=參數也都可以接受一個以上的列,而且可以經由selectors來選擇。例如使用cs.numeric()選中「"C"」及「"D"」列,做為on=的參數:

(
    df2.unpivot(
        on=cs.numeric(),
        index="col1",
        variable_name="col2",
        value_name="col3",
    ).sort("col3")
)
shape: (4, 3)
┌──────┬──────┬──────┐
│ col1 ┆ col2 ┆ col3 │
│ ---  ┆ ---  ┆ ---  │
│ str  ┆ str  ┆ i64  │
╞══════╪══════╪══════╡
│ A    ┆ D    ┆ 2    │
│ A    ┆ C    ┆ 3    │
│ B    ┆ D    ┆ 5    │
│ B    ┆ C    ┆ 6    │
└──────┴──────┴──────┘

3. pl.DataFrame.unstack()

pl.DataFrame.unstack()可以將長格式的pl.DataFrame轉變為寬格式的pl.DataFrame,但是並沒有進行聚合操作,僅是改變呈現維度。

pl.DataFrame.unstack()有四個參數,皆需要以關鍵字方式來指定參數:

  • step=代表要以多少行為單位。
  • how=代表操作方式。
  • columns=代表選定哪些列。
  • fill_values=代表遇到缺失值所需填補的值。

我們一樣從範例中來學習其用法。下面我們指定step=為3及columns=為「"col1"」列時,並分別觀察how=為「"vertical"」及「"horizontal"」的結果:

df.unstack(step=3, columns="col1", how="vertical")
shape: (3, 2)
┌────────┬────────┐
│ col1_0 ┆ col1_1 │
│ ---    ┆ ---    │
│ str    ┆ str    │
╞════════╪════════╡
│ A      ┆ B      │
│ A      ┆ B      │
│ A      ┆ B      │
└────────┴────────┘

how=為「"vertical"」時,「"col1"」列以三行為單位,順著垂直方向排列。

df.unstack(step=3, columns="col1", how="horizontal")
shape: (2, 3)
┌────────┬────────┬────────┐
│ col1_0 ┆ col1_1 ┆ col1_2 │
│ ---    ┆ ---    ┆ ---    │
│ str    ┆ str    ┆ str    │
╞════════╪════════╪════════╡
│ A      ┆ A      ┆ A      │
│ B      ┆ B      ┆ B      │
└────────┴────────┴────────┘

how=為「"horizontal"」時,「"col1"」列以三行為單位,順著水平方向排列。

最後我們展示指定step=為4、columns=為「"col1"」列及how=為「"vertical"」的結果:

df.unstack(step=4, columns="col1", how="vertical")
shape: (4, 2)
┌────────┬────────┐
│ col1_0 ┆ col1_1 │
│ ---    ┆ ---    │
│ str    ┆ str    │
╞════════╪════════╡
│ A      ┆ B      │
│ A      ┆ B      │
│ A      ┆ null   │
│ B      ┆ null   │
└────────┴────────┘

可以看出,當step=無法被行數整除時,會出現缺失值。此時,可以使用fill_values=指定填補值,如:

(
    df.unstack(
        step=4, columns="col1", how="vertical", fill_values="fill_value"
    )
)
shape: (4, 2)
┌────────┬────────────┐
│ col1_0 ┆ col1_1     │
│ ---    ┆ ---        │
│ str    ┆ str        │
╞════════╪════════════╡
│ A      ┆ B          │
│ A      ┆ B          │
│ A      ┆ fill_value │
│ B      ┆ fill_value │
└────────┴────────────┘

4. codepanda

備註

註1:其實pl.DataFrame.pivot()是一種特別的分組聚合模式,以下我們嘗試使用pl.DataFrame.group_by().agg()來達成一樣的效果,其啟發是來自pl.DataFrame.pivot()API文件的最後一個範例

(
    df.group_by("col1", maintain_order=True).agg(
        pl.col("col3").filter(pl.col("col2").eq("C")).max().alias("C"),
        pl.col("col3").filter(pl.col("col2").eq("D")).max().alias("D"),
    )
)

如果將列名以變數取代,可以寫為比較抽象的形式,如:

from polars.testing import assert_frame_equal

on, index, values = pl.col("col2"), pl.col("col1"), pl.col("col3")
unique_column_values = df.select(on.unique()).to_series().sort().to_list()
agg_func = lambda col: col.max()

df3 = df.group_by(index, maintain_order=True).agg(
    agg_func(values.filter(on.eq(value)).alias(value))
    for value in unique_column_values
)

assert_frame_equal(df2, df3)

這裡我們使用pl.testing.assert_frame_equal()來確認df2是否相等於df3。如果不相等的話,會回報AssertionError錯誤。

註2:嘗試使用Polars語法,達成與pl.DataFrame.unpivot()一致的效果,如:

index = "col1"

cols = df2.columns
cols.remove(index)

(
    df2.select(
        "col1",
        pl.repeat(cols, len(cols)).alias("col2"),
        pl.concat_list(pl.col("C", "D")).alias("col3"),
    )
    .explode("col2", "col3")
    .sort("col3")
)

如果將列名以變數取代,並將index_cols轉變為列表,則可以寫為比較抽象的形式,如:

index_cols = ["col1"]

on = df2.columns
for index_col in index_cols:
    on.remove(index_col)

variable_name, value_name = "col2", "col3"

(
    df2.select(
        "col1",
        pl.repeat(on, len(on)).alias(variable_name),
        pl.concat_list(pl.col(on)).alias(value_name),
    )
    .explode(variable_name, value_name)
    .sort(value_name)
)

Code

本日程式碼傳送門


上一篇
[Day15] - 排序
下一篇
[Day17] - 三種join
系列文
Polars熊霸天下20
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言