本文介绍一种使用 pandas 高效实现“按 7 小时/天上限、跳过周末、顺序填充工时”的自动化方案,适用于项目排期、资源调度等场景。核心思路是基于业务日偏移动态生成 plannedstart,并智能拆分 hours 字段。
在实际项目管理或人力资源排班中,常需将总工时(如 15 小时)按「单日最多 7 小时」「仅限周一至周五」的约束,从最早起始日开始连续分配。难点在于:工时分配不是独立的——后序 ID 必须避开前序已占满的工作日,即需全局顺序排程,而非简单按 ID 分组处理。
幸运的是,pandas 提供了优雅的向量化解决方案,无需显式循环或状态维护。关键在于利用 BusinessDay 偏移与 groupby.cumcount() 的协同,实现「按需重复行 + 按组递增工作日 + 精确拆分尾数」三步闭环。
首先确保日期列已转为 datetime64 类型(已含在原始代码中):
import pandas as pd
import numpy as np
df = pd.DataFrame({
'ID': [1, 2, 3],
'EarliestStart': ['28.09.2025', '29.09.2025', '15.11.2025'],
'Hours': [15.00, 5.00, 23.00]
})
df['EarliestStart'] = pd.to_datetime(df['EarliestStart'], format='%d.%m.%Y')接着执行核心逻辑:
# Step 1: 按所需天数重复每行(向上取整:ceil(Hours / 7))
out = df.loc[df.index.repeat(np.ceil(df['Hours'].div(7)))]
# Step 2: 为每组(原 ID)添加递增的 BusinessDay 偏移
n = out.groupby(level=0).cumcount()
out['PlannedStart'] = out['EarliestStart'] + n * pd.offsets.BusinessDay()
# Step 3: 提取星期名称(自动本地化,如 'Thursday')
out['Weekday'] = out['PlannedStart'].dt.day_name()
# Step 4: 计算每日分配工时 —— 前 N−1 天为 7h,最后一天为余数(若余数为 0,则最后两天均分?不,此处按题意设为 7)
s = out.pop('Hours').mod(7)
out['HoursSplitted'] = np.where(
out.index.duplicated(keep='last') | s.eq(0),
7.0,
s
)
# 可选:格式化输出为欧洲风格(dd.MM.yyyy)及千分位逗号
out['PlannedStart'] = out['PlannedStart'].dt.strftime('%d.%m.%Y')
out['EarliestStart'] = out['EarliestStart'].dt.strftime('%d.%m.%Y')
out['HoursSplitted'] = out['HoursSplitted'].apply(lambda x: f"{x:.2f}".replace('.', ','))
该方案以声明式风格替代过程式逻辑,充分利用 pandas 的索引对齐与时间偏移能力,兼具性能与可读性。它不仅解决了示例中的 7 小时/天、跳过周末、全局顺序占用等约束,还可轻松扩展支持:
? 自定义日工作上限(修改除数 7);
? 多班次/多资源并行排程(增加分组维度);
? 加入优先级权重或截止日期约束(配合 sort_values 预排序)。
对于中等规模数据(