在熔化polars数据框后,如何不添加索引将其旋转回原始形态?

2 投票
2 回答
93 浏览
提问于 2025-04-14 15:38
import polars as pl

df = pl.DataFrame({
    'A': range(1,4),
    'B': range(1,4),
    'C': range(1,4),
    'D': range(1,4)
})

print(df)
shape: (3, 4)
┌─────┬─────┬─────┬─────┐
│ A   ┆ B   ┆ C   ┆ D   │
│ --- ┆ --- ┆ --- ┆ --- │
│ i64 ┆ i64 ┆ i64 ┆ i64 │
╞═════╪═════╪═════╪═════╡
│ 1   ┆ 1   ┆ 1   ┆ 1   │
│ 2   ┆ 2   ┆ 2   ┆ 2   │
│ 3   ┆ 3   ┆ 3   ┆ 3   │
└─────┴─────┴─────┴─────┘
df_melt = df.melt(
    variable_name="recipe",
    value_name="revenue")

print(df_melt)
shape: (12, 2)
┌────────┬─────────┐
│ recipe ┆ revenue │
│ ---    ┆ ---     │
│ str    ┆ i64     │
╞════════╪═════════╡
│ A      ┆ 1       │
│ A      ┆ 2       │
│ A      ┆ 3       │
│ B      ┆ 1       │
│ B      ┆ 2       │
│ …      ┆ …       │
│ C      ┆ 2       │
│ C      ┆ 3       │
│ D      ┆ 1       │
│ D      ┆ 2       │
│ D      ┆ 3       │
└────────┴─────────┘

看起来我需要添加一个索引,才能把 df_melt 转换回原来的 df 形式?难道没有办法在不添加索引的情况下,直接转换一个 polars 数据框吗?

df_melt = df_melt.with_columns(index=pl.col("revenue").cum_count().over("recipe"))

df_melt.pivot(
    index='index',
    columns='recipe',
    values='revenue',
    aggregate_function='first'
)
shape: (3, 5)
┌───────┬─────┬─────┬─────┬─────┐
│ index ┆ A   ┆ B   ┆ C   ┆ D   │
│ ---   ┆ --- ┆ --- ┆ --- ┆ --- │
│ u32   ┆ i64 ┆ i64 ┆ i64 ┆ i64 │
╞═══════╪═════╪═════╪═════╪═════╡
│ 1     ┆ 1   ┆ 1   ┆ 1   ┆ 1   │
│ 2     ┆ 2   ┆ 2   ┆ 2   ┆ 2   │
│ 3     ┆ 3   ┆ 3   ┆ 3   ┆ 3   │
└───────┴─────┴─────┴─────┴─────┘

在 R 语言中,我可以在不使用索引的情况下完成类似的 melt 和 pivot 操作,我希望在 Python 中也能实现同样的功能。

df_pandas = df.to_pandas()
library(tidyverse)
library(reticulate)

df_long <-
    py$df_pandas |>
    pivot_longer(
        everything(),
        names_to = 'recipe',
        values_to = 'value'
    )

df_long |>
    pivot_wider(
        names_from='recipe',
        values_from='value'
    ) |>
    unnest(cols = c(A,B,C,D))

2 个回答

0

你不能完全避免使用 pivot 的索引,但你可以做很多其他的事情,来实现类似 pivot 的效果,而不需要索引。其实,直接添加一个明确的索引可能更好,或者更理想的是避免使用 melt。不过,如果你必须使用 melt,这里有一个方法可以做到,而不需要添加索引。

df_melt = df.melt()
# I skip naming the melt columns since we're round tripping them away.
(
    df_melt
    .group_by('variable', maintain_order=True)
    .agg(pl.col('value'))
    .with_columns(
        pl.col('value').list.to_struct()
    )
    .unnest('value')
    .pipe(lambda zz: (
        zz
        .select(pl.col("^field.*$"))
        .transpose(column_names=zz['variable'])
    ))
)
shape: (3, 4)
┌─────┬─────┬─────┬─────┐
│ A   ┆ B   ┆ C   ┆ D   │
│ --- ┆ --- ┆ --- ┆ --- │
│ i64 ┆ i64 ┆ i64 ┆ i64 │
╞═════╪═════╪═════╪═════╡
│ 1   ┆ 1   ┆ 1   ┆ 1   │
│ 2   ┆ 2   ┆ 2   ┆ 2   │
│ 3   ┆ 3   ┆ 3   ┆ 3   │
└─────┴─────┴─────┴─────┘

这个 pipe 确实有点尴尬,因为 transpose 没有内置的方法可以把输入的第一列转换成输出的列名。

2

看起来我需要添加一个索引,才能把 df_melt 转回到原来的 df 形式?

否则,系统怎么知道 df_melt 中的 revenue=1 的条目都是在同一行,而不是说 recipe=A 的收入是 2,recipe=B 的收入是 3,等等呢?你是根据你定义的 df 来推测的,但仅从你问题中定义的 df_melt 是无法做到这一点的。

如果这是个必要条件,我建议你在使用 melt 之前,先给 df 添加一个索引,这样 df_melt 就能携带这些信息:

>>> df_melt=df.with_row_index().melt(id_vars=["index"], variable_name="recipe",value_name="revenue")  
>>> df_melt
shape: (12, 3)
┌───────┬────────┬─────────┐
│ index ┆ recipe ┆ revenue │
│ ---   ┆ ---    ┆ ---     │
│ u32   ┆ str    ┆ i64     │
╞═══════╪════════╪═════════╡
│ 0     ┆ A      ┆ 1       │
│ 1     ┆ A      ┆ 2       │
│ 2     ┆ A      ┆ 3       │
│ 0     ┆ B      ┆ 1       │
│ 1     ┆ B      ┆ 2       │
│ …     ┆ …      ┆ …       │
│ 1     ┆ C      ┆ 2       │
│ 2     ┆ C      ┆ 3       │
│ 0     ┆ D      ┆ 1       │
│ 1     ┆ D      ┆ 2       │
│ 2     ┆ D      ┆ 3       │
└───────┴────────┴─────────┘

然后,透视操作可以利用 index 列来重建原来的数据。

撰写回答