
本文介绍一种高效、可复用的方法,将具有 MultiIndex 列(如 ('Widget A', 't1'))的 DataFrame 转换为按部件(Widget)分组、再按任务时序(t1, t2, ...)分层、最终每个任务包含 (machine_id, duration) 元组列表的三层嵌套结构。
本文介绍一种高效、可复用的方法,将具有 multiindex 列(如 `('widget a', 't1')`)的 dataframe 转换为按部件(widget)分组、再按任务时序(t1, t2, ...)分层、最终每个任务包含 `(machine_id, duration)` 元组列表的三层嵌套结构。
在工业调度、制造执行系统(MES)或资源分配建模中,常需将宽格式的多级索引 DataFrame(列名为 (widget_name, task_id))转化为便于算法消费的嵌套结构——例如用于约束编程、图搜索或启发式调度器的输入。本文提供一个简洁、健壮且可扩展的 Pandas 流水线式解决方案,无需显式循环,完全基于向量化操作与链式方法。
核心思路:熔解 → 清洗 → 构造 → 分组聚合
整个流程遵循“从宽到长、去空留值、构造元组、两级分组”四步原则:
-
重置索引并熔解(
reset_index().melt()):将行索引(如'm_1','m_2')转为普通列,再将所有(widget, task)多级列展开为长表,生成variable_0(widget)、variable_1(task)和value三列; -
剔除缺失值(
dropna(subset='value')):仅保留非空的资源分配记录; -
解析机器 ID 并构造元组(
assign(tmp=...)):利用str.split('_').str[1]提取m_1中的数字1,转换为整型,并与value组合成(machine_id, duration)元组; -
两级分组聚合(
groupby(['variable_0','variable_1']).apply(list)→groupby('variable_0').apply(list)):先按 widget + task 分组,汇总元组;再按 widget 分组,汇总各 task 的列表,最终调用.to_list()得到目标结构。
完整实现代码
import pandas as pd
import numpy as np
def df_to_components(df: pd.DataFrame) -> list:
"""
将 MultiIndex 列(形如 ('Widget A', 't1'))的 DataFrame 转换为嵌套组件列表。
返回结构:
[
[ # Widget A
[(1, 10)], # t1
[(2, 15), (3, 23), ...], # t2
...
],
[ # Widget B
...
],
...
]
"""
return (
df.reset_index()
.melt(id_vars=df.index.names or ['resources'],
value_name='value')
.dropna(subset='value')
.assign(
machine_id=lambda x: x[df.index.names[0] if df.index.names else 'resources']
.str.extract(r'm_(\d+)')[0].astype(int),
tmp=lambda x: list(zip(x['machine_id'], x['value'].astype(int)))
)
.groupby(['variable_0', 'variable_1'], sort=False)['tmp']
.apply(list)
.groupby('variable_0', sort=False)
.apply(list)
.to_list()
)
# ✅ 示例验证(使用问题中的最小可复现数据)
data = [[1, np.nan, np.nan],
[np.nan, 2, 2],
[np.nan, 3, np.nan]]
m_idx = pd.MultiIndex.from_tuples([('A', 't1'), ('A', 't2'), ('B', 't1')])
idx = pd.Index([f'm_{i}' for i in range(1, 4)], name='resources')
df = pd.DataFrame(data, columns=m_idx, index=idx)
components = df_to_components(df)
print(components)
# 输出:[[[(1, 1)], [(2, 2), (3, 3)]], [[(2, 2)]]]
关键注意事项
-
列名兼容性:本方案假设 MultiIndex 列的层级为
(widget_name, task_id),且行索引名称为'resources'(或可通过df.index.names[0]获取)。若实际索引名不同,请在melt()和assign()中显式指定; -
机器 ID 解析鲁棒性:示例中使用正则
r'm_(\d+)'提取数字,适用于m_1,m_12等格式;若命名规则更复杂(如machine_alpha),请替换.str.extract(...)部分; -
NaN 处理:
dropna(subset='value')严格过滤空值,确保输出中无None或np.nan元组; -
顺序保持:所有
groupby(..., sort=False)显式禁用排序,严格保留原始列/行顺序(对调度任务时序至关重要); -
性能提示:该方案全程向量化,对数千列、百万级非空单元格仍高效;若数据极大,可考虑分块处理或使用
dask替代。
总结
该方法以声明式链式调用替代隐式循环,兼具可读性、可维护性与执行效率。它不仅精准还原了问题中 Fig 2 所示的嵌套元组结构,更抽象出通用范式:任何“宽表→长表→结构化嵌套”的转换任务,均可通过 melt → dropna → assign → groupby ×2 → to_list 流水线解决。掌握此模式,可快速适配各类资源-任务-时间三维建模场景。










