
本教程详细介绍了如何将一个具有大量列的宽格式pandas DataFrame重塑为更易读的垂直长格式。文章提供了两种核心方法:当总列数是目标列数的完美倍数时,可以使用numpy的`reshape`功能高效处理;当总列数不是目标列数的完美倍数时,则采用Pandas的`MultiIndex`和`stack`操作灵活应对。通过具体代码示例和注意事项,帮助读者掌握数据重塑技巧,提升数据处理效率和可读性。
数据重塑:将宽格式DataFrame转换为长格式
在数据分析和处理中,我们经常会遇到包含大量列的“宽格式”DataFrame。这种格式在某些情况下可能难以阅读和分析,例如,当逻辑上相关的列被水平地分散在整个数据集中时。本教程将指导您如何将这类宽格式DataFrame,按照每N列一组的方式,重塑为一个更紧凑、更易于理解的“长格式”DataFrame。我们将探讨两种主要场景:当总列数是目标分组列数的完美倍数时,以及当它不是完美倍数时。
场景一:总列数是目标分组列数的完美倍数
当原始DataFrame的总列数能够被目标分组列数(例如,每6列一组)整除时,使用NumPy的reshape功能是最高效且简洁的方法。
1.1 方法概述
这种方法的核心是将DataFrame转换为NumPy数组,然后利用numpy.reshape()将其重塑为新的维度。由于我们希望最终的DataFrame有固定数量的列(例如6列),我们可以指定目标列数为6,并让NumPy自动推断行数。
1.2 示例代码
假设我们有一个包含606列的DataFrame,并且我们希望每6列为一组,将其转换为一个具有6列的新DataFrame。
import pandas as pd import numpy as np # 模拟一个宽格式DataFrame # 实际应用中,这里会是 df = pd.read_csv("groups.csv") np.random.seed(123) # 假设原始DataFrame有3行12列,每6列一组,目标DataFrame有6列 df = pd.DataFrame(np.random.randint(10, size=(3, 12))) print("原始DataFrame:") print(df) # 预期输出列名 target_columns = ['GroupA', 'GroupB', 'GroupC', 'GroupD', 'GroupE', 'GroupF'] # 检查总列数是否为目标分组列数的倍数 print(f"n原始DataFrame列数: {len(df.columns)}") print(f"列数 % 6: {len(df.columns) % 6}") if len(df.columns) % 6 == 0: # 将DataFrame转换为NumPy数组,然后重塑 # -1 表示让NumPy自动计算行数,6 表示目标DataFrame的列数 df_target = pd.DataFrame(df.to_numpy().reshape(-1, 6), columns=target_columns) print("n重塑后的DataFrame (使用 numpy.reshape):") print(df_target) else: print("n原始DataFrame的列数不是6的倍数,此方法不适用。")
代码解析:
- df.to_numpy(): 将Pandas DataFrame转换为底层的NumPy数组。这会移除列名,只保留数据值。
- .reshape(-1, 6): 这是关键步骤。它将NumPy数组重塑为一个新的形状。
- -1: 告诉NumPy根据数组中的元素总数和指定的其他维度(这里是6列)自动计算新的行数。
- 6: 指定新DataFrame的列数。
- pd.DataFrame(…, columns=target_columns): 将重塑后的NumPy数组转换回Pandas DataFrame,并指定新的列名。
输出示例:
原始DataFrame: 0 1 2 3 4 5 6 7 8 9 10 11 0 2 2 6 1 3 9 6 1 0 1 9 0 1 0 9 3 4 0 0 4 1 7 3 2 4 2 7 2 4 8 0 7 9 3 4 6 1 5 原始DataFrame列数: 12 列数 % 6: 0 重塑后的DataFrame (使用 numpy.reshape): GroupA GroupB GroupC GroupD GroupE GroupF 0 2 2 6 1 3 9 1 6 1 0 1 9 0 2 0 9 3 4 0 0 3 4 1 7 3 2 4 4 7 2 4 8 0 7 5 9 3 4 6 1 5
注意事项:
- 此方法要求原始DataFrame的所有数据类型都是兼容的,因为NumPy数组通常是同构的。如果DataFrame包含混合数据类型,to_numpy()可能会将其转换为Object类型。
- 此方法不保留原始的行索引信息,如果需要,需在重塑前进行保存或后续处理。
场景二:总列数不是目标分组列数的完美倍数
当原始DataFrame的总列数不能被目标分组列数整除时(例如,有5252列,但我们仍想每6列一组),numpy.reshape将无法直接使用。此时,我们可以利用Pandas的MultiIndex和stack操作来灵活处理。
2.1 方法概述
这种方法通过创建一个多级列索引来逻辑地分组原始列,然后使用stack()方法将这些分组转换为行。对于不完整的最后一组,stack()会自动填充NaN。
2.2 示例代码
假设我们有一个包含10列的DataFrame,但我们仍然希望每6列为一组进行重塑。
import pandas as pd import numpy as np # 模拟一个宽格式DataFrame np.random.seed(123) # 假设原始DataFrame有3行10列,每6列一组,目标DataFrame有6列 df_imperfect = pd.DataFrame(np.random.randint(10, size=(3, 10))) print("原始DataFrame (列数非6的倍数):") print(df_imperfect) # 预期输出列名 target_columns = ['GroupA', 'GroupB', 'GroupC', 'GroupD', 'GroupE', 'GroupF'] group_size = 6 print(f"n原始DataFrame列数: {len(df_imperfect.columns)}") print(f"列数 % {group_size}: {len(df_imperfect.columns) % group_size}") # 创建一个用于生成MultiIndex的数组 a = np.arange(len(df_imperfect.columns)) # 使用 set_axis 和 MultiIndex 进行重塑 # a % group_size: 生成第一级索引,表示组内位置 (0到5) # a // group_size: 生成第二级索引,表示是第几组 (0, 1, ...) df_target_imperfect = (df_imperfect.set_axis([a % group_size, a // group_size], axis=1) .stack(level=0) # 将第一级索引(组内位置)堆叠为行 .set_axis(target_columns, axis=1) # 设置新的列名 .reset_index(drop=True)) # 重置索引,移除MultiIndex的层级 print("n重塑后的DataFrame (使用 Pandas MultiIndex 和 stack):") print(df_target_imperfect)
代码解析:
- a = np.arange(len(df_imperfect.columns)): 创建一个与列数等长的整数序列,用于生成索引。
- df_imperfect.set_axis([a % group_size, a // group_size], axis=1):
- a % group_size: 计算每个原始列在目标组中的位置(0, 1, 2, 3, 4, 5, 0, 1, …)。这将成为MultiIndex的第一层。
- a // group_size: 计算每个原始列属于哪一个目标组(0, 0, 0, 0, 0, 0, 1, 1, …)。这将成为MultiIndex的第二层。
- set_axis(…, axis=1): 将生成的这两个数组作为新的列索引,创建多级列索引。
- .stack(level=0): 这是核心的重塑操作。它将MultiIndex的第一层(即组内位置)从列级别堆叠到行级别。这意味着每个原始行的数据,将根据其在组中的位置,被转换为多行。
- .set_axis(target_columns, axis=1): 堆叠后,列名会变成默认的数字索引,我们将其重新设置为目标列名。
- .reset_index(drop=True): stack()操作会引入新的索引层级。reset_index(drop=True)用于将这些层级移除,并生成一个干净的默认整数索引。
输出示例:
原始DataFrame (列数非6的倍数): 0 1 2 3 4 5 6 7 8 9 0 2 2 6 1 3 9 6 1 0 1 1 9 0 0 9 3 4 0 0 4 1 2 7 3 2 4 7 2 4 8 0 7 原始DataFrame列数: 10 列数 % 6: 4 重塑后的DataFrame (使用 Pandas MultiIndex 和 stack): GroupA GroupB GroupC GroupD GroupE GroupF 0 2 2 6 1 3.0 9.0 1 6 1 0 1 NaN NaN 2 9 0 0 9 3.0 4.0 3 0 0 4 1 NaN NaN 4 7 3 2 4 7.0 2.0 5 4 8 0 7 NaN NaN
注意事项:
- 当最后一组的列数不足group_size时,stack()会自动填充NaN值。您可能需要根据具体需求处理这些NaN值(例如,使用fillna()或dropna())。
- 此方法比numpy.reshape更灵活,但对于非常大的数据集,性能可能会略低于纯NumPy方法。
- 同样,原始行索引信息不会直接保留。
总结
本教程介绍了两种将宽格式DataFrame重塑为长格式的有效方法:
- numpy.reshape(-1, N): 适用于原始列数是目标分组列数N的完美倍数的情况。它直接操作底层NumPy数组,效率高,代码简洁。
- Pandas MultiIndex + stack(): 适用于原始列数不是目标分组列数N的完美倍数的情况。它利用Pandas强大的索引和重塑功能,能够灵活处理不完整的分组,并自动填充NaN。
选择哪种方法取决于您的具体数据特性和需求。在实际应用中,了解这两种方法可以帮助您更高效、更灵活地处理各种数据重塑任务。始终记住在重塑后检查数据类型和NaN值,以确保数据质量符合后续分析要求。


