如何将含numpy数组的DataFrame转换为周期视角的交付视图
问题背景与需求
我在供应链场景中规划交付安排,收货日期和交付数量存在随机性(提前/延迟到货、订单录入错误等),因此模拟了多个场景(最多上百个)。初始数据以DataFrame形式存储,每行对应一个物料,reception_date和ordered_qty的列表元素分别对应各场景下的收货日期和数量:
import pandas as pd import numpy as np trace_size = 3 # 场景数量 df_initial = pd.DataFrame({ 'item_id': ['item_1', 'item_2'], 'reception_date': [[1, 2, 1], [1, 2, 1]], 'ordered_qty': [[40, 45, 39], [45, 39, 42]] })
初始数据展示:
| item_id | reception_date | ordered_qty |
|---|---|---|
| item_1 | [1, 2, 1] | [40, 45, 39] |
| item_2 | [1, 2, 1] | [45, 39, 42] |
需要将数据转换为按周期日期展示每个场景下的收货数量的格式,目标DataFrame如下:
| item_id | period_date | ordered_qty |
|---|---|---|
| item_1 | 0 | [0, 0, 0] |
| item_1 | 1 | [40, 0, 39] |
| item_1 | 2 | [0, 45, 0] |
| item_2 | 0 | [0, 0, 0] |
| item_2 | 1 | [45, 0, 42] |
| item_2 | 2 | [0, 39, 0] |
当前实现使用了for循环处理每个场景,但场景数量最多可达上百个,希望去掉循环提升效率。当前代码及结果如下:
df = pd.DataFrame(columns=['item_id', 'reception_date', 'ordered_qty']) for z in range(trace_size): df1 = df_initial.copy() df1['reception_date']=df_initial['reception_date'].apply(lambda x: x[z]) df1['ordered_qty']=df_initial['ordered_qty'].apply(lambda x: x[z]) df = pd.concat([df, df1]) df = df.groupby(['item_id', 'reception_date'], as_index=False).agg({'ordered_qty': list}) dyn = pd.DataFrame({'item_id': np.repeat(df_initial['item_id'].values, 3), 'period_date': np.tile(np.arange(0, 3), 2)}) df_final = dyn.merge(df.rename(columns={'reception_date': 'period_date'}), how='outer').fillna(0)
当前结果:
| item_id | period_date | ordered_qty |
|---|---|---|
| item_1 | 0 | 0 |
| item_1 | 1 | [40, 39] |
| item_1 | 2 | [45] |
| item_2 | 0 | 0 |
| item_2 | 1 | [45, 42] |
| item_2 | 2 | [39] |
无循环优化方案
以下是去掉for循环的高效实现,适合大规模场景的处理:
import pandas as pd import numpy as np trace_size = 3 period_range = np.arange(0, 3) # 固定时间范围0-2天 df_initial = pd.DataFrame({ 'item_id': ['item_1', 'item_2'], 'reception_date': [[1, 2, 1], [1, 2, 1]], 'ordered_qty': [[40, 45, 39], [45, 39, 42]] }) # 1. 将每个物料的场景数据展开为单独列,避免循环拼接 df_expanded = df_initial.assign( **{f"date_{i}": df_initial['reception_date'].apply(lambda x: x[i]) for i in range(trace_size)}, **{f"qty_{i}": df_initial['ordered_qty'].apply(lambda x: x[i]) for i in range(trace_size)} ) # 2. 生成所有物料+周期日期的笛卡尔积,确保覆盖所有日期 all_periods = pd.MultiIndex.from_product( [df_initial['item_id'], period_range], names=['item_id', 'period_date'] ).to_frame(index=False) # 3. 对每个场景匹配对应周期的数量,再合并为列表列 result_list = [] for i in range(trace_size): scenario_df = df_expanded[['item_id', f'date_{i}', f'qty_{i}']].rename( columns={f'date_{i}': 'period_date', f'qty_{i}': f'qty_scen_{i}'} ) # 匹配全周期表,无数据则填0 merged = all_periods.merge(scenario_df, on=['item_id', 'period_date'], how='left').fillna(0) result_list.append(merged[f'qty_scen_{i}']) # 4. 将各场景的数量列合并为列表,得到最终结果 df_final = all_periods.assign( ordered_qty=pd.concat(result_list, axis=1).apply(list, axis=1) ) print(df_final)
输出结果:
item_id period_date ordered_qty 0 item_1 0 [0.0, 0.0, 0.0] 1 item_1 1 [40.0, 0.0, 39.0] 2 item_1 2 [0.0, 45.0, 0.0] 3 item_2 0 [0.0, 0.0, 0.0] 4 item_2 1 [45.0, 0.0, 42.0] 5 item_2 2 [0.0, 39.0, 0.0]
优化说明
- 避免了逐个场景循环拼接DataFrame的操作,改为先展开所有场景数据为列,减少内存开销和循环耗时
- 通过笛卡尔积生成完整的物料-周期组合,确保每个日期都被覆盖
- 对每个场景单独匹配数量后合并为列表列,逻辑清晰且效率更高,适合上百个场景的规模
内容的提问来源于stack exchange,提问作者Martin D
相关产品推荐
相关产品推荐

