
本文详细介绍了在polars中如何将包含列表的数据框列进行复杂重塑。通过结合`unpivot`、`list.to_Struct`和`unnest`操作,可以将原始数据框的列名转换为新列的值,并将列表元素展开为多个独立的列,从而实现数据从宽格式到长格式再到特定宽格式的灵活转换,极大地简化了数据处理流程。
在数据分析和处理中,我们经常会遇到需要对数据框的结构进行复杂重塑的场景。特别是在处理包含列表(List)类型数据的列时,如何将这些列表元素有效地展开为独立的列,并同时重构数据框的整体布局,是Polars用户面临的常见挑战。本教程将详细演示如何利用Polars的强大功能,通过一系列链式操作,将一个包含列表列的数据框转换为一个更易于分析的宽格式数据框。
初始数据框结构
假设我们有一个Polars数据框,其中包含多个列,每个列的值都是一个列表。
import polars as pl df = pl.DataFrame({ "foo": [[1, 2, 3], [7, 8, 9]], "bar": [[4, 5, 6], [1, 0, 1]] }) print("原始数据框:") print(df)
输出:
原始数据框: shape: (2, 2) ┌─────┬─────┐ │ foo ┆ bar │ │ --- ┆ --- │ │ list[i64] ┆ list[i64] │ ╞═════╪═════╡ │ [1, 2, 3] ┆ [4, 5, 6] │ │ [7, 8, 9] ┆ [1, 0, 1] │ └─────┴─────┘
我们的目标是将这个数据框转换为以下结构:
shape: (4, 4) ┌──────┬────────┬────────┬────────┐ │ Name ┆ Value0 ┆ Value1 ┆ Value2 │ │ --- ┆ --- ┆ --- ┆ --- │ │ str ┆ i64 ┆ i64 ┆ i64 │ ╞══════╪════════╪════════╪════════╡ │ foo ┆ 1 ┆ 2 ┆ 3 │ │ foo ┆ 7 ┆ 8 ┆ 9 │ │ bar ┆ 4 ┆ 5 ┆ 6 │ │ bar ┆ 1 ┆ 0 ┆ 1 │ └──────┴────────┴────────┴────────┘
可以看到,原始的列名(foo, bar)变成了新列 Name 的值,而每个列表中的元素则被展开成了 Value0, Value1, Value2 等独立的列。
逐步实现数据框重塑
要实现上述转换,我们需要执行以下三个关键步骤:
1. 使用 unpivot 将列名转换为值
unpivot 操作(在其他库中也常被称为 melt)用于将数据框的“宽”格式转换为“长”格式。它会将指定列的列名转换为一个新列的值,并将这些列的对应值放入另一个新列。
在这个例子中,我们将 foo 和 bar 列进行 unpivot 操作。variable_name=”Name” 参数指定了存储原始列名的新列的名称,而默认情况下,原始列的值会存储在一个名为 value 的新列中。
# 步骤 1: unpivot df_unpivoted = df.unpivot(variable_name="Name") print("n步骤 1: unpivot 后的数据框:") print(df_unpivoted)
输出:
步骤 1: unpivot 后的数据框: shape: (4, 2) ┌──────┬───────────┐ │ Name ┆ value │ │ --- ┆ --- │ │ str ┆ list[i64] │ ╞══════╪═══════════╡ │ foo ┆ [1, 2, 3] │ │ foo ┆ [7, 8, 9] │ │ bar ┆ [4, 5, 6] │ │ bar ┆ [1, 0, 1] │ └──────┴───────────┘
现在,我们有了一个 Name 列(包含 foo 和 bar)和一个 value 列(包含原始的列表数据)。
2. 使用 list.to_struct 将列表转换为结构体
接下来,我们需要将 value 列中的每个列表转换为一个结构体(Struct)。结构体是一种复合数据类型,可以包含多个命名字段。这为我们将列表中的元素映射到独立的列提供了中间步骤。
pl.col(“value”).list.to_struct(fields=Lambda x : f”Value{x}”) 这行代码做了几件事:
- pl.col(“value”): 选中 value 列。
- .list.to_struct(): 对列表列应用 to_struct 方法。
- fields=lambda x : f”Value{x}”: 这是关键部分。它定义了结构体中每个字段的名称。lambda x : f”Value{x}” 是一个匿名函数,它会为列表中的每个元素生成一个字段名,例如 Value0, Value1, Value2。x 在这里代表列表元素的索引。
# 步骤 2: 将列表转换为结构体 df_struct = df_unpivoted.with_columns( pl.col("value").list.to_struct(fields=lambda x : f"Value{x}") ) print("n步骤 2: 列表转换为结构体后的数据框:") print(df_struct)
输出:
步骤 2: 列表转换为结构体后的数据框: shape: (4, 2) ┌──────┬────────────────────┐ │ Name ┆ value │ │ --- ┆ --- │ │ str ┆ struct[3] │ ╞══════╪════════════════════╡ │ foo ┆ {1,2,3} │ │ foo ┆ {7,8,9} │ │ bar ┆ {4,5,6} │ │ bar ┆ {1,0,1} │ └──────┴────────────────────┘
现在 value 列的数据类型变为了 struct[3],其中包含了三个字段。
3. 使用 unnest 展开结构体列
最后一步是使用 unnest 操作。unnest 会将一个结构体列中的每个字段展开为数据框中的独立列。
# 步骤 3: 展开结构体列 df_final = df_struct.unnest("value") print("n步骤 3: 展开结构体列后的最终数据框:") print(df_final)
输出:
步骤 3: 展开结构体列后的最终数据框: shape: (4, 4) ┌──────┬────────┬────────┬────────┐ │ Name ┆ Value0 ┆ Value1 ┆ Value2 │ │ --- ┆ --- ┆ --- ┆ --- │ │ str ┆ i64 ┆ i64 ┆ i64 │ ╞══════╪════════╪════════╪════════╡ │ foo ┆ 1 ┆ 2 ┆ 3 │ │ foo ┆ 7 ┆ 8 ┆ 9 │ │ bar ┆ 4 ┆ 5 ┆ 6 │ │ bar ┆ 1 ┆ 0 ┆ 1 │ └──────┴────────┴────────┴────────┘
至此,我们成功地将原始数据框重塑为所需的格式。
完整代码示例
将上述三个步骤链式组合起来,可以得到一个简洁高效的解决方案:
import polars as pl df = pl.DataFrame({ "foo": [[1, 2, 3], [7, 8, 9]], "bar": [[4, 5, 6], [1, 0, 1]] }) output_df = ( df .unpivot(variable_name="Name") .with_columns(pl.col("value").list.to_struct(fields=lambda x : f"Value{x}")) .unnest("value") ) print("n最终重塑后的数据框:") print(output_df)
注意事项与总结
- 链式操作的优势: Polars 的表达式系统允许我们将多个操作链式调用,这不仅使代码更简洁,而且由于Polars的优化执行,通常效率更高。
- fields 参数的灵活性: list.to_struct 中的 fields 参数非常灵活,可以是一个字符串列表,也可以是一个生成字段名的函数(如本例所示)。根据具体需求选择合适的方式。
- 数据类型一致性: 确保列表中的所有子元素具有相同的数据类型,否则 to_struct 可能会遇到类型推断问题。
- 适用于多种场景: 这种组合操作不仅适用于将列表展开为列,还可以作为更复杂数据重塑和特征工程的基础。例如,如果列表长度不固定,to_struct 可能会用 NULL 填充较短的列表以匹配最长列表的结构。
通过掌握 unpivot、list.to_struct 和 unnest 这三个强大的Polars操作,您将能够高效地处理和重塑包含列表数据的复杂数据框,为后续的数据分析和建模工作奠定坚实基础。


