今天我們來說明如何使用pl.DataFrame.pivot()、pl.DataFrame.unpivot()及pl.DataFrame.unstack()。
本日大綱如下:
pl.DataFrame.pivot()
pl.DataFrame.unpivot()
pl.DataFrame.unstack()
codepanda
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 │
└──────┴──────┴──────┴──────┘
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 │
└──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘
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 │
└──────┴──────┴──────┘
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 │
└────────┴────────────┘
codepanda
pl.DataFrame.unpivot()
的函數為pd.DataFrame.melt()。註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)
)