如何使用Pandas对DataFrame每行多组O类关联字段按名称排序对齐
问题描述
现有数据集固定包含Date、Product两列,其余为多组选项字段,每组选项对应Name、Price、Qty三个属性,按O1_*、O2_*、O3_*……规则命名。原始数据存在行内错位问题:同组的三个属性不会固定落在对应编号的O列下,部分高编号O列可能为空。
需要实现逐行对齐逻辑:以每行所有选项的Name字段为排序依据,排序后将同组的Price、Qty跟随对应Name同步移动,按名称排序后依次填入O1、O2、O3……对应列位,保证同选项三个字段归属一致。
原始数据样例
Date Product O1_Name O1_Price O1_Qty O2_Name O2_Price O2_Qty 05-12-2021 MK Widget1 0.99 23 Widget2 1.50 80 05-12-2021 MK Widget2 1.50 85 Widget1 0.99 25 05-11-2021 MK Widget1 0.99 28 Widget2 1.50 85 05-10-2021 MK Widget1 0.99 31 Widget2 1.50 95 05-13-2021 PS WidgetA 0.52 49 WidgetB 0.86 43 05-12-2021 PS WidgetA 0.52 53 WidgetB 0.86 43 05-10-2021 PS WidgetB 0.85 66 WidgetA 0.58 60 05-13-2021 AY WidgetZ 0.15 87 05-12-2021 AY WidgetZ 0.15 88
期望输出样例
Date Product O1_Name O1_Price O1_Qty O2_Name O2_Price O2_Qty 05-12-2021 MK Widget1 0.99 23 Widget2 1.50 80 05-12-2021 MK Widget1 0.99 25 Widget2 1.50 85 05-11-2021 MK Widget1 0.99 28 Widget2 1.50 85 05-10-2021 MK Widget1 0.99 31 Widget2 1.50 95 05-13-2021 PS WidgetA 0.52 49 WidgetB 0.86 43 05-12-2021 PS WidgetA 0.52 53 WidgetB 0.86 43 05-10-2021 PS WidgetA 0.58 60 WidgetB 0.85 66 05-13-2021 AY WidgetZ 0.15 87 05-12-2021 AY WidgetZ 0.15 88
操作参考示意图:
Pandas实现方案
核心思路是先拆分固定列和选项列,逐行将零散的选项字段重组为(Name, Price, Qty)的元组列表,过滤空值后按Name排序,再展开回原列结构即可,自动兼容O3、O4等任意数量的选项列。
完整可运行代码
import pandas as pd import numpy as np # 此处替换为你自己的数据读取逻辑,比如pd.read_excel、pd.read_csv raw_data = [ ["05-12-2021", "MK", "Widget1", 0.99, 23, "Widget2", 1.50, 80], ["05-12-2021", "MK", "Widget2", 1.50, 85, "Widget1", 0.99, 25], ["05-11-2021", "MK", "Widget1", 0.99, 28, "Widget2", 1.50, 85], ["05-10-2021", "MK", "Widget1", 0.99, 31, "Widget2", 1.50, 95], ["05-13-2021", "PS", "WidgetA", 0.52, 49, "WidgetB", 0.86, 43], ["05-12-2021", "PS", "WidgetA", 0.52, 53, "WidgetB", 0.86, 43], ["05-10-2021", "PS", "WidgetB", 0.85, 66, "WidgetA", 0.58, 60], ["05-13-2021", "AY", "WidgetZ", 0.15, 87, np.nan, np.nan, np.nan], ["05-12-2021", "AY", "WidgetZ", 0.15, 88, np.nan, np.nan, np.nan], ] columns = ["Date", "Product", "O1_Name", "O1_Price", "O1_Qty", "O2_Name", "O2_Price", "O2_Qty"] df = pd.DataFrame(raw_data, columns=columns) # 拆分固定列和选项列 fixed_cols = ["Date", "Product"] opt_cols = [c for c in df.columns if c not in fixed_cols] # 自动识别所有O开头的选项编号,兼容O3/O4等任意数量选项 opt_nums = sorted(list(set([c.split("_")[0] for c in opt_cols])), key=lambda x: int(x[1:])) fixed_df = df[fixed_cols].copy() opt_df = df[opt_cols].copy() # 逐行对齐选项 def align_row_opts(row): # 组装(Name, Price, Qty)元组,跳过Name为空的无效选项 opt_tuples = [] for o_num in opt_nums: name = row[f"{o_num}_Name"] if pd.isna(name) or str(name).strip() == "": continue price = row[f"{o_num}_Price"] qty = row[f"{o_num}_Qty"] opt_tuples.append((name, price, qty)) # 按Name字段排序 opt_tuples.sort(key=lambda x: x[0]) # 补全空位,匹配原列长度 while len(opt_tuples) < len(opt_nums): opt_tuples.append((np.nan, np.nan, np.nan)) # 展开为一维数组,对应原列顺序 res = [] for i, o_num in enumerate(opt_nums): res.extend([opt_tuples[i][0], opt_tuples[i][1], opt_tuples[i][2]]) return res # 应用处理逻辑,拼接回最终结果 aligned_opt = opt_df.apply(align_row_opts, axis=1, result_type="expand") aligned_opt.columns = opt_cols result = pd.concat([fixed_df, aligned_opt], axis=1) print(result)
方案说明
- 无需手动指定选项数量,代码自动识别所有
O*_Name/Price/Qty格式的列,支持任意多组选项场景 - 自动跳过Name为空的无效选项,排序后空值自动填充到序号靠后的O列,不会打乱有效选项顺序
- 处理过程中始终将Price、Qty和对应Name绑定移动,不会出现字段归属错配
- 代码运行后输出结果和给出的期望样例完全一致
内容的提问来源于stack exchange,提问作者SG Fresh Idea
相关产品推荐
相关产品推荐

